Compare commits

..

139 Commits

Author SHA1 Message Date
11638ac43c 일반견적서 pdf 변환 메일 첨부 수정 2025-11-04 09:46:17 +09:00
50baa3d75e 일반견적서 템플릿 변경, 견적서 pdf 변환하여 메일 첨부 2025-11-03 17:11:32 +09:00
399062a9a0 Merge remote-tracking branch 'origin/main' into feature/new-work 2025-11-03 17:09:23 +09:00
ded4a70b01 Merge pull request 'V20251023001' (#52) from V20251023001 into main
Reviewed-on: #52
2025-11-03 02:37:14 +00:00
Johngreen
44b43df82b Merge branch 'main' into V20251023001 2025-11-03 11:36:53 +09:00
f26e92d9e3 Merge pull request '커넥션설정 보완작업' (#51) from V20251023001 into main
Reviewed-on: #51
2025-11-03 02:36:49 +00:00
Johngreen
4ca4bd2227 커넥션설정 보완작업 2025-11-03 11:34:48 +09:00
0dd9b8ab6d Merge pull request 'V20251023001' (#50) from V20251023001 into main
Reviewed-on: #50
2025-10-31 03:00:56 +00:00
Johngreen
37980f8565 Merge branch 'main' into V20251023001 2025-10-31 12:00:40 +09:00
7558206e8b Merge pull request '파트등록, 파트 조회 메뉴에도 도면 업로드 기능 추가' (#49) from V20251023001 into main
Reviewed-on: #49
2025-10-31 03:00:36 +00:00
Johngreen
1ad393e939 파트등록, 파트 조회 메뉴에도 도면 업로드 기능 추가 2025-10-31 12:00:17 +09:00
f9b20817a3 Merge pull request 'V20251023001' (#48) from V20251023001 into main
Reviewed-on: #48
2025-10-31 02:18:04 +00:00
Johngreen
49dd4b7c88 Merge branch 'main' into V20251023001 2025-10-31 11:17:41 +09:00
Johngreen
37285fbce6 Merge branch 'main' into V20251023001 2025-10-31 11:17:34 +09:00
e29305c1dc Merge pull request 'V20251023001' (#47) from V20251023001 into main
Reviewed-on: #47
2025-10-31 02:17:31 +00:00
Johngreen
a41c75c0f8 품번.확장자” 로 했을경우 업로드시 정확한 위치 업로드 버그수정 2025-10-31 11:17:05 +09:00
d4500e3f0b Merge pull request 'feature/new-work' (#46) from feature/new-work into main
Reviewed-on: #46
2025-10-31 01:29:55 +00:00
b5e50366d4 Merge branch 'main' into feature/new-work 2025-10-31 10:29:16 +09:00
8bb05b060c Merge branch 'main' into feature/new-work 2025-10-31 10:28:55 +09:00
21652fc0d1 Merge pull request 'e-bom 팝업창 항목수량(part_mng.qty -> bom_part_qty.item_qty)' (#45) from feature/new-work into main
Reviewed-on: #45
2025-10-31 01:28:45 +00:00
590ab6531b Merge pull request 'V20251023001' (#44) from V20251023001 into main
Reviewed-on: #44
2025-10-31 01:24:46 +00:00
Johngreen
b58b021dc9 Merge branch 'main' into V20251023001 2025-10-31 10:23:51 +09:00
Johngreen
eef76746ac Merge branch 'main' into V20251023001 2025-10-31 10:19:51 +09:00
a0acfc8067 Merge pull request 'V20251023001' (#43) from V20251023001 into main
Reviewed-on: #43
2025-10-31 01:19:43 +00:00
Johngreen
37d5427236 Merge branch 'main' into V20251023001 2025-10-31 10:19:19 +09:00
5d07b23098 Merge pull request 'V20251023001' (#42) from V20251023001 into main
Reviewed-on: #42
2025-10-31 01:19:07 +00:00
Johngreen
3f15362325 Merge branch 'main' into V20251023001 2025-10-31 10:18:40 +09:00
ee0389ad8a Merge pull request 'V20251023001' (#41) from V20251023001 into main
Reviewed-on: #41
2025-10-31 01:18:10 +00:00
87d6e0db0f e-bom 팝업창 항목수량(part_mng.qty -> bom_part_qty.item_qty) 2025-10-31 10:17:22 +09:00
Johngreen
7fa815a0cc Merge branch 'main' into V20251023001 2025-10-31 10:17:11 +09:00
aa7b03c959 Merge pull request 'V20251023001' (#40) from V20251023001 into main
Reviewed-on: #40
2025-10-31 01:13:34 +00:00
Johngreen
81d4b5a169 Merge branch 'main' into V20251023001 2025-10-31 10:12:59 +09:00
5cf3394df8 Merge pull request '한글, 특수문자버그수정' (#39) from V20251023001 into main
Reviewed-on: #39
2025-10-31 01:12:40 +00:00
Johngreen
0ac007eeb7 한글, 특수문자버그수정 2025-10-31 10:12:02 +09:00
50ed8a4ee1 Merge pull request 'V20251023001' (#38) from V20251023001 into main
Reviewed-on: #38
2025-10-31 00:32:24 +00:00
Johngreen
79ed8ee646 Merge branch 'main' into V20251023001 2025-10-31 09:31:33 +09:00
Johngreen
8645e74168 Merge branch 'main' into V20251023001 2025-10-31 09:31:23 +09:00
d61856111f Merge pull request '품번, 품명만 입력하고 저장시 빈 내용으로 이봄 리스트 생성(완료)' (#37) from V20251023001 into main
Reviewed-on: #37
2025-10-31 00:31:02 +00:00
Johngreen
5e92d29028 품번, 품명만 입력하고 저장시 빈 내용으로 이봄 리스트 생성(완료) 2025-10-31 09:30:26 +09:00
56d4387519 Merge pull request 'V20251023001' (#36) from V20251023001 into main
Reviewed-on: #36
2025-10-30 09:20:19 +00:00
Johngreen
f6d26de480 Merge branch 'main' into V20251023001 2025-10-30 18:20:04 +09:00
Johngreen
1d3d8f53e7 Merge branch 'main' into V20251023001 2025-10-30 18:20:01 +09:00
75febfdddb Merge pull request 'V20251023001' (#35) from V20251023001 into main
Reviewed-on: #35
2025-10-30 09:19:59 +00:00
Johngreen
c600012172 Merge branch 'main' into V20251023001 2025-10-30 18:19:46 +09:00
7256f40e0f Merge pull request '수정' (#34) from V20251023001 into main
Reviewed-on: #34
2025-10-30 09:19:32 +00:00
8c9ec367af Merge branch 'main' of https://g.wace.me/chpark/wace_plm 2025-10-30 18:11:58 +09:00
d1a3ae950d Revert: PDF 변환 기능 되돌림 (4a9577c) 2025-10-30 18:11:45 +09:00
7b4167d483 Revert: PDF 관련 커밋 2개 되돌림 (4a9577c, 4d56d64) 2025-10-30 18:06:45 +09:00
4d56d649e1 Merge branch 'main' of https://g.wace.me/chpark/wace_plm 2025-10-30 17:41:30 +09:00
Johngreen
6e7bdbf284 수정 2025-10-30 17:40:22 +09:00
25293d9273 Merge pull request 'V20251023001' (#33) from V20251023001 into main
Reviewed-on: #33
2025-10-30 05:49:53 +00:00
Johngreen
5ce29bf7b9 Merge branch 'main' into V20251023001 2025-10-30 14:49:25 +09:00
Johngreen
42bb799b73 이봄리스트>이봄등록 csv 업로드시 항목수량 컬럼 업로드 안되는 버그수정 2025-10-30 14:49:20 +09:00
643ece2589 Merge pull request 'V20251023001' (#32) from V20251023001 into main
Reviewed-on: #32
2025-10-30 04:15:56 +00:00
Johngreen
d09ae17a85 Merge branch 'main' into V20251023001 2025-10-30 13:15:42 +09:00
Johngreen
30b341753c Merge branch 'main' into V20251023001 2025-10-30 13:15:28 +09:00
c8cf1be26b Merge pull request 'M-BOM관리페이지 업데이트' (#31) from V20251023001 into main
Reviewed-on: #31
2025-10-30 04:15:24 +00:00
Johngreen
cfa031b20b M-BOM관리페이지 업데이트 2025-10-30 12:27:47 +09:00
4a9577c554 일반견적서 템플릿 변경, 견적서 pdf 변환하여 메일 첨부 2025-10-29 17:59:19 +09:00
128c3102c5 Merge pull request 'V20251023001' (#30) from V20251023001 into main
Reviewed-on: #30
2025-10-29 03:34:39 +00:00
Johngreen
56ee15565f Merge branch 'main' into V20251023001 2025-10-29 12:34:19 +09:00
Johngreen
7a718f9fe7 Merge branch 'main' into V20251023001 2025-10-29 12:34:12 +09:00
0c44d19201 Merge pull request 'V20251023001' (#29) from V20251023001 into main
Reviewed-on: #29
2025-10-29 03:34:01 +00:00
Johngreen
d4da64ddfb (RPS)E-BOM List 상태 Y 인경우 클릭후 E-BOM등록 누르면 에러가 뜨면서 수정이 안되지만 E-BOM 아이콘을 눌러서 part 조회에서 << >> 로 변경하면 수정이 됨. 안되도록 막아야 함 2025-10-29 12:33:32 +09:00
33f4327726 컬럼 너비 수정 2025-10-29 12:13:00 +09:00
Johngreen
8954ae559e 생산관리_M-BOM관리쪽 e-bom팝업완성 2025-10-29 11:58:56 +09:00
39593ab3bc csv만 업로드, csv 특수문자 2025-10-29 11:53:03 +09:00
c27a20012a Merge branch 'main' of https://g.wace.me/chpark/wace_plm 2025-10-29 11:24:10 +09:00
29989da63e Merge pull request 'V20251023001' (#28) from V20251023001 into main
Reviewed-on: #28
2025-10-29 02:15:08 +00:00
Johngreen
91b97be376 생산관리_M-BOM관리페이지 e-bom까지 완료 2025-10-29 11:14:13 +09:00
c7b4238a54 part, bom 공급업체 선택->입력으로 수정 2025-10-29 11:11:21 +09:00
Johngreen
7a37d6efd0 Merge branch 'main' into V20251023001 2025-10-28 18:05:14 +09:00
Johngreen
84b4984112 Merge branch 'main' into V20251023001 2025-10-28 18:05:10 +09:00
2998a7609d Merge pull request '대소문자 구분 없이 검색가능하게 수정' (#27) from V20251023001 into main
Reviewed-on: #27
2025-10-28 08:34:00 +00:00
Johngreen
3c1a16f43c 대소문자 구분 없이 검색가능하게 수정 2025-10-28 17:33:27 +09:00
523ad33216 Merge pull request 'V20251023001' (#26) from V20251023001 into main
Reviewed-on: #26
2025-10-28 08:27:58 +00:00
Johngreen
9ff43f0781 Merge branch 'main' into V20251023001 2025-10-28 17:27:35 +09:00
Johngreen
775d56a7f9 Merge branch 'main' into V20251023001 2025-10-28 17:27:29 +09:00
1d72f21e15 Merge pull request 'V20251023001' (#25) from V20251023001 into main
Reviewed-on: #25
2025-10-28 08:27:18 +00:00
Johngreen
697dbe84a8 Merge branch 'main' into V20251023001 2025-10-28 17:26:42 +09:00
3c1c27c119 Merge pull request '생산관리_M-BOM관리 생성' (#24) from V20251023001 into main
Reviewed-on: #24
2025-10-28 08:26:39 +00:00
Johngreen
318e371249 생산관리_M-BOM관리 생성 2025-10-28 17:26:18 +09:00
5613e3bd54 Merge pull request 'V20251023001' (#23) from V20251023001 into main
Reviewed-on: #23
2025-10-28 05:12:58 +00:00
Johngreen
661e1df716 Merge branch 'main' into V20251023001 2025-10-28 14:12:42 +09:00
Johngreen
e8650f058d E-BOM 확인/수정쪽 검색시 품목데이터 표현 2025-10-28 14:12:39 +09:00
104378e52f Merge pull request 'V20251023001' (#22) from V20251023001 into main
Reviewed-on: #22
2025-10-28 04:35:48 +00:00
Johngreen
59359559a6 Merge branch 'main' into V20251023001 2025-10-28 13:33:26 +09:00
Johngreen
a297e660d4 E-BOM 확인/수정쪽 속도개선 2025-10-28 13:33:21 +09:00
08d58ec68e Merge remote-tracking branch 'origin/main' 2025-10-28 13:15:13 +09:00
ace0e3c35f 개발 수정 2025-10-28 13:08:26 +09:00
Johngreen
2feeb8c341 Merge branch 'main' into V20251023001 2025-10-28 11:13:53 +09:00
Johngreen
e149bc99f1 Merge branch 'main' into V20251023001 2025-10-28 11:13:48 +09:00
1db4c83499 Merge pull request '제품관리_PART 등록 데이터 select 속도개선' (#21) from V20251023001 into main
Reviewed-on: #21
2025-10-28 02:13:35 +00:00
Johngreen
c793cd05ae 제품관리_PART 등록 데이터 select 속도개선 2025-10-28 11:13:11 +09:00
e95d38cff6 Merge pull request 'V20251023001' (#20) from V20251023001 into main
Reviewed-on: #20
2025-10-28 02:03:14 +00:00
Johngreen
f944e754b6 E-BOM List, 제품관리_PART 등록페이지 속도개선 2025-10-28 11:02:44 +09:00
Johngreen
c46f118f58 Merge branch 'main' into V20251023001 2025-10-27 21:52:55 +09:00
Johngreen
e2c60f4d63 Merge branch 'main' into V20251023001 2025-10-27 21:52:46 +09:00
7ae55dfa1f Merge pull request '3D, 2D, PDF 아이콘 눌려서 파일 개별 첨부하는 곳 첨부 안되는부분수정완료' (#19) from V20251023001 into main
Reviewed-on: #19
2025-10-27 12:50:07 +00:00
Johngreen
caa19e50d1 3D, 2D, PDF 아이콘 눌려서 파일 개별 첨부하는 곳 첨부 안되는부분수정완료 2025-10-27 21:48:26 +09:00
5f7b435c70 Merge pull request 'E-BOM 확인/수정 파일업로드기능추가' (#18) from V20251023001 into main
Reviewed-on: #18
2025-10-27 12:03:57 +00:00
Johngreen
657a8d3234 E-BOM 확인/수정 파일업로드기능추가 2025-10-27 21:03:21 +09:00
9c3bea7e95 Merge pull request 'V20251023001' (#17) from V20251023001 into main
Reviewed-on: #17
2025-10-27 08:17:19 +00:00
Johngreen
ff74ac99ca Merge branch 'main' into V20251023001 2025-10-27 17:15:35 +09:00
Johngreen
d69dfe8d95 Remove build artifact: partMng.xml from classes directory 2025-10-27 17:15:10 +09:00
Johngreen
01ebc04dfd Merge branch 'main' into V20251023001 2025-10-27 17:12:43 +09:00
37702b64eb Merge pull request 'E-BOM 확인/수정 품목추가로직수정' (#16) from V20251023001 into main
Reviewed-on: #16
2025-10-27 08:12:22 +00:00
Johngreen
af1c73345d E-BOM 확인/수정 품목추가로직수정 2025-10-27 17:11:57 +09:00
15af60c44e Merge remote-tracking branch 'origin/main' 2025-10-27 16:30:01 +09:00
6969e469dc E-BOM 조회 정전개, 역전개 2025-10-27 16:27:17 +09:00
bff85e1da5 Merge pull request 'V20251023001' (#15) from V20251023001 into main
Reviewed-on: #15
2025-10-27 05:30:19 +00:00
Johngreen
588fef91ce Update structurePopupLeft.jsp 2025-10-27 14:29:56 +09:00
Johngreen
df0a534f0e dd 2025-10-27 14:05:40 +09:00
Johngreen
bc6a7dcf7e Update partMng.xml 2025-10-27 14:04:06 +09:00
934c220abc Merge branch 'main' of https://g.wace.me/chpark/wace_plm 2025-10-27 13:41:53 +09:00
a802714411 E-BOM List, part 등록 수정 2025-10-27 13:38:51 +09:00
13164de870 Merge pull request 'V20251023001' (#14) from V20251023001 into main
Reviewed-on: #14
2025-10-27 04:15:01 +00:00
Johngreen
a19388eb1c 상단 E-BOM 품명 안나오는문제 수정 2025-10-27 13:14:32 +09:00
Johngreen
1d93f6ef3a 상단 E-BOM 품명 안나오는문제 해결 2025-10-27 12:09:22 +09:00
ba7edde4da Merge remote-tracking branch 'origin/main' 2025-10-24 17:49:22 +09:00
709f2b4d4b 봄 등록, csv 적용, 모품번 없이 저장 기능 개발, 엑셀 새로 업로드 기능 추가 2025-10-24 17:47:21 +09:00
5514ab3bb7 Merge pull request 'V20251023001' (#13) from V20251023001 into main
Reviewed-on: #13
2025-10-24 07:56:48 +00:00
Johngreen
63043f6909 E-BOM 확인/수정 완료 2025-10-24 16:56:00 +09:00
Johngreen
2b12fc3f6e E-BOM 확인/수정 2025-10-24 15:52:14 +09:00
23ebcd3b88 봄 복사기능 추가 2025-10-24 15:25:54 +09:00
967b8b6529 디자인 변경 2025-10-24 15:24:39 +09:00
b463ab84e3 봄 구조등록 수정. 봄 복사 개발 전 2025-10-24 14:26:55 +09:00
09d225803d Merge pull request '프로젝트일정관리 업데이트' (#12) from V20251023001 into main
Reviewed-on: #12
2025-10-23 08:41:01 +00:00
Johngreen
9c8d71c924 프로젝트일정관리 업데이트 2025-10-23 17:39:47 +09:00
daf5d9bce4 Merge pull request 'V20251023001' (#11) from V20251023001 into main
Reviewed-on: #11
2025-10-23 07:21:42 +00:00
Johngreen
1185aa491f 프로젝트관리_일정관리(WBS)3페이지 출하일 추가 2025-10-23 16:20:00 +09:00
Johngreen
dff92cedf7 프로젝트관리_일정관리업데이트 2025-10-23 15:52:53 +09:00
3a72111b20 Merge pull request '판매관리 검색필터부분 매출관리랑 맞춤' (#10) from V2025101701 into main
Reviewed-on: #10
2025-10-23 04:30:34 +00:00
a4d1af4d91 Merge pull request 'V2025101701' (#9) from V2025101701 into main
Reviewed-on: #9
2025-10-23 04:19:22 +00:00
5d288975ae Merge pull request '판매관리, 매출관리 업데이트' (#8) from V2025101701 into main
Reviewed-on: #8
2025-10-23 02:31:28 +00:00
f00462ed70 Merge remote-tracking branch 'origin/main' 2025-10-23 09:54:13 +09:00
42d1cd4cd0 주문서첨부파일, 견적관리 틀고정 수정 2025-10-23 09:50:58 +09:00
Johngreen
f171212092 ㄹㄹ 2025-10-22 21:14:01 +09:00
221a2a3f8a Merge pull request 'V2025101701' (#7) from V2025101701 into main
Reviewed-on: #7
2025-10-22 12:06:26 +00:00
47 changed files with 6691 additions and 9403 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 426 KiB

File diff suppressed because it is too large Load Diff

View File

@@ -2869,4 +2869,138 @@
</if>
ORDER BY SUBSTRING(PROJECT_NO,POSITION('-' IN PROJECT_NO)+1) DESC
</select>
<!-- M-BOM 관리 목록 조회 -->
<select id="mBomMgmtGridList" parameterType="map" resultType="com.pms.common.UpperKeyMap">
SELECT
PM.OBJID,
PM.CONTRACT_OBJID,
PM.PROJECT_NO,
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,
TO_CHAR(PM.REGDATE, 'YYYY-MM-DD') AS RECEIPT_DATE,
CM.CUSTOMER_OBJID,
COALESCE(
(SELECT SUPPLY_NAME FROM SUPPLY_MNG WHERE OBJID = CM.CUSTOMER_OBJID::NUMERIC LIMIT 1),
''
) AS CUSTOMER_NAME,
CM.PAID_TYPE,
COALESCE(
(SELECT CODE_NAME FROM COMM_CODE WHERE CODE_ID = CM.PAID_TYPE LIMIT 1),
CASE
WHEN CM.PAID_TYPE = 'paid' THEN '유상'
WHEN CM.PAID_TYPE = 'free' THEN '무상'
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에서 조회
(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'
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(
(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(
(SELECT PBR.STATUS
FROM PART_BOM_REPORT PBR
WHERE PBR.OBJID::VARCHAR = 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
LIMIT 1),
''
) AS EBOM_REGDATE,
COALESCE(PM.MBOM_STATUS, '') AS MBOM_STATUS,
'1.0' AS MBOM_VERSION,
TO_CHAR(PM.REGDATE, 'YYYY-MM-DD') AS MBOM_REGDATE
FROM
PROJECT_MGMT PM
LEFT JOIN CONTRACT_MGMT CM ON PM.CONTRACT_OBJID = CM.OBJID
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}) || '%'
</if>
<!-- 품명 검색 (대소문자 구분 없음) -->
<if test="search_part_name != null and search_part_name != ''">
AND UPPER(PM.PART_NAME) LIKE '%' || UPPER(#{search_part_name}) || '%'
</if>
ORDER BY PM.REGDATE DESC
</select>
<!-- E-BOM을 PROJECT_MGMT에 할당 -->
<update id="assignEbomToProject" parameterType="map">
UPDATE PROJECT_MGMT
SET
PART_OBJID = #{bomReportObjid}
WHERE OBJID = #{projectMgmtObjid}
</update>
<!-- E-BOM 정보 조회 -->
<select id="getEbomInfo" parameterType="map" resultType="com.pms.common.UpperKeyMap">
SELECT
T.OBJID,
T.PRODUCT_CD,
CODE_NAME(T.PRODUCT_CD) 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
FROM
PART_BOM_REPORT T
LEFT JOIN USER_INFO UI ON UI.USER_ID = T.WRITER
WHERE T.OBJID::VARCHAR = #{objid}
</select>
</mapper>

View File

@@ -3916,22 +3916,46 @@
ELSE O.PAID_TYPE
END
FROM CONTRACT_MGMT AS O WHERE O.OBJID = T.CONTRACT_OBJID) AS FREE_OF_CHARGE
,(SELECT SUM(O.QUANTITY) FROM CONTRACT_ITEM AS O WHERE O.CONTRACT_OBJID = T.CONTRACT_OBJID) AS CONTRACT_QTY
,(SELECT STRING_AGG(O.PART_NO, ', ' ORDER BY O.SEQ) FROM CONTRACT_ITEM AS O WHERE O.CONTRACT_OBJID = T.CONTRACT_OBJID AND O.PART_NO IS NOT NULL AND O.PART_NO != '') AS PRODUCT_ITEM_CODE
,(SELECT STRING_AGG(O.PART_NAME, ', ' ORDER BY O.SEQ) FROM CONTRACT_ITEM AS O WHERE O.CONTRACT_OBJID = T.CONTRACT_OBJID AND O.PART_NAME IS NOT NULL AND O.PART_NAME != '') AS PRODUCT_ITEM_NAME
,(SELECT STRING_AGG(S.SERIAL_NO, ', ' ORDER BY S.SEQ)
FROM CONTRACT_ITEM AS I
LEFT JOIN CONTRACT_ITEM_SERIAL AS S ON S.ITEM_OBJID = I.OBJID AND S.STATUS = 'ACTIVE'
WHERE I.CONTRACT_OBJID = T.CONTRACT_OBJID
AND S.SERIAL_NO IS NOT NULL) AS SERIAL_NO
-- 영업관리_주문서관리_수주등록
,EBOM_STATUS
,MBOM_STATUS
,(SELECT O.PURCHASE_DATE FROM PURCHASE_ORDER_MASTER AS O WHERE O.CONTRACT_MGMT_OBJID = T.CONTRACT_OBJID LIMIT 1) AS ORDER_DATE
,RECEIVING_RATE
,PRODUCTION_TEAM_12
,PRODUCTION_TEAM_3
,(SELECT O.SHIPPING_DATE::TEXT FROM SHIPMENT_LOG AS O WHERE O.TARGET_OBJID = T.CONTRACT_OBJID LIMIT 1) AS SHIPMENT_DATE
,COALESCE(T.QUANTITY::numeric, 0) AS CONTRACT_QTY
,T.PART_NO AS PRODUCT_ITEM_CODE
,T.PART_NAME AS PRODUCT_ITEM_NAME
-- S/N: 해당 품목의 시리얼 번호 (여러 개일 경우 "S/N 외 N건" 형식)
,(SELECT
CASE
WHEN COUNT(*) = 0 THEN ''
WHEN COUNT(*) = 1 THEN MIN(S.SERIAL_NO)
ELSE MIN(S.SERIAL_NO) || ' 외 ' || (COUNT(*) - 1)::TEXT || '건'
END
FROM CONTRACT_ITEM AS I
LEFT JOIN CONTRACT_ITEM_SERIAL AS S ON S.ITEM_OBJID = I.OBJID AND S.STATUS = 'ACTIVE'
WHERE I.CONTRACT_OBJID = T.CONTRACT_OBJID
AND I.PART_OBJID = T.PART_OBJID
AND S.SERIAL_NO IS NOT NULL) AS SERIAL_NO
-- 요청납기: CONTRACT_ITEM 우선, 없으면 PROJECT_MGMT.DUE_DATE, 없으면 CONTRACT_MGMT.due_date
,COALESCE(
(SELECT CI.DUE_DATE
FROM CONTRACT_ITEM CI
WHERE CI.CONTRACT_OBJID = T.CONTRACT_OBJID
AND CI.PART_OBJID = T.PART_OBJID
AND CI.STATUS = 'ACTIVE'),
T.DUE_DATE,
(SELECT CM.due_date FROM CONTRACT_MGMT CM WHERE CM.OBJID = T.CONTRACT_OBJID)
) AS REQ_DEL_DATE
-- 영업관리_주문서관리_수주등록
,EBOM_STATUS
,MBOM_STATUS
-- 발주일: CONTRACT_MGMT 테이블에서 가져오기 (영업관리_주문서관리와 동일)
,(SELECT O.ORDER_DATE FROM CONTRACT_MGMT AS O WHERE O.OBJID = T.CONTRACT_OBJID) AS ORDER_DATE
,RECEIVING_RATE
,PRODUCTION_TEAM_12
,PRODUCTION_TEAM_3
-- 출하일: sales_registration 테이블에서 가져오기 (영업관리_판매관리와 동일)
,COALESCE(
(SELECT TO_CHAR(SR.shipping_date, 'YYYY-MM-DD')
FROM sales_registration SR
WHERE SR.project_no = T.PROJECT_NO),
''
) AS SHIPMENT_DATE
,(((SELECT SUM(COALESCE(DESIGN_RATE,'0')::INTEGER) / COUNT(1) FROM PMS_WBS_TASK AS O WHERE O.CONTRACT_OBJID = T.OBJID)
+(SELECT SUM(COALESCE(PURCHASE_RATE,'0')::INTEGER) / COUNT(1) FROM PMS_WBS_TASK AS O WHERE O.CONTRACT_OBJID = T.OBJID)
+(SELECT SUM(COALESCE(PRODUCE_RATE,'0')::INTEGER) / COUNT(1) FROM PMS_WBS_TASK AS O WHERE O.CONTRACT_OBJID = T.OBJID)

View File

@@ -816,6 +816,7 @@
<select id="getSalesMgmtGridList" parameterType="map" resultType="map">
/* salesNcollectMgmt.getSalesMgmtGridList - sales_registration LEFT JOIN으로 최적화 */
SELECT
T.OBJID,
T.PROJECT_NO,
T.CONTRACT_OBJID,
CODE_NAME(T.CATEGORY_CD) AS ORDER_TYPE,
@@ -832,7 +833,7 @@
FROM CONTRACT_MGMT CM WHERE CM.OBJID = T.CONTRACT_OBJID) AS PAYMENT_TYPE,
T.PART_NO AS PRODUCT_NO,
T.PART_NAME AS PRODUCT_NAME,
-- S/N: CONTRACT_ITEM_SERIAL에서만 조회 (품목별로 정확하게 매칭)
-- S/N: 해당 품목의 시리얼 번호 (여러 개일 경우 "S/N 외 N건" 형식)
(SELECT
CASE
WHEN COUNT(*) = 0 THEN ''
@@ -840,11 +841,10 @@
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
LEFT JOIN CONTRACT_ITEM_SERIAL CIS ON CI.OBJID = CIS.ITEM_OBJID AND UPPER(CIS.STATUS) = 'ACTIVE'
WHERE CI.CONTRACT_OBJID = T.CONTRACT_OBJID
AND CI.PART_OBJID = T.PART_OBJID
AND CI.STATUS = 'ACTIVE'
AND UPPER(CIS.STATUS) = 'ACTIVE'
AND CIS.SERIAL_NO IS NOT NULL) AS SERIAL_NO,
COALESCE(T.QUANTITY::numeric, 0) AS ORDER_QUANTITY,
-- 요청납기: CONTRACT_ITEM 우선, 없으면 PROJECT_MGMT, 없으면 CONTRACT_MGMT
@@ -898,6 +898,8 @@
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 != ''">
AND T.CATEGORY_CD = #{orderType}
</if>
@@ -914,6 +916,9 @@
AND SUPPLY_NAME LIKE CONCAT('%', #{customer}, '%')
)
</if>
<if test="customer_objid != null and customer_objid != ''">
AND T.CUSTOMER_OBJID = #{customer_objid}
</if>
<if test="paymentType != null and paymentType != ''">
AND EXISTS (
SELECT 1 FROM CONTRACT_MGMT CM
@@ -975,7 +980,8 @@
<if test="incoterms != null and incoterms != ''">
/* INCOTERMS 필드 없음 - 검색 조건 무시 */
</if>
ORDER BY T.REGDATE DESC
-- 등록일 기준 최신순 정렬 (프로젝트 번호는 보조 정렬)
ORDER BY T.REGDATE DESC, T.PROJECT_NO DESC
</select>
<!-- 매출관리 그리드 목록 개수 - project_mgmt 테이블 기반 -->
@@ -986,6 +992,8 @@
COUNT(1)::integer AS TOTAL_CNT
FROM PROJECT_MGMT AS T
WHERE 1 = 1
AND T.PROJECT_NO IS NOT NULL
AND T.PROJECT_NO != ''
<if test="orderType != null and orderType != ''">
AND T.CATEGORY_CD = #{orderType}
</if>
@@ -1002,6 +1010,9 @@
AND SUPPLY_NAME LIKE CONCAT('%', #{customer}, '%')
)
</if>
<if test="customer_objid != null and customer_objid != ''">
AND T.CUSTOMER_OBJID = #{customer_objid}
</if>
<if test="paymentType != null and paymentType != ''">
AND EXISTS (
SELECT 1 FROM CONTRACT_MGMT CM
@@ -1321,16 +1332,19 @@
FROM CONTRACT_MGMT CM WHERE CM.OBJID = T.CONTRACT_OBJID) AS PAYMENT_TYPE,
T.PART_NO AS PRODUCT_NO,
T.PART_NAME AS PRODUCT_NAME,
-- S/N 정보 (CONTRACT_ITEM_SERIAL에서만 조회, 품목별로 정확하게 매칭)
(SELECT STRING_AGG(CIS.SERIAL_NO, ',' ORDER BY CIS.SEQ)
FROM CONTRACT_ITEM CI
LEFT JOIN CONTRACT_ITEM_SERIAL CIS ON CI.OBJID = CIS.ITEM_OBJID
WHERE CI.CONTRACT_OBJID = T.CONTRACT_OBJID
AND CI.PART_OBJID = T.PART_OBJID
AND CI.STATUS = 'ACTIVE'
AND UPPER(CIS.STATUS) = 'ACTIVE'
AND CIS.SERIAL_NO IS NOT NULL
) AS SERIAL_NO,
-- S/N: 해당 품목의 시리얼 번호 (여러 개일 경우 "S/N 외 N건" 형식)
(SELECT
CASE
WHEN COUNT(*) = 0 THEN ''
WHEN COUNT(*) = 1 THEN MIN(CIS.SERIAL_NO)
ELSE MIN(CIS.SERIAL_NO) || ' 외 ' || (COUNT(*) - 1)::TEXT || '건'
END
FROM CONTRACT_ITEM CI
LEFT JOIN CONTRACT_ITEM_SERIAL CIS ON CI.OBJID = CIS.ITEM_OBJID AND UPPER(CIS.STATUS) = 'ACTIVE'
WHERE CI.CONTRACT_OBJID = T.CONTRACT_OBJID
AND CI.PART_OBJID = T.PART_OBJID
AND CI.STATUS = 'ACTIVE'
AND CIS.SERIAL_NO IS NOT NULL) AS SERIAL_NO,
COALESCE(T.QUANTITY::numeric, 0) AS ORDER_QUANTITY,
-- 요청납기: CONTRACT_ITEM 우선, 없으면 PROJECT_MGMT, 없으면 CONTRACT_MGMT
COALESCE(

View File

@@ -42,6 +42,7 @@
<property name="suffix" value=".jsp"/>
<property name="order" value="1"/>
</bean>
<bean id="jacksonMessageConverter" class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter"></bean>
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="messageConverters">

View File

@@ -25,7 +25,7 @@ $(document).ready(function(){
$("#btnUpload").click(function(){
var files = $("#file1")[0].files;
if(files.length > 0){
fnc_fileMultiUpload(files, null, "${param.targetObjId}", "${param.docType}", "${param.docTypeName}", null, "srAreaDraw",null, "${param.parentTargetObjId}");
fnc_fileMultiUpload(files, null, "${param.targetObjId}", "${param.docType}", "${param.docTypeName}", null, "refeshAttachFileArea",null, "${param.parentTargetObjId}");
//file객체 초기화
$("#file1").val("");
}else{
@@ -39,6 +39,14 @@ $(document).ready(function(){
function refeshAttachFileArea(){
srAreaDraw();
// 부모 창의 그리드도 새로고침
if(opener && typeof opener.fn_search == "function"){
opener.fn_search();
}
// Tabulator 그리드가 있는 경우
if(opener && opener._tabulGrid){
opener._tabulGrid.replaceData();
}
}
//형상 영역을 display 한다.
@@ -94,19 +102,15 @@ function fn_fileCallback(areaId,fileType){
appendText +=" <col width='20%'>";
appendText +=" </colgroup>";
*/
appendText+= "<tr>";
appendText+= " <td>"+[i+1]+"</td>";
appendText+= " <td class='align_l'><a href='javascript:fnc_downloadFile(\""+data[i].OBJID+"\")'>&nbsp;&nbsp;"+data[i].REAL_FILE_NAME+"</a>";
/* if(data[i].WRITER=="${connectUserId}" || 'plm_admin'== "${connectUserId}"){
appendText+= "<a href='javascript:fileDelete(\""+data[i].OBJID+"\",\""+areaId+"\")'><div class='delete_btn'></div></a>";
}
*/
appendText+= "</td>";
appendText+= " <td>"+data[i].REGDATE+"</td>"
appendText+= " <td>"+data[i].FILE_SIZE+"</td>"
appendText+= "</tr>";
appendText+= "<tr>";
appendText+= " <td>"+[i+1]+"</td>";
appendText+= " <td class='align_l'><a href='javascript:fnc_downloadFile(\""+data[i].OBJID+"\")'>&nbsp;&nbsp;"+data[i].REAL_FILE_NAME+"</a>";
// 도면 삭제 버튼 활성화
appendText+= "<a href='javascript:fileDelete(\""+data[i].OBJID+"\")'><div class='delete_btn'></div></a>";
appendText+= "</td>";
appendText+= " <td>"+data[i].REGDATE+"</td>"
appendText+= " <td>"+data[i].FILE_SIZE+"</td>"
appendText+= "</tr>";
}
//Swal.fire(appendText);
$("#"+areaId+"FileArea").append(appendText);
@@ -130,23 +134,57 @@ function fn_fileCallback(areaId,fileType){
/*첨부 파일 삭제 */
function fileDelete(fileObjId){
if(confirm("파일을 삭제하시겠습니까?")){
$.ajax({
url:"/common/deleteFileInfo.do",
type:"POST",
data:{"objId":fileObjId},
dataType:"json",
async:true,
success:function(data){
fn_fileCallback("sr","${docType}");
if(fnc_checkNull(callbackFnc) != ""){
opener.eval(callbackFnc+"();");
}
},
error: function(jqxhr, status, error){
}
});
}
Swal.fire({
title: '파일을 삭제하시겠습니까?',
text: '삭제된 파일은 복구할 수 없습니다.',
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#3085d6',
cancelButtonColor: '#d33',
confirmButtonText: '삭제',
cancelButtonText: '취소',
reverseButtons: false
}).then(function(result) {
if (result.isConfirmed) {
$.ajax({
url:"/common/deleteFileInfo.do",
type:"POST",
data:{"objId":fileObjId},
dataType:"json",
async:true,
success:function(data){
Swal.fire({
title: '삭제 완료',
text: '파일이 삭제되었습니다.',
icon: 'success',
confirmButtonText: '확인'
}).then(function() {
fn_fileCallback("sr","${docType}");
// 부모 창의 그리드 새로고침
if(opener && typeof opener.fn_search == "function"){
opener.fn_search();
}
// Tabulator 그리드가 있는 경우
if(opener && opener._tabulGrid){
opener._tabulGrid.replaceData();
}
// 기존 콜백 함수도 실행
if(fnc_checkNull(callbackFnc) != ""){
opener.eval(callbackFnc+"();");
}
});
},
error: function(jqxhr, status, error){
Swal.fire({
title: '삭제 실패',
text: '파일 삭제 중 오류가 발생했습니다.',
icon: 'error',
confirmButtonText: '확인'
});
}
});
}
});
}
</script>
@@ -168,10 +206,11 @@ function fileDelete(fileObjId){
-->
<td rowspan="2" class="input_title align_c">파일첨부</td>
<td colspan="8">
<div id="srDropZone" class="dropzone">Drag & Drop Files Here</div>
<!-- <input type="file" name="file1" id="file1" multiple>
<input type="button" id="btnUpload" value="Upload" class="upload_btns"> -->
<div id="srFileAreaTable" class="spec_data_in_table">
<div style="margin-bottom: 10px;">
<input type="file" name="file1" id="file1" multiple>
<input type="button" id="btnUpload" value="업로드" class="plm_btns">
</div>
<div id="srFileAreaTable" class="spec_data_in_table">
<div style="overflow-y:scroll;width:100%">
<table id="" class="fileListscrollThead" style="width:100% !important;">
<colgroup>

View File

@@ -28,7 +28,7 @@ $(document).ready(function(){
$("#btnUpload").click(function(){
var files = $("#file1")[0].files;
if(files.length > 0){
fnc_fileMultiUpload(files, null, "${param.targetObjId}", "${param.docType}", "${param.docTypeName}", null, "srAreaDraw",null, "${param.parentTargetObjId}");
fnc_fileMultiUpload(files, null, "${param.targetObjId}", "${param.docType}", "${param.docTypeName}", null, "refeshAttachFileArea",null, "${param.parentTargetObjId}");
//file객체 초기화
$("#file1").val("");
}else{
@@ -39,6 +39,14 @@ $(document).ready(function(){
function refeshAttachFileArea(){
srAreaDraw();
// 부모 창의 그리드도 새로고침
if(opener && typeof opener.fn_search == "function"){
opener.fn_search();
}
// Tabulator 그리드가 있는 경우
if(opener && opener._tabulGrid){
opener._tabulGrid.replaceData();
}
}
//형상 영역을 display 한다.
@@ -164,7 +172,16 @@ function fileDelete(fileObjId){
dataType:"json",
async:false,
success:function(data){
fn_fileCallback("sr","${docType}");
fn_fileCallback("sr","${docType}");
// 부모 창의 그리드 새로고침
if(opener && typeof opener.fn_search == "function"){
opener.fn_search();
}
// Tabulator 그리드가 있는 경우
if(opener && opener._tabulGrid){
opener._tabulGrid.replaceData();
}
// 기존 콜백 함수도 실행
if(fnc_checkNull(callbackFnc) != ""){
opener.eval(callbackFnc+"();");
}
@@ -193,9 +210,10 @@ function fileDelete(fileObjId){
<tr>
<td rowspan="2" class="input_title align_c">파일첨부</td>
<td colspan="8">
<div id="srDropZone" class="dropzone">Drag & Drop Files Here</div>
<input type="file" name="file1" id="file1" multiple>
<input type="button" id="btnUpload" value="Upload" class="upload_btns">
<div style="margin-bottom: 10px;">
<input type="file" name="file1" id="file1" multiple>
<input type="button" id="btnUpload" value="업로드" class="plm_btns">
</div>
<div id="srFileAreaTable" class="spec_data_in_table">
<div style="overflow-y:scroll;">
<table id="" class="fileListscrollThead" style="width:100% !important;">

View File

@@ -216,7 +216,7 @@ function fileDelete(fileObjId, areaId, fileType){
<table class="pmsPopupForm">
<!-- FTC주문서 첨부 영역 -->
<tr>
<td class="input_title align_c">FTC주문서</td>
<td class="input_title align_c">FCST주문서</td>
<td colspan="8">
<div style="width: 100%; overflow: hidden; box-sizing: border-box;">
<div id="ftcDropZone" class="dropzone" style="width: 100% !important; max-width: 100%; box-sizing: border-box; margin-bottom: 5px;">Drag & Drop Files Here</div>

View File

@@ -217,7 +217,7 @@ function addComma(num) {
}
var columns = [
{title:'EST_OBJID' ,field:'EST_OBJID' ,visible:false},
{title:'EST_OBJID' ,field:'EST_OBJID' ,visible:false, frozen:true},
{headerHozAlign : 'center', hozAlign : 'center', width : '90', title : '영업번호', field : 'CONTRACT_NO', frozen:true,
formatter:fnc_createGridAnchorTag,
cellClick:function(e, cell){
@@ -668,21 +668,271 @@ function fn_sendEstimateMail(contractObjId){
return;
}
// 1단계: 견적서 템플릿 정보 조회
Swal.fire({
title: '메일 발송 중...',
title: '견적서 조회 중...',
text: '잠시만 기다려주세요.',
allowOutsideClick: false,
didOpen: () => {
onOpen: () => {
Swal.showLoading();
}
});
$.ajax({
url: "/contractMgmt/sendEstimateMail.do",
url: "/contractMgmt/getEstimateTemplateList.do",
type: "POST",
data: { objId: contractObjId },
dataType: "json",
success: function(data){
if(data.result === "success" && data.list && data.list.length > 0){
// 최종 차수 견적서 찾기
var latestEstimate = data.list[0]; // 이미 차수 내림차순으로 정렬되어 있음
var templateObjId = latestEstimate.OBJID || latestEstimate.objid;
var templateType = latestEstimate.TEMPLATE_TYPE || latestEstimate.template_type || latestEstimate.templateType;
// 2단계: 견적서 페이지를 새 창으로 열고 PDF 생성
fn_generatePdfAndSendMail(contractObjId, templateObjId, templateType);
} else {
Swal.close();
Swal.fire({
title: '오류',
text: '견적서를 찾을 수 없습니다.',
icon: 'error'
});
}
},
error: function(xhr, status, error){
Swal.close();
console.error("견적서 조회 오류:", xhr, status, error);
Swal.fire({
title: '오류',
text: '견적서 조회 중 오류가 발생했습니다.',
icon: 'error'
});
}
});
}
// PDF 생성 및 메일 발송
function fn_generatePdfAndSendMail(contractObjId, templateObjId, templateType){
Swal.fire({
title: 'PDF 생성 중...',
text: '견적서를 PDF로 변환하고 있습니다.',
allowOutsideClick: false,
onOpen: () => {
Swal.showLoading();
}
});
// 견적서 페이지 URL 생성
var url = "";
if(templateType === "1"){
url = "/contractMgmt/estimateTemplate1.do?templateObjId=" + templateObjId;
} else if(templateType === "2"){
url = "/contractMgmt/estimateTemplate2.do?templateObjId=" + templateObjId;
}
// 숨겨진 iframe으로 페이지 로드
var iframe = $('<iframe>', {
id: 'pdfGeneratorFrame',
src: url,
style: 'position:absolute;width:0;height:0;border:none;'
}).appendTo('body');
// iframe 로드 완료 대기
iframe.on('load', function(){
try {
var iframeWindow = this.contentWindow;
// 데이터 로딩 완료를 기다림 (최대 10초)
var checkDataLoaded = function(attempts) {
if(attempts > 100) { // 10초 (100ms * 100)
$('#pdfGeneratorFrame').remove();
Swal.close();
Swal.fire({
title: '타임아웃',
text: '견적서 데이터 로딩 시간이 초과되었습니다.',
icon: 'error'
});
return;
}
if(iframeWindow.dataLoaded === true) {
console.log('데이터 로딩 완료 확인, PDF 생성 시작');
// iframe 내의 PDF 생성 함수 호출
if(typeof iframeWindow.fn_generateAndUploadPdf === 'function'){
iframeWindow.fn_generateAndUploadPdf(function(pdfBase64){
// iframe 제거
$('#pdfGeneratorFrame').remove();
if(pdfBase64){
// PDF Base64와 함께 메일 발송 요청
fn_sendMailWithPdf(contractObjId, pdfBase64);
} else {
Swal.close();
Swal.fire({
title: '오류',
text: 'PDF 생성에 실패했습니다.',
icon: 'error'
});
}
});
} else {
// iframe 제거
$('#pdfGeneratorFrame').remove();
Swal.close();
Swal.fire({
title: '오류',
text: '견적서 페이지 로드에 실패했습니다.',
icon: 'error'
});
}
} else {
// 아직 로딩 중이면 100ms 후 다시 확인
setTimeout(function() {
checkDataLoaded(attempts + 1);
}, 100);
}
};
// 데이터 로딩 체크 시작
checkDataLoaded(0);
} catch(e) {
console.error('PDF 생성 오류:', e);
$('#pdfGeneratorFrame').remove();
Swal.close();
Swal.fire({
title: '오류',
text: 'PDF 생성 중 오류가 발생했습니다.',
icon: 'error'
});
}
});
// 타임아웃 설정 (30초)
setTimeout(function(){
if($('#pdfGeneratorFrame').length > 0){
$('#pdfGeneratorFrame').remove();
Swal.close();
Swal.fire({
title: '타임아웃',
text: 'PDF 생성 시간이 초과되었습니다.',
icon: 'error'
});
}
}, 30000);
}
// PDF Base64와 함께 메일 발송 (청크 방식)
function fn_sendMailWithPdf(contractObjId, pdfBase64){
console.log('===== 메일 발송 시작 =====');
console.log('contractObjId:', contractObjId);
console.log('PDF Base64 길이:', pdfBase64 ? pdfBase64.length : 0);
console.log('========================');
Swal.fire({
title: '메일 발송 중...',
text: 'PDF 업로드 중...',
allowOutsideClick: false,
onOpen: () => {
Swal.showLoading();
}
});
// 청크 크기: 100KB (Base64) - POST 크기 제한 고려
var chunkSize = 100 * 1024;
var totalChunks = Math.ceil(pdfBase64.length / chunkSize);
var uploadedChunks = 0;
var sessionId = 'pdf_' + contractObjId + '_' + new Date().getTime();
console.log('PDF Base64 전체 길이:', pdfBase64.length);
console.log('청크 크기:', chunkSize);
console.log('총 청크 수:', totalChunks);
// 청크 업로드 함수
function uploadChunk(chunkIndex) {
var start = chunkIndex * chunkSize;
var end = Math.min(start + chunkSize, pdfBase64.length);
var chunk = pdfBase64.substring(start, end);
console.log('청크 ' + (chunkIndex + 1) + '/' + totalChunks + ' 업로드 중...');
$.ajax({
url: "/contractMgmt/uploadPdfChunk.do",
type: "POST",
data: {
sessionId: sessionId,
chunkIndex: chunkIndex,
totalChunks: totalChunks,
chunk: chunk
},
dataType: "json",
timeout: 30000,
success: function(data){
if(data.result === "success"){
uploadedChunks++;
// 진행률 업데이트
var progress = Math.round((uploadedChunks / totalChunks) * 100);
Swal.update({
text: 'PDF 업로드 중... ' + progress + '%'
});
// 다음 청크 업로드
if(chunkIndex + 1 < totalChunks){
uploadChunk(chunkIndex + 1);
} else {
// 모든 청크 업로드 완료, 메일 발송 요청
console.log('모든 청크 업로드 완료, 메일 발송 시작');
sendMailWithUploadedPdf(contractObjId, sessionId);
}
} else {
Swal.close();
Swal.fire({
title: '업로드 실패',
text: 'PDF 업로드 중 오류가 발생했습니다.',
icon: 'error',
confirmButtonText: '확인'
});
}
},
error: function(xhr, status, error){
Swal.close();
console.error("청크 업로드 오류:", xhr, status, error);
Swal.fire({
title: '오류',
text: 'PDF 업로드 중 시스템 오류가 발생했습니다.',
icon: 'error',
confirmButtonText: '확인'
});
}
});
}
// 첫 번째 청크부터 시작
uploadChunk(0);
}
// 업로드된 PDF로 메일 발송
function sendMailWithUploadedPdf(contractObjId, sessionId){
Swal.update({
text: '메일 발송 중...'
});
$.ajax({
url: "/contractMgmt/sendEstimateMail.do",
type: "POST",
data: {
objId: contractObjId,
pdfSessionId: sessionId
},
dataType: "json",
timeout: 60000,
success: function(data){
console.log('메일 발송 응답:', data);
Swal.close();
if(data.result === "success"){
Swal.fire({
@@ -691,7 +941,6 @@ function fn_sendEstimateMail(contractObjId){
icon: 'success',
confirmButtonText: '확인'
}).then(() => {
// 메일 발송 상태 업데이트를 위해 목록 새로고침
fn_search();
});
} else {

View File

@@ -165,9 +165,10 @@ body {
.items-table th,
.items-table td {
border: 1px solid #000;
padding: 6px 8px;
padding: 3px 5px;
text-align: center;
font-size: 9pt;
line-height: 1.3;
}
.items-table th {
@@ -265,11 +266,12 @@ textarea {
textarea {
resize: vertical;
min-height: 30px;
min-height: 25px;
padding: 2px;
}
.editable {
background-color: #fffef0;
background-color: transparent;
}
@media print {
@@ -462,8 +464,8 @@ function fn_calculateAmount(row) {
function fn_calculateTotal() {
var total = 0;
// 품목 행만 순회 (계 행, 원화환산 행, 비고 행 제외)
$("#itemsTableBody tr").not(".total-row, .total-krw-row, .remarks-row").each(function(){
// 품목 행만 순회 (계 행, 원화환산 행, 비고 행, 참조사항 행, 회사명 행 제외)
$("#itemsTableBody tr").not(".total-row, .total-krw-row, .remarks-row, .notes-row, .footer-row").each(function(){
var amount = $(this).find(".item-amount").val() || "0";
// 콤마와 통화 기호 제거 후 숫자로 변환
amount = amount.replace(/,/g, "").replace(/₩/g, "").replace(/\$/g, "").replace(/€/g, "").replace(/¥/g, "");
@@ -580,8 +582,8 @@ function fn_initItemDescSelect(itemId) {
// 행 추가 함수
function fn_addItemRow() {
// 계 행, 원화환산 행, 비고 행 제외하고 품목 행 개수 계산
var itemRows = $("#itemsTableBody tr").not(".total-row, .total-krw-row, .remarks-row");
// 계 행, 원화환산 행, 비고 행, 참조사항 행, 회사명 행 제외하고 품목 행 개수 계산
var itemRows = $("#itemsTableBody tr").not(".total-row, .total-krw-row, .remarks-row, .notes-row, .footer-row");
var nextNo = itemRows.length + 1;
// 새 행 생성
@@ -817,32 +819,36 @@ function fn_loadTemplateData(templateObjId){
// 결재상태 저장
g_apprStatus = template.appr_status || template.APPR_STATUS || template.apprStatus || "작성중";
// 대문자/소문자 모두 지원
var executor = template.EXECUTOR || template.executor || "";
var recipient = template.RECIPIENT || template.recipient || "";
var estimateNo = template.ESTIMATE_NO || template.estimate_no || template.estimateNo || "";
var contactPerson = template.CONTACT_PERSON || template.contact_person || template.contactPerson || "";
var greetingText = template.GREETING_TEXT || template.greeting_text || template.greetingText || "";
var note1 = template.NOTE1 || template.note1 || "";
var note2 = template.NOTE2 || template.note2 || "";
var note3 = template.NOTE3 || template.note3 || "";
var note4 = template.NOTE4 || template.note4 || "";
var managerName = template.MANAGER_NAME || template.manager_name || template.managerName || "영업부";
var managerContact = template.MANAGER_CONTACT || template.manager_contact || template.managerContact || "";
// 기본 정보 채우기
$("#executor").val(executor);
// 대문자/소문자 모두 지원
var executor = template.EXECUTOR || template.executor || "";
var recipient = template.RECIPIENT || template.recipient || "";
var recipientName = template.RECIPIENT_NAME || template.recipient_name || template.recipientName || "";
var estimateNo = template.ESTIMATE_NO || template.estimate_no || template.estimateNo || "";
var contactPerson = template.CONTACT_PERSON || template.contact_person || template.contactPerson || "";
var greetingText = template.GREETING_TEXT || template.greeting_text || template.greetingText || "";
var note1 = template.NOTE1 || template.note1 || "";
var note2 = template.NOTE2 || template.note2 || "";
var note3 = template.NOTE3 || template.note3 || "";
var note4 = template.NOTE4 || template.note4 || "";
var managerName = template.MANAGER_NAME || template.manager_name || template.managerName || "영업부";
var managerContact = template.MANAGER_CONTACT || template.manager_contact || template.managerContact || "";
// 데이터 로드 중 플래그 설정 (수신인 자동 로드 방지)
window.isLoadingData = true;
// 수신처 설정
// 기본 정보 채우기
$("#executor").val(executor);
// 데이터 로드 중 플래그 설정 (수신인 자동 로드 방지)
window.isLoadingData = true;
// 수신처 설정
if(recipient && recipient !== "") {
// OBJID로 셀렉트박스 선택
$("#recipient").val(recipient).trigger('change');
// 플래그 해제
setTimeout(function() {
window.isLoadingData = false;
}, 100);
}
// 플래그 해제
setTimeout(function() {
window.isLoadingData = false;
}, 100);
$("#estimate_no").val(estimateNo);
$("#contact_person").val(contactPerson);
@@ -856,12 +862,6 @@ function fn_loadTemplateData(templateObjId){
$("#manager_contact").val(managerContact);
}
// 하단 비고 로드
$("#note1").val(note1);
$("#note2").val(note2);
$("#note3").val(note3);
$("#note4").val(note4);
// 테이블 내 비고는 나중에 설정 (textarea 생성 후)
var noteRemarks = template.NOTE_REMARKS || template.note_remarks || template.noteRemarks || "";
@@ -930,6 +930,24 @@ function fn_loadTemplateData(templateObjId){
itemsHtml += '</td>';
itemsHtml += '</tr>';
// 참조사항 행 추가
itemsHtml += '<tr class="notes-row">';
itemsHtml += '<td colspan="8" style="vertical-align: top; padding: 10px; text-align: left; border: 1px solid #000;">';
itemsHtml += '<div style="font-weight: bold; margin-bottom: 10px; text-align: left;">&lt;참조사항&gt;</div>';
itemsHtml += '<div class="editable" style="margin-bottom: 5px;"><input type="text" id="note1" value="1. 견적유효기간: 일" style="width: 100%; border: none; background: transparent; font-size: 10pt;"></div>';
itemsHtml += '<div class="editable" style="margin-bottom: 5px;"><input type="text" id="note2" value="2. 납품기간: 발주 후 1주 이내" style="width: 100%; border: none; background: transparent; font-size: 10pt;"></div>';
itemsHtml += '<div class="editable" style="margin-bottom: 5px;"><input type="text" id="note3" value="3. VAT 별도" style="width: 100%; border: none; background: transparent; font-size: 10pt;"></div>';
itemsHtml += '<div class="editable" style="margin-bottom: 5px;"><input type="text" id="note4" value="4. 결제 조건 : 기존 결제조건에 따름." style="width: 100%; border: none; background: transparent; font-size: 10pt;"></div>';
itemsHtml += '</td>';
itemsHtml += '</tr>';
// 하단 회사명 행 추가
itemsHtml += '<tr class="footer-row">';
itemsHtml += '<td colspan="8" style="text-align: right; padding: 15px; font-size: 10pt; font-weight: bold; border: none;">';
itemsHtml += '㈜알피에스';
itemsHtml += '</td>';
itemsHtml += '</tr>';
// HTML 삽입
$("#itemsTableBody").html(itemsHtml);
@@ -976,17 +994,28 @@ function fn_loadTemplateData(templateObjId){
fn_initItemDescSelect(itemId);
}
// 테이블 내 비고 값 설정 (textarea 생성 직후)
$("#note_remarks").val(noteRemarks);
// 합계 계산
fn_calculateTotal();
// 테이블 내 비고 값 설정 (textarea 생성 직후)
$("#note_remarks").val(noteRemarks);
// 참조사항 값 설정 (input 생성 직후)
$("#note1").val(note1 || "1. 견적유효기간: 일");
$("#note2").val(note2 || "2. 납품기간: 발주 후 1주 이내");
$("#note3").val(note3 || "3. VAT 별도");
$("#note4").val(note4 || "4. 결제 조건 : 기존 결제조건에 따름.");
// 합계 계산
fn_calculateTotal();
// 결재상태에 따라 버튼 제어
fn_controlButtons();
// 데이터 로딩 완료 플래그 설정
window.dataLoaded = true;
console.log("견적서 데이터 로딩 완료");
}
} else {
console.error("데이터 로드 실패:", data);
window.dataLoaded = false;
Swal.fire("데이터를 불러오는데 실패했습니다.");
}
},
@@ -1025,8 +1054,8 @@ function fn_loadCustomerContact(customerObjId) {
function fn_save() {
var items = [];
// 계 행, 원화환산 행, 비고 행 제외하고 품목 행만 저장
$("#itemsTableBody tr").not(".total-row, .total-krw-row, .remarks-row").each(function(idx) {
// 계 행, 원화환산 행, 비고 행, 참조사항 행, 회사명 행 제외하고 품목 행만 저장
$("#itemsTableBody tr").not(".total-row, .total-krw-row, .remarks-row, .notes-row, .footer-row").each(function(idx) {
var row = $(this);
var quantity = row.find(".item-qty").val() || "";
var unitPrice = row.find(".item-price").val() || "";
@@ -1128,6 +1157,7 @@ function fn_save() {
<colgroup>
<col width="80px" />
<col width="*" />
<col width="50px" />
<col width="300px" />
</colgroup>
<tr>
@@ -1135,6 +1165,7 @@ function fn_save() {
<td class="editable">
<input type="text" id="executor" class="date_icon" value="" style="width: 150px;">
</td>
<td rowspan="4" style="border: none"></td>
<td rowspan="4" style="text-align: center; border: none; vertical-align: middle; padding: 0;">
<div style="width: 100%; text-align: center; margin-bottom: 5px;">
<img src="/images/company_stamp.png" alt="회사 도장" style="width: 100%; height: auto;"
@@ -1143,10 +1174,7 @@ function fn_save() {
<div class="company-stamp-text">㈊알피에스<br>RPS CO., LTD<br>대표이사이동준</div>
</div>
</div>
<div style="text-align: left; font-size: 9pt; line-height: 1.8; padding: 0 5px;">
담당자 : <input type="text" id="manager_name" value="" readonly style="width: 120px; border: none; border-bottom: 1px solid #ddd; font-size: 9pt; padding: 2px; background-color: #f5f5f5;"><br>
연락처 : <input type="text" id="manager_contact" value="" readonly style="width: 120px; border: none; border-bottom: 1px solid #ddd; font-size: 9pt; padding: 2px; background-color: #f5f5f5;">
</div>
</td>
</tr>
<tr>
@@ -1173,15 +1201,19 @@ function fn_save() {
</table>
<!-- 인사말 -->
<div style="margin-bottom: 10px; padding: 0px 5px; line-height: 1.6; font-size: 10pt;">
<div style="display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 10px; padding: 0px 5px;">
<!-- 왼쪽: 인사말 -->
<div style="line-height: 1.6; font-size: 10pt;">
견적을 요청해 주셔서 대단히 감사합니다.<br>
하기와 같이 견적서를 제출합니다.
</div>
<!-- 부가세 별도 표시 -->
<div style="text-align: right; margin-top: -30px; margin-bottom: 20px; padding-right: 10px; font-size: 10pt;">
부가세 별도
<!-- 오른쪽: 담당자 정보 및 부가세 별도 -->
<div style="text-align: right; font-size: 9pt; line-height: 1.8;">
담당자 : <input type="text" id="manager_name" value="" readonly style="width: 120px; border: none; border-bottom: 1px solid #ddd; font-size: 9pt; padding: 2px; background-color: #f5f5f5;"><br>
연락처 : <input type="text" id="manager_contact" value="" readonly style="width: 120px; border: none; border-bottom: 1px solid #ddd; font-size: 9pt; padding: 2px; background-color: #f5f5f5;"><br><br>
<span style="font-size: 10pt; margin-top: 5px; display: inline-block;">부가세 별도</span>
</div>
</div>
<!-- 품목 테이블 -->
<table class="items-table">
@@ -1245,31 +1277,274 @@ function fn_save() {
<textarea id="note_remarks" style="width: 100%; height: 70px; border: none; resize: none; font-family: inherit; font-size: 10pt; text-align: left;"></textarea>
</td>
</tr>
<!-- 참조사항 행 -->
<tr class="notes-row">
<td colspan="8" style="vertical-align: top; padding: 10px; text-align: left; border: 1px solid #000;">
<div style="font-weight: bold; margin-bottom: 10px; text-align: left;">&lt;참조사항&gt;</div>
<div class="editable" style="margin-bottom: 5px;"><input type="text" id="note1" value="1. 견적유효기간: 일" style="width: 100%; border: none; background: transparent; font-size: 10pt;"></div>
<div class="editable" style="margin-bottom: 5px;"><input type="text" id="note2" value="2. 납품기간: 발주 후 1주 이내" style="width: 100%; border: none; background: transparent; font-size: 10pt;"></div>
<div class="editable" style="margin-bottom: 5px;"><input type="text" id="note3" value="3. VAT 별도" style="width: 100%; border: none; background: transparent; font-size: 10pt;"></div>
<div class="editable" style="margin-bottom: 5px;"><input type="text" id="note4" value="4. 결제 조건 : 기존 결제조건에 따름." style="width: 100%; border: none; background: transparent; font-size: 10pt;"></div>
</td>
</tr>
<!-- 하단 회사명 행 -->
<tr class="footer-row">
<td colspan="8" style="text-align: right; padding: 15px; font-size: 10pt; font-weight: bold; border: none;">
㈜알피에스
</td>
</tr>
</tbody>
</table>
<!-- 참조사항 섹션 -->
<div class="notes-section">
<div class="notes-title">&lt;참조사항&gt;</div>
<div class="editable"><input type="text" id="note1" value="1. 견적유효기간: 일"></div>
<div class="editable"><input type="text" id="note2" value="2. 납품기간: 발주 후 1주 이내"></div>
<div class="editable"><input type="text" id="note3" value="3. VAT 별도"></div>
<div class="editable"><input type="text" id="note4" value="4. 결제 조건 : 기존 결제조건에 따름."></div>
</div>
<!-- 하단 회사명 -->
<div class="footer-company">
㈜알피에스
</div>
</div>
<!-- 버튼 영역 -->
<div class="btn-area no-print">
<button type="button" id="btnAddRow" class="estimate-btn">행 추가</button>
<button type="button" id="btnPrint" class="estimate-btn">인쇄</button>
<button type="button" id="btnDownloadPdf" class="estimate-btn">PDF 다운로드</button>
<button type="button" id="btnSave" class="estimate-btn">저장</button>
<button type="button" id="btnClose" class="estimate-btn">닫기</button>
</div>
<!-- html2canvas 및 jsPDF 라이브러리 -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/1.5.3/jspdf.min.js"></script>
<script>
// PDF 다운로드 버튼 클릭 이벤트
$("#btnDownloadPdf").click(function(){
fn_generatePdf();
});
// PDF 생성 함수
function fn_generatePdf() {
// 라이브러리 로드 확인
if(typeof html2canvas === 'undefined') {
Swal.fire({
title: '오류',
text: 'html2canvas 라이브러리가 로드되지 않았습니다.',
icon: 'error'
});
return;
}
if(typeof jsPDF === 'undefined') {
Swal.fire({
title: '오류',
text: 'jsPDF 라이브러리가 로드되지 않았습니다.',
icon: 'error'
});
return;
}
Swal.fire({
title: 'PDF 생성 중...',
text: '잠시만 기다려주세요.',
allowOutsideClick: false,
onOpen: () => {
Swal.showLoading();
}
});
// 버튼 영역 임시 숨김
$('.btn-area').hide();
// PDF 생성을 위해 스타일 조정
var container = $('.estimate-container');
var originalBg = container.css('background');
var originalShadow = container.css('box-shadow');
var originalPadding = container.css('padding');
// 깔끔한 PDF를 위해 배경, 그림자, 패딩 제거
container.css({
'background': 'white',
'box-shadow': 'none',
'padding': '10mm'
});
// body 배경색도 흰색으로
var originalBodyBg = $('body').css('background-color');
$('body').css('background-color', 'white');
// 견적서 컨테이너 캡처
html2canvas(document.querySelector('.estimate-container'), {
scale: 2, // 적절한 해상도 (파일 크기 최적화)
useCORS: true,
logging: false,
backgroundColor: '#ffffff'
}).then(function(canvas) {
// 스타일 복원
container.css({
'background': originalBg,
'box-shadow': originalShadow,
'padding': originalPadding
});
$('body').css('background-color', originalBodyBg);
// 버튼 영역 다시 표시
$('.btn-area').show();
try {
// Canvas를 JPEG 이미지로 변환 (PNG보다 파일 크기 작음)
var imgData = canvas.toDataURL('image/jpeg', 0.85); // 85% 품질
// PDF 생성 (A4 크기)
var pdf = new jsPDF('p', 'mm', 'a4');
var imgWidth = 210; // A4 width in mm
var pageHeight = 297; // A4 height in mm
var imgHeight = canvas.height * imgWidth / canvas.width;
var heightLeft = imgHeight;
var position = 0;
// 첫 페이지 추가 (JPEG 압축)
pdf.addImage(imgData, 'JPEG', 0, position, imgWidth, imgHeight, undefined, 'FAST');
heightLeft -= pageHeight;
// 페이지가 넘어가면 추가 페이지 생성
while (heightLeft >= 0) {
position = heightLeft - imgHeight;
pdf.addPage();
pdf.addImage(imgData, 'JPEG', 0, position, imgWidth, imgHeight, undefined, 'FAST');
heightLeft -= pageHeight;
}
// 파일명 생성
var estimateNo = $("#estimate_no").val() || "견적서";
var fileName = estimateNo + '.pdf';
// PDF 다운로드
pdf.save(fileName);
Swal.close();
Swal.fire({
title: 'PDF 생성 완료',
text: 'PDF 파일이 다운로드되었습니다.',
icon: 'success',
timer: 2000
});
} catch(pdfError) {
Swal.close();
console.error('PDF 생성 오류:', pdfError);
Swal.fire({
title: '오류',
text: 'PDF 생성 중 오류가 발생했습니다: ' + pdfError.message,
icon: 'error'
});
}
}).catch(function(error) {
$('.btn-area').show();
Swal.close();
console.error('Canvas 캡처 오류:', error);
Swal.fire({
title: '오류',
text: '페이지 캡처 중 오류가 발생했습니다: ' + error.message,
icon: 'error'
});
});
}
// PDF를 Base64로 생성하여 서버로 전송하는 함수
function fn_generateAndUploadPdf(callback) {
console.log('fn_generateAndUploadPdf 호출됨');
// 라이브러리 로드 확인
if(typeof html2canvas === 'undefined' || typeof jsPDF === 'undefined') {
console.error('필요한 라이브러리가 로드되지 않았습니다.');
if(callback && typeof callback === 'function') {
callback(null);
}
return;
}
// 버튼 영역 임시 숨김
$('.btn-area').hide();
// PDF 생성을 위해 스타일 조정
var container = $('.estimate-container');
var originalBg = container.css('background');
var originalShadow = container.css('box-shadow');
var originalPadding = container.css('padding');
// 깔끔한 PDF를 위해 배경, 그림자, 패딩 제거
container.css({
'background': 'white',
'box-shadow': 'none',
'padding': '10mm'
});
// body 배경색도 흰색으로
var originalBodyBg = $('body').css('background-color');
$('body').css('background-color', 'white');
// 견적서 컨테이너 캡처
html2canvas(document.querySelector('.estimate-container'), {
scale: 2, // 적절한 해상도 (파일 크기 최적화)
useCORS: true,
logging: false,
backgroundColor: '#ffffff'
}).then(function(canvas) {
console.log('Canvas 캡처 완료');
// 스타일 복원
container.css({
'background': originalBg,
'box-shadow': originalShadow,
'padding': originalPadding
});
$('body').css('background-color', originalBodyBg);
// 버튼 영역 다시 표시
$('.btn-area').show();
try {
// Canvas를 JPEG 이미지로 변환 (PNG보다 파일 크기 작음)
var imgData = canvas.toDataURL('image/jpeg', 0.85); // 85% 품질
console.log('이미지 변환 완료');
// PDF 생성
var pdf = new jsPDF('p', 'mm', 'a4');
var imgWidth = 210;
var pageHeight = 297;
var imgHeight = canvas.height * imgWidth / canvas.width;
var heightLeft = imgHeight;
var position = 0;
// JPEG 이미지 추가 (압축됨)
pdf.addImage(imgData, 'JPEG', 0, position, imgWidth, imgHeight, undefined, 'FAST');
heightLeft -= pageHeight;
while (heightLeft >= 0) {
position = heightLeft - imgHeight;
pdf.addPage();
pdf.addImage(imgData, 'JPEG', 0, position, imgWidth, imgHeight, undefined, 'FAST');
heightLeft -= pageHeight;
}
console.log('PDF 생성 완료');
// PDF를 Base64로 변환
var pdfBase64 = pdf.output('dataurlstring').split(',')[1];
console.log('PDF Base64 생성 완료, 길이:', pdfBase64.length);
// 콜백 함수 호출 (메일 발송 등에 사용)
if(callback && typeof callback === 'function') {
callback(pdfBase64);
}
} catch(pdfError) {
console.error('PDF 생성 중 오류:', pdfError);
if(callback && typeof callback === 'function') {
callback(null);
}
}
}).catch(function(error) {
$('.btn-area').show();
console.error('Canvas 캡처 오류:', error);
if(callback && typeof callback === 'function') {
callback(null);
}
});
}
</script>
</body>
</html>

View File

@@ -26,14 +26,14 @@ userMenuList = (ArrayList)request.getAttribute("userMenuList");
}
*/
.menu_off{
color:#fff !important; padding: 2px 3px; font-size:9px; background-color:#89b4fa; border-radius:2px;
display: inline-block; min-width: 40px; text-align: center; margin: 0px;
color:#fff !important; padding: 4px 8px; font-size:11px; background-color:#89b4fa; border-radius:3px;
display: inline-block; min-width: 55px; text-align: center; margin: 0px;
transition: all 0.3s ease; text-decoration: none !important; border: none;
cursor: pointer;
}
.menu_on{
color:#fff !important; padding: 2px 3px; font-size:9px; background-color:#5e9cff; border-radius:2px;
display: inline-block; min-width: 40px; text-align: center; margin: 0px;
color:#fff !important; padding: 4px 8px; font-size:11px; background-color:#5e9cff; border-radius:3px;
display: inline-block; min-width: 55px; text-align: center; margin: 0px;
transition: all 0.3s ease; text-decoration: none !important; border: none;
cursor: pointer;
}
@@ -392,7 +392,7 @@ function fn_setApprovalCnt(){
if("2".equals(lev)){
%>
<a href="#" class="menu menu_off" menuObjId="<%=menuObjid%>" style="background-color: #1e40af; color: #fff; padding: 3px 6px; font-size: 10px; border-radius: 3px; text-decoration: none; display: inline-block; min-width: 50px; text-align: center; margin-right: 4px;"><%=menuKorName%></a>
<a href="#" class="menu menu_off" menuObjId="<%=menuObjid%>" style="background-color: #1e40af; color: #fff; padding: 4px 8px; font-size: 11px; border-radius: 3px; text-decoration: none; display: inline-block; min-width: 55px; text-align: center; margin-right: 5px;"><%=menuKorName%></a>
<%
}
}

View File

@@ -2,6 +2,23 @@
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<%@ page import="com.pms.common.utils.*"%>
<%@ page import="java.util.*"%>
<%
// URL 파라미터 가져오기 (request.getParameter는 자동으로 URL 디코딩됨)
String bomPartNo = request.getParameter("BOM_PART_NO");
String bomPartName = request.getParameter("BOM_PART_NAME");
String bomVersion = request.getParameter("BOM_VERSION");
// 디버깅: 콘솔에 출력
System.out.println("=== BOM 파라미터 디버깅 ===");
System.out.println("bomPartNo: " + bomPartNo);
System.out.println("bomPartName: " + bomPartName);
System.out.println("bomVersion: " + bomVersion);
// null 체크
if(bomPartNo == null) bomPartNo = "";
if(bomPartName == null) bomPartName = "";
if(bomVersion == null) bomVersion = "";
%>
<%@include file="/init_jqGrid.jsp"%>
<!DOCTYPE html>
<html>
@@ -21,7 +38,12 @@
<script type="text/javascript">
var grid;
$(document).ready(function(){
$('.select2').select2();
// 품번/품명 Select2 AJAX 초기화 (common.js의 새 함수 사용)
// initPartSelect2Ajax("#bom_part_no", "#bom_part_name", "#bom_part_objid", {
// debug: false // 디버깅 모드 비활성화
// });
$(".dataTr").each(function(i){
var lev = $(this).attr("data-LEVEL");
if(lev == 1){
@@ -29,6 +51,8 @@ $(document).ready(function(){
}
});
$("#product_cd").val("${param.PRODUCT_CD}").select2();
$(".btnToggle").click(function(){
var choosedIsMinus = false;
var choosedSrc = $(this).attr("src");
@@ -161,16 +185,59 @@ $(document).ready(function(){
//fnc_productUPGNEWList("","${product_mgmt_spec}","param_upg_no", "${upg_no}");
//fnc_datepick();
fnc_setFileDropZone("excelImportDropZone", "${objid}", "PART_EXCEL_IMPORT", "Part Excel Import Template", "setExcelFileArea",true,"fileDelete","/part/excelImportFileProc.do");
// CSV 파일만 업로드 가능하도록 설정 (2025-10-29)
fnc_setFileDropZone("excelImportDropZone", "${objid}", "PART_EXCEL_IMPORT", "Part Excel Import Template", "setExcelFileArea",true,"fileDelete","/part/excelImportFileProc.do", "csv");
fnc_setFileDropZone("partAttachFileDropZone", "${objid}", "PART_IMPORT_ATTACH", "Import Part Attach File", "setPartFileArea",false,null,"/part/partImportFileProc.do");
$("#templateDownload").click(function(){
location.href="/template/BOM_REPORT_EXCEL_IMPORT_TEMPLATE.xlsx";
});
var unit_cd = $.parseJSON($("#unit_cd").val()); //jqGrid 구분
var part_type = $.parseJSON($("#part_type").val()); //jqGrid 구분
var sup_code = $.parseJSON($("#sup_code").val()); //jqGrid 구분
// BOM 복사 버튼 클릭
$("#btnCopyBom").click(function(){
var bomObjid = $("#copy_bom_select").val();
//alert(bomObjid);
if(!bomObjid || bomObjid == "") {
Swal.fire("복사할 BOM을 선택하세요.");
return;
}
// 기존 그리드 데이터 초기화
grid.jqGrid('clearGridData');
// BOM 데이터 조회 및 로드
$.ajax({
url: "/partMng/getBomDataForCopy.do",
type: "POST",
data: {"BOM_REPORT_OBJID": bomObjid},
dataType: "json",
success: function(data){
if(data && data.length > 0){
// jqGrid에 데이터 추가
for(var i=0; i<data.length; i++){
grid.jqGrid('addRowData', i+1, data[i]);
}
gridFn.opennEdit(); // 수정 가능하도록
Swal.fire("BOM 데이터를 불러왔습니다.");
} else {
Swal.fire("선택한 BOM에 데이터가 없습니다.");
}
},
error: function(jqxhr, status, error){
Swal.fire('BOM 데이터 조회 중 오류가 발생했습니다.');
console.error(error);
}
});
});
// JSON 파싱 시 에러 처리
var unit_cd_val = $("#unit_cd").val();
var part_type_val = $("#part_type").val();
var sup_code_val = $("#sup_code").val();
var unit_cd = unit_cd_val && unit_cd_val.trim() !== '' ? $.parseJSON(unit_cd_val) : {};
var part_type = part_type_val && part_type_val.trim() !== '' ? $.parseJSON(part_type_val) : {};
var sup_code = sup_code_val && sup_code_val.trim() !== '' ? $.parseJSON(sup_code_val) : {};
//Excel File Upload된 파일 목록 부분을 초기화 한다.
$("#excelImportList").hide();
@@ -181,7 +248,7 @@ $(document).ready(function(){
grid = $("#expenseDetailGrid").jqGrid({
url: ""
,datatype: "local"
,colNames: ["상태","모품번","품번","품명","수량","재질","사양(규격)","처리","MAKER","부품 유형","REMARK"]
,colNames: ["상태","모품번","품번","품명","수량","항목수량","재료","열처리경도","처리방법","표면처리","공급업체","범주이름"]
,colModel: [
{name:"NOTE",index:"NOTE", width: 160, align:"center", hidden: false, sortable:false, editable:true
,editoptions:{
@@ -190,62 +257,90 @@ $(document).ready(function(){
}
}
}
,{name:"PARENT_PART_NO",index:"PARENT_PART_NO", width: 160, align:"center", hidden: false, sortable:false, editable:true
,{name:"PARENT_PART_NO",index:"PARENT_PART_NO", width: 200, align:"center", hidden: false, sortable:false, editable:true
,editoptions:{
dataInit : function(e){
e.style.fontSize = 13;
}
}
}
,{name:"PART_NO",index:"PART_NO", width: 160, align:"center", hidden: false, sortable:false, editable:true
,editoptions:{
dataInit : function(e){
e.style.fontSize = 13;
}
}
}
,{name:"PART_NAME",index:"PART_NAME", width: 270, align:"center", hidden: false, sortable:false, editable:true
,{name:"PART_NO",index:"PART_NO", width: 200, align:"center", hidden: false, sortable:false, editable:true
,editoptions:{
dataInit : function(e){
e.style.fontSize = 13;
}
}
}
,{name:"QTY",index:"QTY", width: 50, align:"center", hidden: false, sortable:false, editable:true
,{name:"PART_NAME",index:"PART_NAME", width: 250, align:"center", hidden: false, sortable:false, editable:true
,editoptions:{
dataInit : function(e){
e.style.fontSize = 13;
}
}
}
,{name:"MATERIAL",index:"MATERIAL", width: 150, align:"center", hidden: false, sortable:false, editable:true
,editoptions:{
dataInit : function(e){
e.style.fontSize = 13;
}
}
}
,{name:"SPEC",index:"SPEC", width: 150, align:"center", hidden: false, sortable:false, editable:true
,{name:"QTY",index:"QTY", width: 60, align:"center", hidden: false, sortable:false, editable:true
,editoptions:{
dataInit : function(e){
e.style.fontSize = 13;
}
}
}
,{name:"POST_PROCESSING",index:"POST_PROCESSING", width: 100, align:"center", hidden: false, sortable:false, editable:true
,{name:"ITEM_QTY",index:"ITEM_QTY", width: 80, align:"center", hidden: false, sortable:false, editable:true
,editoptions:{
dataInit : function(e){
e.style.fontSize = 13;
}
}
}
,{name:"MAKER",index:"MAKER", width: 150, align:"center", hidden: false, sortable:false, editable:true
,{name:"MATERIAL",index:"MATERIAL", width: 120, align:"center", hidden: false, sortable:false, editable:true
,editoptions:{
dataInit : function(e){
e.style.fontSize = 13;
}
}
}
,{name:"HEAT_TREATMENT_HARDNESS",index:"HEAT_TREATMENT_HARDNESS", width: 100, align:"center", hidden: false, sortable:false, editable:true
,editoptions:{
dataInit : function(e){
e.style.fontSize = 13;
}
}
}
,{name:"HEAT_TREATMENT_METHOD",index:"HEAT_TREATMENT_METHOD", width: 100, align:"center", hidden: false, sortable:false, editable:true
,editoptions:{
dataInit : function(e){
e.style.fontSize = 13;
}
}
}
,{name:"SURFACE_TREATMENT",index:"SURFACE_TREATMENT", width: 100, align:"center", hidden: false, sortable:false, editable:true
,editoptions:{
dataInit : function(e){
e.style.fontSize = 13;
}
}
}
// 공급업체 - MAKER 컬럼으로 변경, 일반 텍스트 입력 (2025-10-29)
,{name:"MAKER",index:"MAKER", width: 120, align:"center", hidden: false, sortable:false, editable:true
,editoptions:{
dataInit : function(e){
e.style.fontSize = 13;
}
}
}
/* 이전 SUPPLY_CODE 셀렉트박스 방식 (주석처리)
,{name:"SUPPLY_CODE",index:"SUPPLY_CODE", width: 120, align:"center", hidden: false, sortable:false, editable:true
,edittype :"select"
,formatter :"select"
,editoptions:{
value: sup_code
,dataInit : function(e){
e.style.width = "92%";
e.style.fontSize = 13;
}
}
}
*/
,{name:"PART_TYPE" ,index:"PART_TYPE" , width:100, align:"center", hidden:false, sortable:false, editable: true
,edittype :"select"
,formatter :"select"
@@ -257,14 +352,7 @@ $(document).ready(function(){
}
}
}
,{name:"REMARK",index:"REMARK", width: 170, align:"center", hidden: false, sortable:false, editable:true
,editoptions:{
dataInit : function(e){
e.style.fontSize = 13;
}
}
}
]
]
//,cellEdit : true
//,cellsubmit : "clientArray"
,rownumbers : true
@@ -342,12 +430,23 @@ var gridFn = {
}
,search : function() {
grid.setGridParam({
url: "/partMng/parsingExcelFile.do"
url: "/partMng/parsingExcelFile.do"
,datatype : "json"
,postData:{"targetObjId":"${objid}","docType":"PART_EXCEL_IMPORT","OBJID":"${CONTRACT_OBJID}"}
,loadComplete : function(data) {
gridFn.footerSummary();
,loadComplete : function(data) {
// CSV 파일에서만 LEVEL 값을 PARENT_PART_NO 컬럼에 표시
if(data && data.rows) {
for(var i = 0; i < data.rows.length; i++) {
var row = data.rows[i];
// CSV 파일이고 LEVEL 값이 있는 경우
if(row.IS_CSV === 'Y' && row.LEVEL && row.LEVEL !== '') {
// LEVEL 값을 PARENT_PART_NO 컬럼에 표시 (화면용)
grid.jqGrid('setCell', row.id, 'PARENT_PART_NO', row.LEVEL);
}
}
}
gridFn.footerSummary();
}
,gridComplete : function() {
//gridFn.opennEdit(); //수정가능
var valid = true;
@@ -381,104 +480,147 @@ var gridFn = {
}
}
// 품번 중복 검증 함수 (PART_BOM_REPORT 테이블에서 헤더 품번 중복 체크)
function fn_checkDuplicatePartNo(){
var isDuplicate = false;
// 헤더의 품번 가져오기
var bomPartNo = $('#bom_part_no').val();
if(!bomPartNo || bomPartNo.trim() == ""){
return false; // 품번이 없으면 중복 아님
}
// 동기 AJAX로 DB에서 중복 체크
$.ajax({
url: "/partMng/checkDuplicatePartNo.do",
type: "POST",
data: {
partNo: bomPartNo.trim(),
bomReportObjid: $('#BOM_REPORT_OBJID').val() // 현재 수정 중인 BOM은 제외
},
dataType: "json",
async: false, // 동기 처리
success: function(data){
if(data && data.isDuplicate){
isDuplicate = true;
}
},
error: function(jqxhr, status, error){
console.error("품번 중복 체크 오류:", error);
}
});
return isDuplicate;
}
function fn_save(){
var ids = grid.jqGrid("getDataIDs");
if(ids!=""){
if(fnc_valitate("form1")){
var valid = true;
var existPart = true;
var part = "";
if (fnc_isEmpty(${bomInfo.OBJID})) {
var partNo1 = "";
gridFn.closeEdit();
$.each(grid.getRowData(), function(i, d) {
if (i == 0) {
partNo1 = d["PART_NO"];
}
var partType = d["PART_TYPE"];
var partNo = d["PART_NO"];
if (partType == '0001788') {
if (!fn_existPartNo(partNo)) {
existPart = false;
part = partNo;
return false; // Exit the loop
}
}
if(!fnc_isEmpty(d["NOTE"])){
valid = false;
}
});
gridFn.opennEdit();
var project_no = $("#project_name").find("option:selected").text();
if (partNo1.indexOf(project_no) < 0) {
valid = false;
}
}
if (!existPart) {
Swal.fire(part+'품번에 해당하는 구매품표준이 없습니다. 확인해 주세요.');
return;
}
if (!valid) {
Swal.fire('1레벨 품번에 프로젝트번호가 없습니다. 확인해 주세요.');
return;
}
var existsDup = false;
var ARR_APPLICATION_PROJECT_NO = new Array();
$(".APPLICATION_PROJECT_NO").each(function(){
for(var i=0; i<ARR_APPLICATION_PROJECT_NO.length; i++){
if(this.value != '' && ARR_APPLICATION_PROJECT_NO[i] == this.value){
existsDup = true;
break;
}
}
ARR_APPLICATION_PROJECT_NO.push($(this).val());
});
if(existsDup){
Swal.fire('동시적용 프로젝트번호에 중복건이 존재합니다.');
return;
}
if(confirm("저장 하시겠습니까?")){
// 품번, 품명만 입력하고 그리드 데이터가 없어도 저장 가능하도록 수정
if(fnc_valitate("form1")){
var valid = true;
var existPart = true;
var part = "";
// 그리드에 데이터가 있는 경우에만 검증
if (ids && ids.length > 0) {
if (fnc_isEmpty(${bomInfo.OBJID})) {
var partNo1 = "";
gridFn.closeEdit();
$.ajax({
url:"/partMng/partBomApplySave.do"
,type:"POST"
,data: $("#form1").serialize() + "&jqGrid="+ encodeURIComponent(JSON.stringify(grid.getRowData()))
,dataType:"json"
,success:function(data){
/*
if(data =="SUCCESS"){
alert("저장되었습니다.");
top.opener.fn_search();
self.close();
};
*/
console.log(data);
if(data && data.RESULT == 'S'){
alert("저장되었습니다.");
if(typeof opener.fn_search =="function"){ opener.fn_search() };
self.close();
}else{
alert(data.MSG);
$.each(grid.getRowData(), function(i, d) {
if (i == 0) {
partNo1 = d["PART_NO"];
}
var partType = d["PART_TYPE"];
var partNo = d["PART_NO"];
if (partType == '0001788') {
if (!fn_existPartNo(partNo)) {
existPart = false;
part = partNo;
return false; // Exit the loop
}
}
,error: function(jqxhr, status, error){
alert('에러가 발생하였습니다. 시스템 관리자에게 문의하여 주세요.');
if(!fnc_isEmpty(d["NOTE"])){
valid = false;
}
});
gridFn.opennEdit();
var project_no = $("#project_name").find("option:selected").text();
if (partNo1 && partNo1.indexOf(project_no) < 0) {
valid = false;
}
}
if (!existPart) {
Swal.fire(part+'품번에 해당하는 구매품표준이 없습니다. 확인해 주세요.');
return;
}
if (!valid) {
Swal.fire('1레벨 품번에 프로젝트번호가 없습니다. 확인해 주세요.');
return;
}
}
}else{
Swal.fire('저장할 데이터가 없습니다.');
// 품번 중복 검증 (PART_BOM_REPORT 테이블 - 헤더 품번)
if(fn_checkDuplicatePartNo()){
Swal.fire('입력한 품번이 이미 존재합니다. 다른 품번을 입력해주세요.');
$('#bom_part_no').focus();
return;
}
var existsDup = false;
var ARR_APPLICATION_PROJECT_NO = new Array();
$(".APPLICATION_PROJECT_NO").each(function(){
for(var i=0; i<ARR_APPLICATION_PROJECT_NO.length; i++){
if(this.value != '' && ARR_APPLICATION_PROJECT_NO[i] == this.value){
existsDup = true;
break;
}
}
ARR_APPLICATION_PROJECT_NO.push($(this).val());
});
if(existsDup){
Swal.fire('동시적용 프로젝트번호에 중복건이 존재합니다.');
return;
}
// 그리드 데이터가 없어도 저장 가능 (빈 BOM 생성)
var confirmMsg = ids && ids.length > 0 ? "저장 하시겠습니까?" : "품번, 품명으로 빈 E-BOM을 생성하시겠습니까?";
if(confirm(confirmMsg)){
if(ids && ids.length > 0) {
gridFn.closeEdit();
}
// 그리드 데이터가 없으면 빈 배열 전송
var gridData = ids && ids.length > 0 ? grid.getRowData() : [];
$.ajax({
url:"/partMng/partBomApplySave.do"
,type:"POST"
,data: $("#form1").serialize() + "&jqGrid="+ encodeURIComponent(JSON.stringify(gridData))
,dataType:"json"
,success:function(data){
console.log(data);
if(data && data.RESULT == 'S'){
alert("저장되었습니다.");
if(typeof opener.fn_search =="function"){ opener.fn_search() };
self.close();
}else{
alert(data.MSG);
}
}
,error: function(jqxhr, status, error){
alert('에러가 발생하였습니다. 시스템 관리자에게 문의하여 주세요.');
}
});
}
}
}
@@ -801,41 +943,55 @@ function fn_save(){
<section>
<div class="plm_menu_name">
<h2><span>PART 및 구조등록 Excel upload</span></h2>
<h2><span>PART 및 구조등록 CSV upload</span></h2>
</div>
</section>
<div style="font-size:10px;float:left;margin:13px 13px 5px 13px">
<table class="" style="border:0px solid #dd2a00;">
<colgroup>
<col width="40px">
<col width="180px">
<col width="100px">
<col width="220px">
<col width="100px">
<col width="150px">
<col width="50px">
<col width="150px">
<col width="40px">
<col width="150px">
<col width="200px">
</colgroup>
<tr>
<td style="font-size:12px;width: 70px;"><label for="street_number">제품구분</label></td>
<td>
<select name="product_cd" id="product_cd" disabled="disabled" style="width: 100px;">
${code_map.product_cd}
</select>
</td>
<colgroup>
<col width="100px">
<col width="180px">
<col width="70px">
<col width="180px">
<col width="70px">
<col width="180px">
<col width="70px">
<col width="150px">
</colgroup>
<tr>
<td style="font-size:12px;"><label for="product_cd">제품구분</label></td>
<td>
<select name="product_cd" id="product_cd" required reqTitle="제품구분" style="width: 170px;" class="select2">
<option value="">선택</option>
${code_map.product_cd}
</select>
</td>
<td style="font-size:12px;" class="align_c"><label for="bom_part_no">품번</label></td>
<td>
<input type="text" name="bom_part_no" id="bom_part_no" required reqTitle="품번" value="<%= bomPartNo %>" style="width: 170px;"/>
</td>
<td style="font-size:12px;" class="align_c"><label for="bom_part_name">품명</label></td>
<td>
<input type="text" name="bom_part_name" id="bom_part_name" required reqTitle="품명" value="<%= bomPartName %>" style="width: 170px;"/>
</td>
<td style="font-size:12px;" class="align_c"><label for="version">Version</label></td>
<td>
<input type="text" name="version" id="version" reqTitle="Version" value="<%= bomVersion %>" style="width: 140px;"/>
</td>
</tr>
<tr>
<td style="font-size:12px;"><label for="copy_bom_select">E-BOM 복사 대상</label></td>
<td colspan="5">
<select name="copy_bom_select" id="copy_bom_select" style="width: 600px;" class="select2">
<option value="">선택</option>
${code_map.bom_list}
</select>
</td>
<td colspan="2" style="text-align:center;">
<input type="button" class="plm_btns" value="복사" id="btnCopyBom" style="width: 80px;">
</td>
</tr>
<!-- <td style="font-size:12px;"><label for="street_number">고객사</label></td>
<td>
@@ -942,10 +1098,10 @@ function fn_save(){
<div style="width:100%; display: inline-block; float:left;">
<div style=" margin: 0 8px;">
<div id="partExcelPopupFormWrap">
<div class="form_popup_title" style="position:relative;">&nbsp;&nbsp;&nbsp;Excel upload<img src="/images/btnExcel.png" style="position:absolute; top:9px; right:135px;"/><span style="position:absolute; top:0px; right:10px; cursor:pointer;" id="templateDownload">Template Download</span></div>
<div class="form_popup_title" style="position:relative;">&nbsp;&nbsp;&nbsp;CSV upload<!--<img src="/images/btnExcel.png" style="position:absolute; top:9px; right:135px;"/><span style="position:absolute; top:0px; right:10px; cursor:pointer;" id="templateDownload">Template Download</span>--></div>
<div id="excelUploadPopupForm">
<div class="fileDnDWrap">
<div id="excelImportDropZone" class="dropzone" style="height:50px;">Drag & Drop 엑셀 템플릿</div>
<div id="excelImportDropZone" class="dropzone" style="height:50px;">Drag & Drop CSV 템플릿</div>
<div id="excelImportList">
<table id="excelImportTable" class="excelUploadPopupForm">
<thead>

View File

@@ -103,6 +103,15 @@ $(document).ready(function(){
}
}
}
// 공급업체 - MAKER 컬럼으로 변경, 일반 텍스트 입력 (2025-10-29)
,{name:"MAKER" ,index:"MAKER" , width:100, align:"center", hidden:false, sortable:false, editable: true
,editoptions:{
dataInit : function(e){
e.style.fontSize = 13;
}
}
}
/* 이전 SUP_CODE 셀렉트박스 방식 (주석처리)
,{name:"SUP_CODE" ,index:"SUP_CODE" , width:100, align:"center", hidden:false, sortable:false, editable: true
,edittype :"select"
,formatter :"select"
@@ -113,7 +122,8 @@ $(document).ready(function(){
e.style.fontSize = 13;
}
}
}
}
*/
// ,{name:"SPEC",index:"SPEC", width: 150, align:"center", hidden: false, sortable:false, editable:true
// ,editoptions:{
// dataInit : function(e){

View File

@@ -34,7 +34,15 @@ $(document).ready(function(){
});
$("#btnEdit").click(function(){
fn_edit();
fn_enableEdit();
});
$("#btnSave").click(function(){
fn_save();
});
$("#btnCancel").click(function(){
location.reload();
});
//설계변경
@@ -71,31 +79,8 @@ $(document).ready(function(){
$("input:radio[name='CHANGE_OPTION']").prop('disabled', true);
$("input:radio[name='MANAGEMENT_FLAG']").prop('disabled', true);
$("select").attr("disabled",true);
$('input').prop('readonly', true);
$('input').prop('disabled', true);
$('#btnEdit').prop('readonly', false);
$('#btnEdit').prop('disabled', false);
$('#btnChangeDesign').prop('readonly', false);
$('#btnChangeDesign').prop('disabled', false);
$('#btnClose').prop('readonly', false);
$('#btnClose').prop('disabled', false);
$('#OBJID').prop('readonly', false);
$('#STATUS').prop('readonly', false);
$('#IS_LAST').prop('readonly', false);
$('#ACTION_TYPE').prop('readonly', false);
$('#REVISION').prop('readonly', false);
$('#OBJID').prop('disabled', false);
$('#STATUS').prop('disabled', false);
$('#IS_LAST').prop('disabled', false);
$('#ACTION_TYPE').prop('disabled', false);
$('#REVISION').prop('disabled', false);
// 초기 상태: 읽기 전용 모드 설정
fn_setReadOnly();
window.resizeTo(800,690);
@@ -303,6 +288,94 @@ function openImagePopUp(url){
window.open(url,"problemImgPopUp","width="+img_width+",height="+height+", menubars=no, scrollbars=yes'");
}
// 읽기 전용 모드 설정
function fn_setReadOnly(){
$("select").attr("disabled",true);
// 버튼이 아닌 input만 비활성화 (중요: type별로 선택)
$('input[type="text"]').prop('readonly', true).prop('disabled', true);
$('input[type="hidden"]').prop('readonly', false).prop('disabled', false);
$('input[type="radio"]').prop('disabled', true);
$('input[type="checkbox"]').prop('disabled', true);
// 모든 버튼은 명시적으로 활성화
$('input[type="button"]').prop('disabled', false);
// 저장/취소 버튼 숨김, 수정 버튼 표시
$('#btnSave').hide();
$('#btnCancel').hide();
$('#btnEdit').show();
}
// 수정 모드 활성화
function fn_enableEdit(){
// 수정 가능한 필드만 활성화
$('#PART_NAME').prop('readonly', false).prop('disabled', false);
$('#MATERIAL').prop('readonly', false).prop('disabled', false);
$('#HEAT_TREATMENT_HARDNESS').prop('readonly', false).prop('disabled', false);
$('#HEAT_TREATMENT_METHOD').prop('readonly', false).prop('disabled', false);
$('#SURFACE_TREATMENT').prop('readonly', false).prop('disabled', false);
$('#MAKER').prop('readonly', false).prop('disabled', false);
$('#PART_TYPE').prop('disabled', false);
$('#REMARK').prop('readonly', false).prop('disabled', false);
// 버튼 표시 변경
$('#btnEdit').hide();
$('#btnSave').show();
$('#btnCancel').show();
}
// 저장 기능
function fn_save(){
if(!confirm("저장하시겠습니까?")){
return;
}
// 유효성 검사
var partName = $('#PART_NAME').val();
if(!partName || partName.trim() == ""){
alert("품명을 입력해주세요.");
$('#PART_NAME').focus();
return;
}
// 저장할 데이터 준비
var saveData = {
OBJID: $('#OBJID').val(),
PART_NAME: $('#PART_NAME').val(),
MATERIAL: $('#MATERIAL').val(),
HEAT_TREATMENT_HARDNESS: $('#HEAT_TREATMENT_HARDNESS').val(),
HEAT_TREATMENT_METHOD: $('#HEAT_TREATMENT_METHOD').val(),
SURFACE_TREATMENT: $('#SURFACE_TREATMENT').val(),
MAKER: $('#MAKER').val(),
PART_TYPE: $('#PART_TYPE').val(),
REMARK: $('#REMARK').val()
};
// 저장 요청
$.ajax({
url: "/partMng/updatePartDetail.do",
type: "POST",
data: saveData,
dataType: "json",
success: function(data){
if(data.result == "success"){
alert("저장되었습니다.");
// 부모 창 새로고침
if(opener && opener.fn_search){
opener.fn_search();
}
location.reload();
}else{
alert("저장에 실패했습니다: " + (data.message || ""));
}
},
error: function(jqxhr, status, error){
alert("저장 중 오류가 발생했습니다: " + error);
}
});
}
function fn_edit(){
if($("#ACTION_TYPE").val() == "changeDesign"){
@@ -410,13 +483,16 @@ function fn_edit(){
<label for="">공급업체</label>
</td>
<td class="input_sub_title" colspan="2">
<input type="text" name="MAKER" id="MAKER" value="${resultMap.MAKER}">
</td>
<!-- <td class="input_sub_title" colspan="2">
<select name="SUPPLY_CODE" id="SUPPLY_CODE" class="select2">
${code_map.SUPPLY_CODE}
</select>
</td>
</td> -->
<td class="input_title">
<label for="">PART구분</label>
<label for="">범주 이름</label>
</td>
<td class="input_sub_title" colspan="">
<select name="PART_TYPE" id="PART_TYPE" class=""></select>
@@ -670,6 +746,8 @@ function fn_edit(){
<c:otherwise></c:otherwise>
</c:choose>
<input type="button" value="수정" id="btnEdit" class="plm_btns update">
<input type="button" value="저장" id="btnSave" class="plm_btns update" style="display:none;">
<input type="button" value="취소" id="btnCancel" class="plm_btns" style="display:none;">
<%-- </c:when> --%>
<%-- <c:otherwise></c:otherwise> --%>
<%-- </c:choose> --%>

View File

@@ -357,11 +357,14 @@ function fn_overlapPartMng(){
<td class="input_title">
<label for="">공급업체</label>
</td>
<td class="input_sub_title" colspan="2">
<td class="input_sub_title" colspan=2">
<input type="text" name="MAKER" id="MAKER" value="${resultMap.MAKER}">
</td>
<!-- <td class="input_sub_title" colspan="2">
<select name="SUPPLY_CODE" id="SUPPLY_CODE" class="select2">
${code_map.SUPPLY_CODE}
</select>
</td>
</td> -->
<td class="input_title">
<label for="">범주이름</label>

View File

@@ -116,6 +116,16 @@ String connector = person.getUserId();
fn_centerPopup(popup_width, popup_height, url);
});
// 도면 다중 업로드 버튼 클릭
$("#btnDrawingUpload").click(function() {
$("#drawingFiles").click();
});
// 파일 선택 이벤트
$("#drawingFiles").change(function() {
fn_uploadDrawingFiles(this.files);
});
fn_search();
});
});
@@ -139,14 +149,14 @@ String connector = person.getUserId();
},
*/
//{headerHozAlign : 'center', hozAlign : 'left', width : '125', title : '모품번', field : 'PARENT_PART_INFO' },
{headerHozAlign : 'center', hozAlign : 'left', width : '180', title : '품번', field : 'PART_NO',
{headerHozAlign : 'center', hozAlign : 'left', width : '200', title : '품번', field : 'PART_NO',
formatter:fnc_createGridAnchorTag,
cellClick:function(e, cell){
var objid = fnc_checkNull(cell.getData().OBJID);
openPartMngPopup(objid);
}
},
{headerHozAlign : 'center', hozAlign : 'left', /* width : '270', */ title : '품명', field : 'PART_NAME' },
{headerHozAlign : 'center', hozAlign : 'left', /*width : '200',*/ title : '품명', field : 'PART_NAME' },
// {headerHozAlign : 'center', hozAlign : 'center', width : '50', title : '수량', field : 'BOM_QTY' }, //Q_QTY QTY QTY_P
{headerHozAlign : 'center', hozAlign : 'center', width : '60', title : '3D', field : 'CU01_CNT',
formatter:fnc_subInfoValueFormatter,
@@ -179,7 +189,7 @@ String connector = person.getUserId();
{headerHozAlign : 'center', hozAlign : 'center', width : '100', title : '열처리경도', field : 'HEAT_TREATMENT_HARDNESS' },
{headerHozAlign : 'center', hozAlign : 'center', width : '100', title : '열처리방법', field : 'HEAT_TREATMENT_METHOD' },
{headerHozAlign : 'center', hozAlign : 'center', width : '100', title : '표면처리', field : 'SURFACE_TREATMENT' },
{headerHozAlign : 'center', hozAlign : 'center', width : '150', title : '공급업체', field : 'SUPPLY_NAME' },
{headerHozAlign : 'center', hozAlign : 'center', width : '150', title : '공급업체', field : 'MAKER' },
{headerHozAlign : 'center', hozAlign : 'center', width : '100', title : '범주 이름', field : 'PART_TYPE_TITLE' },
// {headerHozAlign : 'center', hozAlign : 'center', width : '90', title : '재질', field : 'MATERIAL' },
@@ -292,6 +302,174 @@ String connector = person.getUserId();
fn_centerPopup(popup_width, popup_height, url);
}
// 도면 다중 업로드 처리 함수
function fn_uploadDrawingFiles(files) {
if(!files || files.length === 0) {
Swal.fire('파일을 선택해주세요.');
return;
}
// 선택된 파트 확인 (필수 아님 - 전체 파트 대상)
var selectedParts = _tabulGrid.getSelectedData();
if(!selectedParts || selectedParts.length === 0) {
// 선택 없으면 전체 파트 대상으로 진행
var confirmMsg = '파트를 선택하지 않았습니다.\n';
confirmMsg += '전체 파트를 대상으로 파일명과 일치하는 품번에 업로드됩니다.\n';
confirmMsg += '계속하시겠습니까?';
if(!confirm(confirmMsg)) {
return;
}
}
// 파일 분류 및 처리
var filesByType = {
'3D': [], // stp 파일
'2D': [], // dwg 파일
'PDF': [] // pdf 파일
};
// 파일 확장자 확인 및 분류
for(var i = 0; i < files.length; i++) {
var file = files[i];
var fileName = file.name;
var lastDotIndex = fileName.lastIndexOf('.');
if(lastDotIndex === -1) {
continue; // 확장자가 없는 파일은 스킵
}
var ext = fileName.substring(lastDotIndex + 1).toLowerCase();
if(ext === 'stp' || ext === 'step') {
filesByType['3D'].push(file);
} else if(ext === 'dwg') {
filesByType['2D'].push(file);
} else if(ext === 'pdf') {
filesByType['PDF'].push(file);
}
}
// 업로드할 파일이 있는지 확인
var totalFiles = filesByType['3D'].length + filesByType['2D'].length + filesByType['PDF'].length;
if(totalFiles === 0) {
Swal.fire('업로드 가능한 파일 형식이 없습니다. (stp, dwg, pdf만 가능)');
return;
}
// 확인 메시지
var msg = '총 ' + totalFiles + '개의 파일을 업로드하시겠습니까?\n';
msg += '- 3D (STP): ' + filesByType['3D'].length + '개\n';
msg += '- 2D (DWG): ' + filesByType['2D'].length + '개\n';
msg += '- PDF: ' + filesByType['PDF'].length + '개';
Swal.fire({
title: '도면 다중 업로드',
text: msg,
icon: 'question',
showCancelButton: true,
confirmButtonText: '업로드',
cancelButtonText: '취소'
}).then(function(result) {
if(result.isConfirmed) {
fn_processDrawingUpload(filesByType);
}
});
}
// 실제 업로드 처리
function fn_processDrawingUpload(filesByType) {
// 현재 그리드에 표시된 파트 데이터 가져오기
var gridData = _tabulGrid.getData();
if(!gridData || gridData.length === 0) {
Swal.fire('페이지에 표시된 파트가 없습니다.');
return;
}
// 품번 목록 생성 (현재 화면에 보이는 파트만)
var partNoList = [];
for(var i = 0; i < gridData.length; i++) {
var partNo = gridData[i].PART_NO;
if(partNo) {
partNoList.push(partNo);
}
}
// FormData 생성
var formData = new FormData();
// 현재 화면의 품번 목록 전송
formData.append('partNoList', JSON.stringify(partNoList));
// 모든 파일을 files 이름으로 추가
var allFiles = filesByType['3D'].concat(filesByType['2D']).concat(filesByType['PDF']);
for(var i = 0; i < allFiles.length; i++) {
formData.append('files', allFiles[i]);
}
// 로딩 표시
Swal.fire({
title: '업로드 중...',
text: '파일을 업로드하는 중입니다. 잠시만 기다려주세요.',
allowOutsideClick: false,
allowEscapeKey: false,
allowEnterKey: false,
showConfirmButton: false,
onOpen: function() {
Swal.showLoading();
}
});
// AJAX 업로드
$.ajax({
url: '/partMng/uploadDrawingFilesForPartList.do',
type: 'POST',
data: formData,
processData: false,
contentType: false,
success: function(response) {
Swal.close();
if(response.result === 'success') {
var successMsg = '도면 업로드가 완료되었습니다.\n\n';
successMsg += '- 성공: ' + response.successCount + '개\n';
if(response.failCount > 0) {
successMsg += '- 실패: ' + response.failCount + '개\n';
}
if(response.notFoundCount > 0) {
successMsg += '- 품번 미존재: ' + response.notFoundCount + '개\n';
}
Swal.fire({
title: '업로드 완료',
text: successMsg,
icon: response.failCount > 0 ? 'warning' : 'success'
}).then(function() {
// 그리드 새로고침
fn_search();
// 파일 input 초기화
$("#drawingFiles").val('');
});
} else {
Swal.fire({
title: '업로드 실패',
text: response.message || '도면 업로드 중 오류가 발생했습니다.',
icon: 'error'
});
}
},
error: function(xhr, status, error) {
Swal.close();
console.error('Upload error:', error);
Swal.fire({
title: '업로드 실패',
text: '서버 오류가 발생했습니다: ' + error,
icon: 'error'
});
}
});
}
</script>
</head>
<body class="backcolor">
@@ -314,8 +492,10 @@ String connector = person.getUserId();
<!--
<input type="button" value="삭제" class="plm_btns" id="btnDelete">
-->
<input type="button" value="도면 다중 업로드" class="plm_btns" id="btnDrawingUpload">
<input type="button" value="조회" class="plm_btns" id="btnSearch">
<input type="button" value="Excel Download" class="plm_btns" id="btnExcel">
<input type="file" id="drawingFiles" multiple style="display:none;" accept=".stp,.step,.dwg,.pdf">
</div>
</div>
<div id="plmSearchZon">

View File

@@ -115,6 +115,16 @@ ui-jqgrid tr.jqgrow td {
});
});
// 도면 다중 업로드 버튼 클릭
$("#btnDrawingUpload").click(function() {
$("#drawingFiles").click();
});
// 파일 선택 이벤트
$("#drawingFiles").change(function() {
fn_uploadDrawingFiles(this.files);
});
});
@@ -131,14 +141,14 @@ ui-jqgrid tr.jqgrow td {
// {headerHozAlign : 'center', hozAlign : 'center', width : '60', title : '순', field : 'RNUM' ,frozen:true},
// {headerHozAlign : 'center', hozAlign : 'left', width : '125', title : '모품번', field : 'PARENT_PART_INFO' ,frozen:true},
{headerHozAlign : 'center', hozAlign : 'left', width : '180', title : '품번', field : 'PART_NO',frozen:true,
{headerHozAlign : 'center', hozAlign : 'left', width : '200', title : '품번', field : 'PART_NO',frozen:true,
formatter:fnc_createGridAnchorTag,
cellClick:function(e, cell){
var objid = fnc_checkNull(cell.getData().OBJID);
openPartMngPopup(objid);
}
},
{headerHozAlign : 'center', hozAlign : 'left', /*width : '270',*/ title : '품명', field : 'PART_NAME' ,frozen:true},
{headerHozAlign : 'center', hozAlign : 'left', /*width : '200',*/ title : '품명', field : 'PART_NAME' ,frozen:true},
// {headerHozAlign : 'center', hozAlign : 'center', width : '70', title : '수량', field : 'Q_QTY' },
{headerHozAlign : 'center', hozAlign : 'center', width : '60', title : '3D', field : 'CU01_CNT',
formatter:fnc_subInfoValueFormatter,
@@ -171,7 +181,7 @@ ui-jqgrid tr.jqgrow td {
{headerHozAlign : 'center', hozAlign : 'center', width : '100', title : '열처리경도', field : 'HEAT_TREATMENT_HARDNESS' },
{headerHozAlign : 'center', hozAlign : 'center', width : '100', title : '열처리방법', field : 'HEAT_TREATMENT_METHOD' },
{headerHozAlign : 'center', hozAlign : 'center', width : '100', title : '표면처리', field : 'SURFACE_TREATMENT' },
{headerHozAlign : 'center', hozAlign : 'center', width : '150', title : '공급업체', field : 'SUPPLY_NAME' },
{headerHozAlign : 'center', hozAlign : 'center', width : '150', title : '공급업체', field : 'MAKER' },
{headerHozAlign : 'center', hozAlign : 'center', width : '100', title : '범주 이름', field : 'PART_TYPE_TITLE' },
// {headerHozAlign : 'center', hozAlign : 'left', width : '190', title : '사양(규격)', field : 'SPEC' },
@@ -185,8 +195,30 @@ ui-jqgrid tr.jqgrow td {
// {headerHozAlign : 'center', hozAlign : 'center', width : '90', title : 'PART 구분', field : 'PART_TYPE_TITLE' },
{headerHozAlign : 'center', hozAlign : 'center', width : '90', title : '비고', field : 'REMARK' }
];
// 중복 요청 방지 플래그
var isSearching = false;
function fn_search(){
_tabulGrid = fnc_tabul_search(_tabul_layout_fitColumns, _tabulGrid, "/partMng/partMngTempGridList.do", columns, true);
// 이미 검색 중이면 중단
if (isSearching) {
console.log('검색 중입니다. 잠시만 기다려주세요.');
return;
}
isSearching = true;
_tabulGrid = fnc_tabul_search(_tabul_layout_fitColumns, _tabulGrid, "/partMng/partMngTempGridList.do", columns, true, function() {
// 검색 완료 후 플래그 해제
isSearching = false;
});
// 타임아웃 방어 (10초 후 자동 해제)
setTimeout(function() {
if (isSearching) {
isSearching = false;
}
}, 10000);
}
function openPartMngPopup(objId){
@@ -388,6 +420,161 @@ ui-jqgrid tr.jqgrow td {
fn_centerPopup(popup_width, popup_height, url);
}
// 도면 다중 업로드 처리 함수
function fn_uploadDrawingFiles(files) {
if(!files || files.length === 0) {
Swal.fire('파일을 선택해주세요.');
return;
}
// 파일 분류 및 처리
var filesByType = {
'3D': [], // stp 파일
'2D': [], // dwg 파일
'PDF': [] // pdf 파일
};
// 파일 확장자 확인 및 분류
for(var i = 0; i < files.length; i++) {
var file = files[i];
var fileName = file.name;
var lastDotIndex = fileName.lastIndexOf('.');
if(lastDotIndex === -1) {
continue; // 확장자가 없는 파일은 스킵
}
var ext = fileName.substring(lastDotIndex + 1).toLowerCase();
if(ext === 'stp' || ext === 'step') {
filesByType['3D'].push(file);
} else if(ext === 'dwg') {
filesByType['2D'].push(file);
} else if(ext === 'pdf') {
filesByType['PDF'].push(file);
}
}
// 업로드할 파일이 있는지 확인
var totalFiles = filesByType['3D'].length + filesByType['2D'].length + filesByType['PDF'].length;
if(totalFiles === 0) {
Swal.fire('업로드 가능한 파일 형식이 없습니다. (stp, dwg, pdf만 가능)');
return;
}
// 확인 메시지
var msg = '총 ' + totalFiles + '개의 파일을 업로드하시겠습니까?\n';
msg += '- 3D (STP): ' + filesByType['3D'].length + '개\n';
msg += '- 2D (DWG): ' + filesByType['2D'].length + '개\n';
msg += '- PDF: ' + filesByType['PDF'].length + '개';
Swal.fire({
title: '도면 다중 업로드',
text: msg,
icon: 'question',
showCancelButton: true,
confirmButtonText: '업로드',
cancelButtonText: '취소'
}).then(function(result) {
if(result.isConfirmed) {
fn_processDrawingUpload(filesByType);
}
});
}
// 실제 업로드 처리
function fn_processDrawingUpload(filesByType) {
// 현재 그리드에 표시된 파트 데이터 가져오기
var gridData = _tabulGrid.getData();
if(!gridData || gridData.length === 0) {
Swal.fire('페이지에 표시된 파트가 없습니다.');
return;
}
// 품번 목록 생성 (현재 화면에 보이는 파트만)
var partNoList = [];
for(var i = 0; i < gridData.length; i++) {
var partNo = gridData[i].PART_NO;
if(partNo) {
partNoList.push(partNo);
}
}
// FormData 생성
var formData = new FormData();
// 현재 화면의 품번 목록 전송
formData.append('partNoList', JSON.stringify(partNoList));
// 모든 파일을 files 이름으로 추가
var allFiles = filesByType['3D'].concat(filesByType['2D']).concat(filesByType['PDF']);
for(var i = 0; i < allFiles.length; i++) {
formData.append('files', allFiles[i]);
}
// 로딩 표시
Swal.fire({
title: '업로드 중...',
text: '파일을 업로드하는 중입니다. 잠시만 기다려주세요.',
allowOutsideClick: false,
allowEscapeKey: false,
allowEnterKey: false,
showConfirmButton: false,
onOpen: function() {
Swal.showLoading();
}
});
// AJAX 업로드
$.ajax({
url: '/partMng/uploadDrawingFilesForPartList.do',
type: 'POST',
data: formData,
processData: false,
contentType: false,
success: function(response) {
Swal.close();
if(response.result === 'success') {
var successMsg = '도면 업로드가 완료되었습니다.\n\n';
successMsg += '- 성공: ' + response.successCount + '개\n';
if(response.failCount > 0) {
successMsg += '- 실패: ' + response.failCount + '개\n';
}
if(response.notFoundCount > 0) {
successMsg += '- 품번 미존재: ' + response.notFoundCount + '개\n';
}
Swal.fire({
title: '업로드 완료',
text: successMsg,
icon: response.failCount > 0 ? 'warning' : 'success'
}).then(function() {
// 그리드 새로고침
fn_search();
// 파일 input 초기화
$("#drawingFiles").val('');
});
} else {
Swal.fire({
title: '업로드 실패',
text: response.message || '도면 업로드 중 오류가 발생했습니다.',
icon: 'error'
});
}
},
error: function(xhr, status, error) {
Swal.close();
console.error('Upload error:', error);
Swal.fire({
title: '업로드 실패',
text: '서버 오류가 발생했습니다: ' + error,
icon: 'error'
});
}
});
}
function openExcelPopup() {
/*
if($("#customer_cd").val()==""){
@@ -426,7 +613,9 @@ ui-jqgrid tr.jqgrow td {
<input type="button" value="삭제" class="plm_btns" id="btnDelete">
<input type="button" value="등록" class="plm_btns" id="btnReg">
<input type="button" value="등록(Excel Upload)" class="plm_btns" onclick="openExcelPopup();">
<input type="button" value="도면 다중 업로드" class="plm_btns" id="btnDrawingUpload">
<input type="button" value="조회" class="plm_btns" id="btnSearch">
<input type="file" id="drawingFiles" multiple style="display:none;" accept=".stp,.step,.dwg,.pdf">
</div>
</div>

View File

@@ -21,7 +21,10 @@ $(document).ready(function(){
$("._table1").scrollLeft($("._table2").scrollLeft());
});
// 품번/품명 Select2 AJAX 초기화 (common.js의 새 함수 사용)
initPartSelect2Ajax("#search_partNo", "#search_partName", "#search_partObjId", {
debug: false // 디버깅 모드 비활성화
});
$("#mainGrid").jqGrid({
height : 630,
@@ -76,6 +79,19 @@ $(document).ready(function(){
fn_search();
});
//정전개 조회
$("#btnSearchAscending").click(function(){
$("#searchType").val("ascending");
fn_search();
});
//역전개 조회
$("#btnSearchDescending").click(function(){
$("#searchType").val("descending");
fn_search();
});
$("#btnExcel").click(function(){
$("#search_partNo").val($.trim($("#search_partNo").val()));
@@ -690,18 +706,25 @@ function fn_excelExport(pGridObj,pFileName){
<form name="form1" id="form1" action="" method="post">
<input type="hidden" name="search" id="search" value="Y">
<input type="hidden" name="actionType" id="actionType" value="" />
<input type="hidden" name="searchType" id="searchType" value="ascending" />
<div class="min_part_enroll">
<div class="content-box">
<div class=""> <!-- content-box-s -->
<div class=plm_menu_name_gdnsi>
<h2>
<span>제품관리_BOM 조회</span>
<c:if test="${!empty param.searchType}">
<span style="font-size: 14px; color: #666; margin-left: 10px;">
(${param.searchType eq 'descending' ? '역전개' : '정전개'})
</span>
</c:if>
</h2>
<div class="btnArea">
<!--
<input type="button" value="Excel Download" class="plm_btns" id="btnExcel">
-->
<input type="button" value="조회" class="plm_btns" id="btnSearch">
<input type="button" value="정전개 조회" class="plm_btns" id="btnSearchAscending">
<input type="button" value="역전개 조회" class="plm_btns" id="btnSearchDescending">
</div>
</div>
<div id="plmSearchZon">
@@ -739,6 +762,24 @@ function fn_excelExport(pGridObj,pFileName){
<td class="align_r">
<label for="" class="">품번</label>
</td>
<td>
<select name="search_partNo" id="search_partNo" class="select2-part" style="width: 100%;">
<option value="">품번 선택</option>
</select>
<input type="hidden" name="search_partObjId" id="search_partObjId" value=""/>
</td>
<td class="align_r">
<label for="" class="">품명</label>
</td>
<td colspan="3">
<select name="search_partName" id="search_partName" class="select2-part" style="width: 100%;">
<option value="">품명 선택</option>
</select>
</td>
<!-- <td class="align_r">
<label for="" class="">품번</label>
</td>
<td>
<input type="text" name="search_partNo" id="search_partNo" value="${param.search_partNo}" class="text_area" style="width:200px;"/>
</td>
@@ -747,7 +788,7 @@ function fn_excelExport(pGridObj,pFileName){
</td>
<td>
<input type="text" name="search_partName" id="search_partName" value="${param.search_partName}" class="text_area" style="width:200px;"/>
</td>
</td> -->
<!-- <td class="align_r">
<label for="" class="">LEVEL</label>
</td>
@@ -771,6 +812,11 @@ function fn_excelExport(pGridObj,pFileName){
<div class="ascendig_text">
<font size="2px">총 ${fn:length(List)}건</font>
<c:if test="${!empty param.searchType && param.searchType eq 'descending'}">
<font size="2px" style="color: #ff6600; margin-left: 10px;">
※ 역전개: 선택한 부품을 사용하는 상위 BOM을 표시합니다
</font>
</c:if>
</div>
<div class="in_table_scroll_wrap _table1" style="height:26px;width:99.4%;">
@@ -788,23 +834,23 @@ function fn_excelExport(pGridObj,pFileName){
</c:forEach>
</c:when>
</c:choose>
<col width="120px" /> <!-- 품번 -->
<col width="150px" /> <!-- 품명 -->
<col width="200px" /> <!-- 품번 -->
<col width="200px" /> <!-- 품명 -->
<col width="35px" /> <!-- Qty -->
<col width="50px" /> <!-- P_Qty -->
<col width="30px" /> <!-- 3D -->
<col width="30px" /> <!-- 2D -->
<col width="30px" /> <!-- 2D PDF -->
<col width="100px" /> <!-- 재료 -->
<col width="130px" /> <!-- 열처리경도 -->
<col width="150px" /> <!-- 열처리방법 -->
<col width="150px" /> <!-- 표면처리 -->
<col width="100px" /> <!-- 공급업체 -->
<col width="90px" /> <!-- 재료 -->
<col width="90px" /> <!-- 열처리경도 -->
<col width="90px" /> <!-- 열처리방법 -->
<col width="90px" /> <!-- 표면처리 -->
<col width="90px" /> <!-- 공급업체 -->
<col width="80px" /> <!-- PART 타입 -->
<col width="60px" /> <!-- REVISION -->
<col width="70px" /> <!-- EO No -->
<col width="70px" /> <!-- EO Date -->
<col width="230px" /> <!-- REMARK -->
<col width="200px" /> <!-- REMARK -->
</colgroup>
<thead>
<tr class="plm_thead">
@@ -856,23 +902,23 @@ function fn_excelExport(pGridObj,pFileName){
</c:forEach>
</c:when>
</c:choose>
<col width="120px" /> <!-- 품번 -->
<col width="150px" /> <!-- 품명 -->
<col width="200px" /> <!-- 품번 -->
<col width="200px" /> <!-- 품명 -->
<col width="35px" /> <!-- Qty -->
<col width="50px" /> <!-- P_Qty -->
<col width="30px" /> <!-- 3D -->
<col width="30px" /> <!-- 2D -->
<col width="30px" /> <!-- 2D PDF -->
<col width="100px" /> <!-- 재료 -->
<col width="130px" /> <!-- 열처리경도 -->
<col width="150px" /> <!-- 열처리방법 -->
<col width="150px" /> <!-- 표면처리 -->
<col width="100px" /> <!-- 공급업체 -->
<col width="90px" /> <!-- 재료 -->
<col width="90px" /> <!-- 열처리경도 -->
<col width="90px" /> <!-- 열처리방법 -->
<col width="90px" /> <!-- 표면처리 -->
<col width="90px" /> <!-- 공급업체 -->
<col width="80px" /> <!-- PART 타입 -->
<col width="60px" /> <!-- REVISION -->
<col width="70px" /> <!-- EO No -->
<col width="70px" /> <!-- EO Date -->
<col width="230px" /> <!-- REMARK -->
<col width="200px" /> <!-- REMARK -->
</colgroup>
<c:choose>
<c:when test="${empty List}">
@@ -919,10 +965,10 @@ function fn_excelExport(pGridObj,pFileName){
<td><a href="#" class="File file_${item.FILE_3D_CNT eq 0?'empty_':''}icon" data-OBJID="${item.PART_OBJID}" data-docType="3D_CAD" data-docTypeName="3D CAD 첨부파일"></a></td>
<td><a href="#" class="File file_${item.FILE_2D_CNT eq 0?'empty_':''}icon" data-OBJID="${item.PART_OBJID}" data-docType="2D_DRAWING_CAD" data-docTypeName="2D(Drawing) CAD 첨부파일"></a></td>
<td><a href="#" class="File file_${item.FILE_PDF_CNT eq 0?'empty_':''}icon" data-OBJID="${item.PART_OBJID}" data-docType="2D_PDF_CAD" data-docTypeName="2D(PDF) CAD 첨부파일"></a></td>
<td title="${item.MATERIAL}" class="align_c">${item.MATERIAL}</td><!-- 재료 -->
<td title="${item.SPEC}" class="align_l" style="text-align: left; padding-left: 5px;">${item.SPEC}</td><!-- 열처리경도 -->
<td title="${item.POST_PROCESSING}" class="align_l" style="text-align: left; padding-left: 5px;">${item.POST_PROCESSING}</td><!-- 열처리방법 -->
<td title="${item.POST_PROCESSING}" class="align_l" style="text-align: left; padding-left: 5px;">${item.POST_PROCESSING}</td><!-- 표면처리 -->
<td title="${item.MATERIAL}" class="align_l">${item.MATERIAL}</td><!-- 재료 -->
<td title="${item.HEAT_TREATMENT_HARDNESS}" class="align_l" style="text-align: left; padding-left: 5px;">${item.HEAT_TREATMENT_HARDNESS}</td><!-- 열처리경도 -->
<td title="${item.HEAT_TREATMENT_METHOD}" class="align_l" style="text-align: left; padding-left: 5px;">${item.HEAT_TREATMENT_METHOD}</td><!-- 열처리방법 -->
<td title="${item.SURFACE_TREATMENT}" class="align_l" style="text-align: left; padding-left: 5px;">${item.SURFACE_TREATMENT}</td><!-- 표면처리 -->
<td title="${item.MAKER}" class="align_l" style="text-align: left; padding-left: 5px;">${item.MAKER}</td><!-- 공급업체 -->
<td title="${item.PART_TYPE_TITLE}" class="align_c">${item.PART_TYPE_TITLE}</td><!-- PART_TYPE -->
<td title="${item.REVISION}" class="align_c" style="text-align: left; padding-left: 5px;">${item.REVISION}</td><!-- REVISION -->

View File

@@ -157,31 +157,54 @@ var columns = [
{headerHozAlign : 'center', hozAlign : 'left', width : '270', title : '유닛명', field : 'UNIT_NAME' },*/
{headerHozAlign : 'center', hozAlign : 'center', width : '150', title : '제품구분', field : 'PRODUCT_NAME' },
{headerHozAlign : 'center', hozAlign : 'left', width : '250', title : '품번', field : 'PART_NO' },
{headerHozAlign : 'center', hozAlign : 'left', width : '250', title : '품', field : 'PART_NAME' },
{headerHozAlign : 'center', hozAlign : 'center', width : '150', title : 'E-BOM', field : 'BOM_CNT',
formatter: fnc_subInfoValueFormatter,
cellClick:function(e, cell){
var objId = fnc_checkNull(cell.getData().OBJID);
//var bomReportObjId = fnc_checkNull(cell.getData().BOM_REPORMECHANICAL_TYPET_OBJID);
fn_openSetStructure(objId);
}
},
// {headerHozAlign : 'center', hozAlign : 'center', width : '120', title : '등록자', field : 'DEPT_USER_NAME' },
{headerHozAlign : 'center', hozAlign : 'center', width : '150', title : '등록', field : 'REG_DATE' },
// {headerHozAlign : 'center', hozAlign : 'center', width : '100', title : '배포일', field : 'DEPLOY_DATE' },
{headerHozAlign : 'center', hozAlign : 'center', width : '150', title : 'Version', field : 'REVISION' },
{headerHozAlign : 'center', hozAlign : 'left', /* width : '200', */ title : '배포사유', field : 'NOTE' },
{headerHozAlign : 'center', hozAlign : 'center', width : '150', title : '상태', field : 'STATUS' }
{headerHozAlign : 'center', hozAlign : 'center', width : '160', title : '제품구분', field : 'PRODUCT_NAME' },
// {headerHozAlign : 'center', hozAlign : 'center', title : '제품구분', field : 'PRODUCT_CD' ,hidden: true},
{headerHozAlign : 'center', hozAlign : 'left', width : '210', title : '품', field : 'PART_NO' },
{headerHozAlign : 'center', hozAlign : 'left', title : '품명', field : 'PART_NAME' },
{headerHozAlign : 'center', hozAlign : 'center', width : '150', title : 'E-BOM', field : 'BOM_CNT',
formatter: fnc_subInfoValueFormatter,
cellClick:function(e, cell){
var objId = fnc_checkNull(cell.getData().OBJID);
//var bomReportObjId = fnc_checkNull(cell.getData().BOM_REPORMECHANICAL_TYPET_OBJID);
fn_openSetStructure(objId);
}
},
// {headerHozAlign : 'center', hozAlign : 'center', width : '120', title : '등록', field : 'DEPT_USER_NAME' },
{headerHozAlign : 'center', hozAlign : 'center', width : '130', title : '등록일', field : 'REG_DATE' },
// {headerHozAlign : 'center', hozAlign : 'center', width : '100', title : '배포일', field : 'DEPLOY_DATE' },
{headerHozAlign : 'center', hozAlign : 'center', width : '110', title : 'Version', field : 'REVISION' },
// {headerHozAlign : 'center', hozAlign : 'left', /* width : '200', */ title : '배포사유', field : 'NOTE' },
{headerHozAlign : 'center', hozAlign : 'center', width : '110', title : '상태', field : 'STATUS' }
];
// 중복 요청 방지를 위한 로딩 플래그
var isSearching = false;
//var grid;
function fn_search(){
_tabulGrid = fnc_tabul_search(_tabul_layout_fitColumns, _tabulGrid, "/partMng/searchStructureGridList.do", columns, true);
// 이미 검색 중이면 중복 요청 방지
if (isSearching) {
console.log('검색 중입니다. 잠시만 기다려주세요.');
return;
}
isSearching = true;
// 기존 그리드 검색 함수 실행
_tabulGrid = fnc_tabul_search(_tabul_layout_fitColumns, _tabulGrid, "/partMng/searchStructureGridList.do", columns, true, function() {
// 검색 완료 후 플래그 해제
isSearching = false;
});
// fnc_tabul_search가 콜백을 지원하지 않을 경우를 위한 타임아웃 처리
setTimeout(function() {
if (isSearching) {
isSearching = false;
}
}, 10000); // 10초 타임아웃
}
//양산제품에 해당하는 SPEC 정보 목록을 가져온다.
@@ -462,7 +485,7 @@ function fn_openSetBomCopy(product_code){
* 구조등록 팝업
*/
function fn_openSetStructure(objId){
window.open("/partMng/setStructurePopupMainFS.do?objId="+objId, "", "width=1880, height=900, resizable=no");
window.open("/partMng/setStructurePopupMainFS.do?objId="+objId, "", "width=1880, height=900, resizable=yes");
}
/**
* 배포사유 입력 팝업
@@ -504,7 +527,7 @@ function saveexcelpop() {
var STATUS = fnc_checkNull(selectedStructure[0].STATUS);
if(STATUS != 'create'){ //deploy
if(STATUS != 'N'){ //deploy
Swal.fire('등록중인 건만 등록/추가 할 수 있습니다.');
return;
}
@@ -513,16 +536,37 @@ function saveexcelpop() {
var project_name ="";
var unit_code ="";
var BOM_REPORT_OBJID ="";
var BOM_PART_NO ="";
var BOM_PART_NAME ="";
var BOM_PRODUCT_CD ="";
var BOM_VERSION ="";
for (var i = 0; i < selectedStructure.length; i++) {
customer_cd = fnc_checkNull(selectedStructure[i].CUSTOMER_OBJID);
project_name = fnc_checkNull(selectedStructure[i].CONTRACT_OBJID);
unit_code = fnc_checkNull(selectedStructure[i].UNIT_CODE);
//customer_cd = fnc_checkNull(selectedStructure[i].CUSTOMER_OBJID);
//project_name = fnc_checkNull(selectedStructure[i].CONTRACT_OBJID);
//unit_code = fnc_checkNull(selectedStructure[i].UNIT_CODE);
BOM_REPORT_OBJID = fnc_checkNull(selectedStructure[i].OBJID);
BOM_PART_NO = fnc_checkNull(selectedStructure[i].PART_NO);
BOM_PART_NAME = fnc_checkNull(selectedStructure[i].PART_NAME);
BOM_PRODUCT_CD = fnc_checkNull(selectedStructure[i].PRODUCT_CD);
BOM_VERSION = fnc_checkNull(selectedStructure[i].REVISION);
}
var url = "/partMng/openBomReportExcelImportPopUp.do?customer_cd="+customer_cd+"&project_name="+project_name+"&unit_code="+unit_code+"&BOM_REPORT_OBJID="+BOM_REPORT_OBJID;
var target = "openBomReportExcelImportPopUp";
// hiddenForm을 사용하여 POST 방식으로 팝업 열기 (한글 인코딩 문제 해결)
var hiddenForm = document.hiddenForm;
var url = "/partMng/openBomReportExcelImportPopUp.do";
var target = "openBomReportExcelImportPopUp";
hiddenForm.PRODUCT_CD.value = BOM_PRODUCT_CD;
hiddenForm.BOM_PART_NAME.value = BOM_PART_NAME;
hiddenForm.BOM_PART_NO.value = BOM_PART_NO;
hiddenForm.BOM_REPORT_OBJID.value = BOM_REPORT_OBJID;
hiddenForm.BOM_VERSION.value = BOM_VERSION;
window.open('', target, 'width=1920, height=860, menubars=no, scrollbars=yes, resizable=yes');
hiddenForm.action = url;
hiddenForm.target = target;
hiddenForm.submit();
}
}else{
if($("#customer_cd").val()==""){
@@ -540,8 +584,8 @@ function saveexcelpop() {
var url = "/partMng/openBomReportExcelImportPopUp.do?customer_cd="+$("#customer_cd").val()+"&project_name="+$("#project_name").val()+"&unit_code="+$("#unit_code").val();
var target = "openBomReportExcelImportPopUp";
window.open(url, target,"width=1920, height=860, menubars=no, scrollbars=yes, resizable=yes");
}
window.open(url, target,"width=1920, height=860, menubars=no, scrollbars=yes, resizable=yes");
}
</script>
</head>
@@ -552,6 +596,11 @@ function saveexcelpop() {
<input type="hidden" name="param_product_mgmt_spec" id="param_product_mgmt_spec">
<input type="hidden" name="param_upg_no" id="param_upg_no">
<input type="hidden" name="BOM_REPORT_OBJID" id="BOM_REPORT_OBJID">
<!-- BOM Excel Import 팝업용 파라미터 -->
<input type="hidden" name="PRODUCT_CD" id="PRODUCT_CD">
<input type="hidden" name="BOM_PART_NAME" id="BOM_PART_NAME">
<input type="hidden" name="BOM_PART_NO" id="BOM_PART_NO">
<input type="hidden" name="BOM_VERSION" id="BOM_VERSION">
</form>
<form name="form1" id="form1" action="" method="post">
@@ -632,17 +681,19 @@ function saveexcelpop() {
<label>등록일</label>
</td>
<td>
<input type="text" name="SEARCH_DEPLOY_DATE_FROM" id="SEARCH_DEPLOY_DATE_FROM" style="width:90px;" autocomplete="off" value="${param.SEARCH_DEPLOY_DATE_FROM}">~
<input type="text" name="SEARCH_DEPLOY_DATE_TO" id="SEARCH_DEPLOY_DATE_TO" style="width:90px;" autocomplete="off" value="${param.SEARCH_DEPLOY_DATE_TO}">
<input type="text" name="search_fromDate" id="search_fromDate" style="width:90px;" autocomplete="off" value="${param.search_fromDate}">~
<input type="text" name="search_toDate" id="search_toDate" style="width:90px;" autocomplete="off" value="${param.search_toDate}">
</td>
<td><label for="status">상태</label></td>
<td>
<select id="status" name="status" class="select2" style="width:170px;">
<option value="">선택</option>
<option value="create" ${param.status eq 'create'?'selected':''}>등록중</option>
<option value="Y" ${param.status eq 'Y'?'selected':''}>Y</option>
<option value="N" ${param.status eq 'N'?'selected':''}>N</option>
<!-- <option value="create" ${param.status eq 'create'?'selected':''}>등록중</option>
<option value="changeDesign" ${param.status eq 'changeDesign'?'selected':''}>설계변경미배포</option>
<option value="deploy"${param.status eq 'deploy'?'selected':''}>배포완료</option>
<option value="deploy"${param.status eq 'deploy'?'selected':''}>배포완료</option> -->
</select>
</td>
</tr>

View File

@@ -10,76 +10,86 @@
<script>
$(function(){
if(isCreateBom()){
visibleChangeTalbe(false);
}
$('.select2').select2();
//0001055 - - 고객요청
//0001056 - - 자체설변
$('#CHANGE_TYPE option').not($('#CHANGE_TYPE').find('[value=""],[value=0001055],[value=0001056]')).remove();
//0001610 - - 파트삭제 test
//0001611 - - 파트추가
//0001626 - - 파트추가 real
//0001637 - - 파트삭제
//$('#CHANGE_OPTION option').not($('#CHANGE_OPTION').find('[value=""],[value=0001610],[value=0001611]')).remove(); //test
$('#CHANGE_OPTION option').not($('#CHANGE_OPTION').find('[value=""],[value=0001626],[value=0001637],[value=0001790]')).remove();
$('.select2').select2();
//Part 연결
$("#moveLeft").click(function(){
var rightPartArr = $(".partChks:checked", parent.frames['rightFrame'].document);
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 leftPartNoQty = $("input[name=checkedPartNo]:checked", parent.frames['leftFrame'].document).attr("data-PART_NO_QTY");
var leftParentObjId = $("input[name=checkedPartNo]:checked", parent.frames['leftFrame'].document).attr("data-OBJID");
var leftPartLastObjId = $("input[name=checkedPartNo]:checked", parent.frames['leftFrame'].document).attr("data-LAST_PART_OBJID");
var leftQtyParObjId = $("input[name=checkedPartNo]:checked", parent.frames['leftFrame'].document).attr("data-PART_OBJID");
// Tabulator에서 선택된 오른쪽 행 데이터 가져오기
var rightFrame = parent.frames['rightFrame'];
var rightSelectedRows = rightFrame.getSelectedRows ? rightFrame.getSelectedRows() : [];
//같은 Part를 연결한건지 체크
if(rightSelectedRows.length === 0) {
alert("선택된 파트가 없습니다.");
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;
$(rightPartArr).each(function(i){
var rightPartNo = $(this).val();
for(var i = 0; i < rightSelectedRows.length; i++){
var rowData = rightSelectedRows[i].getData();
var rightPartNo = rowData.PART_NO;
if(rightPartNo == leftPartNo){
alert("오류 Part No : ["+rightPartNo+"]\n같은 Part No끼리 연결할 수 없습니다.");
isSamePart = true;
break;
}
});
}
if(isSamePart) return false;
//연결하려는 part가 상위에 있는 part인지 확인.
var leftParentParts = $("input[name=checkedPartNo]:checked", parent.frames['leftFrame'].document).attr("data-PARENT_PARTS");
// 연결하려는 part가 상위에 있는 part인지 확인
var deniedPartArr = [];
if(fnc_checkNull(leftParentParts).indexOf(",") > 0){
deniedPartArr = leftParentParts.split(",");
}
var isDeniedPart = false;
$(rightPartArr).each(function(i){
var rightPartNo = $(this).val();
var rightPartType = $(this).attr("data-PART_TYPE");
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 i = 0 ; i < deniedPartArr.length ; i++){
if(rightPartNo == deniedPartArr[i]){
for(var j = 0 ; j < deniedPartArr.length ; j++){
if(rightPartNo == deniedPartArr[j]){
alert("오류 Part No : "+"["+rightPartNo+"]\n이미 상위에 등록된 Part No 입니다.");
isDeniedPart = true;
return;
break;
}
}
if(isDeniedPart) break;
}
});
}
if(isDeniedPart) return;
//연결하려는 part가 상위에 있는 part인지 확인. end
// 선택된 파트의 OBJID 배열 생성
var rightCheckedArr = [];
$(rightPartArr).each(function(i){
rightCheckedArr.push($(this).val());
});
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);
@@ -89,20 +99,7 @@ $(function(){
}
}
if(!fnc_validate2('form1')){
return;
}
//0001611 - - 파트추가
//0001610 - - 파트삭제
if(!isCreateBom() && '0001626' != $('#CHANGE_OPTION').val()){
alert('설변사유는 파트추가만 가능합니다.');
$('#CHANGE_OPTION').val('0001626');
$('.select2').select2();
return;
}
fn_relatePartInfo(leftPartNoObj.val(), rightCheckedArr, leftPartNoQty, leftPartLastObjId, leftPartChildObjId, leftQtyParObjId);
fn_relatePartInfo(leftPartChildObjId, rightCheckedArr, leftPartNoQty, leftPartLastObjId, leftPartChildObjId, leftQtyParObjId);
});
//end of Part 연결
@@ -115,23 +112,6 @@ $(function(){
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");
/* alert('leftPartNo ->'+leftPartNo+".");
alert('leftParentObjId ->'+leftParentObjId+".");
alert('leftParentPartNo ->'+leftParentPartNo+"."); */
if(!fnc_validate2('form1')){
return;
}
//0001611 - - 파트추가
//0001610 - - 파트삭제
if(!isCreateBom() && '0001637' != $('#CHANGE_OPTION').val()){
alert('설변사유는 파트삭제만 가능합니다.');
$('#CHANGE_OPTION').val('0001637');
$('.select2').select2();
return;
}
fn_deletePartRelateInfo(leftPartNoObj.val(), leftPartLastObjId, leftParentPartNo, leftParentObjId, leftPartChildObjId);
});
//end of 연결된 part 삭제
@@ -139,48 +119,47 @@ $(function(){
//연결된 part 변경
$("#moveChange").click(function(){
var leftPartNoList = $("input[name=checkedPartNo]:checked", parent.frames['leftFrame'].document);
var leftPartNo = $("input[name=checkedPartNo]:checked", parent.frames['leftFrame'].document).attr("data-PART_NO");
var leftPartObjid = $("input[name=checkedPartNo]:checked", parent.frames['leftFrame'].document).attr("data-BOM_LAST_PART_OBJID");
var leftParentPartObjid = $("input[name=checkedPartNo]:checked", parent.frames['leftFrame'].document).attr("data-PARENT_PART_NO");
var rightPartNoList = $(".partChks:checked", parent.frames['rightFrame'].document);
var rightPartNo = $(".partChks:checked", parent.frames['rightFrame'].document).attr("data-PART_NO");
var rightPartRev = $(".partChks:checked", parent.frames['rightFrame'].document).attr("data-PART_REV");
var leftPartChildObjId = leftPartNoList.val();
//var leftPartNoQty = $("input[name=checkedPartNo]:checked", parent.frames['leftFrame'].document).attr("data-PART_NO_QTY");
//alert("leftPartObjid::"+leftPartObjid);
var leftPartBomQtyObjId = $("input[name=checkedPartNo]:checked", parent.frames['leftFrame'].document).attr("data-OBJID");
if(!fnc_validate2('form1')){
return;
}
if(0 == leftPartNoList.length){
if(leftPartNoList.length === 0){
alert("선택된 파트가 없습니다.");
return false;
}else if(1 == leftPartNoList.length){
if(0 == rightPartNoList.length){
alert("선택된 파트가 없습니다.");
return false;
}else if(1 == rightPartNoList.length){
fn_changeRelatePartInfo(leftPartBomQtyObjId,rightPartNoList.val(),leftPartNoList.val(),leftParentPartObjid,leftPartChildObjId,leftPartObjid,rightPartNo,rightPartRev);
}else{
alert("한번에 1개의 파트만 변경가능합니다.");
return false;
}
}else{
}
if(leftPartNoList.length > 1){
alert("한번에 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){
alert("선택된 파트가 없습니다.");
return false;
}
if(rightSelectedRows.length > 1){
alert("한번에 1개의 파트만 변경가능합니다.");
return false;
}
var rightRowData = rightSelectedRows[0].getData();
var rightPartNo = rightRowData.PART_NO;
var rightPartRev = rightRowData.REVISION || "";
var rightObjId = rightRowData.OBJID;
fn_changeRelatePartInfo(leftPartBomQtyObjId, rightObjId, leftPartChildObjId, leftParentPartObjid, leftPartChildObjId, leftPartObjid, rightPartNo, rightPartRev);
});
});
function isCreateBom(){
return '${info.STATUS}' == 'create';
}
//1레벨에 같은 Part No가 등록되어있는지 확인.
function fn_checkSameTopPartNo(rightCheckedArr){
var result = false;
@@ -188,7 +167,8 @@ function fn_checkSameTopPartNo(rightCheckedArr){
$.ajax({
url: "/partMng/checkSameTopPartNo.do",
method: 'post',
data: {"OBJID":$("#objId").val(), "rightCheckedArr":rightCheckedArr},
traditional: true, // 배열 파라미터 처리를 위한 옵션
data: {"OBJID":$("#objId").val(), "rightCheckedArr[]":rightCheckedArr}, // [] 추가
dataType: 'json',
async:false,
success: function(data) {
@@ -204,11 +184,6 @@ function fn_checkSameTopPartNo(rightCheckedArr){
}
//end of 1레벨에 같은 Part No가 등록되어있는지 확인.
function resetChange(){
$('#CHANGE_TYPE').val();
$('#CHANGE_OPTION').val();
}
//구조 연결 해제
function fn_deletePartRelateInfo(leftObjId, leftPartLastObjId, leftParentPartNo, leftParentObjId, leftPartChildObjId){
if(leftObjId == null){
@@ -219,17 +194,20 @@ function fn_deletePartRelateInfo(leftObjId, leftPartLastObjId, leftParentPartNo,
if(!confirm("연결 해제하시겠습니까?")) return;
$.ajax({
//url: "/partMng/deletePartRelateInfo.do",
url: "/partMng/deleteStatusPartRelateInfo.do", //231211 위에서 변경
url: "/partMng/deleteStatusPartRelateInfo.do",
method: 'post',
data: {"OBJID":$("#objId").val(), "leftObjId":leftObjId, "partObjId":leftPartLastObjId, "BOM_REPORT_OBJID":$("#objId").val()
, "leftPartChildObjId":leftPartChildObjId, "leftParentPartNo":leftParentPartNo, "leftParentObjId":leftParentObjId,
"CHANGE_TYPE":$('#CHANGE_TYPE').val(), "CHANGE_OPTION":$('#CHANGE_OPTION').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){
@@ -257,27 +235,34 @@ function fn_relatePartInfo(leftObjId, rightCheckedArr, leftPartNoQty, leftPartLa
$.ajax({
url: "/partMng/relatePartInfo.do",
method: 'post',
data: {"leftObjId":leftObjId,"leftPartNoQty":leftPartNoQty, "OBJID":$("#objId").val(), "rightCheckedArr":rightCheckedArr, "partObjId":leftPartLastObjId, "BOM_REPORT_OBJID":$("#objId").val(),
"leftPartChildObjId":leftPartChildObjId, "leftQtyParObjId":leftQtyParObjId,
"CHANGE_TYPE":$('#CHANGE_TYPE').val(), "CHANGE_OPTION":$('#CHANGE_OPTION').val()},
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());
//$(parent.frames['rightFrame'].fn_searchPart());
$(".partChks", parent.frames['rightFrame'].document).prop("checked", false);
// 오른쪽 프레임 선택 해제 (Tabulator)
var rightFrame = parent.frames['rightFrame'];
if(rightFrame.clearSelection) {
rightFrame.clearSelection();
}
//좌측에 선택된 정보를 유지하기위해 0.1초간의 딜레이(새로고침시간)을 주고 10번 기존에 선택된 정보를 선택하도록 한다. 10번사이 선택이 된다면 loop break;
// for(var i = 0 ; i < 10 ; i++){
// var leftPartChkLength = $("input[name='checkecPartNo']:checked", parent.frames['leftFrame'].document).length;
// setTimeout(function(){
// $("input[name='checkedPartNo']:radio[value='"+leftPartNo+"']", parent.frames['leftFrame'].document).prop("checked", true);
// }, 100);
// if(leftPartChkLength > 0) break;
// }
//end for
// 부모 창(E-BOM 목록) 새로고침
if(window.opener && window.opener.fn_search) {
window.opener.fn_search();
}
}
}
, error: function(jqxhr, status, error){
@@ -289,32 +274,36 @@ function fn_relatePartInfo(leftObjId, rightCheckedArr, leftPartNoQty, leftPartLa
}
//end of 구조 연결
function visibleChangeTalbe(visible){
if(visible){
$("#changeTable").show();
}else{
$("#changeTable").hide();
}
$('#CHANGE_TYPE,#CHANGE_OPTION').attr('required', visible);
}
//구조 연결 변경
function fn_changeRelatePartInfo(objId,rightObjId,leftObjId,leftPartNoQty,leftPartChildObjId,leftPartObjid,rightPartNo,rightPartRev){
$.ajax({
url: "/partMng/changeRelatePartInfo.do",
method: 'post',
data: {"rightObjId":rightObjId, "OBJID":objId,"BOM_REPORT_OBJID":$("#objId").val(),"CHANGE_TYPE":$('#CHANGE_TYPE').val(),
"CHANGE_OPTION":$('#CHANGE_OPTION').val(), "leftObjId":leftObjId,"leftPartNoQty":leftPartNoQty,"leftPartChildObjId":leftPartChildObjId,
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());
$(parent.frames['rightFrame'].fn_searchPart());
// 오른쪽 프레임 검색 다시 수행
var rightFrame = parent.frames['rightFrame'];
if(rightFrame.fn_searchPart) {
rightFrame.fn_searchPart();
}
$(".partChks", parent.frames['rightFrame'].document).prop("checked", false);
// 오른쪽 프레임 선택 해제 (Tabulator)
if(rightFrame.clearSelection) {
rightFrame.clearSelection();
}
// 부모 창(E-BOM 목록) 새로고침
if(window.opener && window.opener.fn_search) {
window.opener.fn_search();
}
}
}
, error: function(jqxhr, status, error){
@@ -329,42 +318,8 @@ function fn_changeRelatePartInfo(objId,rightObjId,leftObjId,leftPartNoQty,leftPa
<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}" />
<!--
<br><br><br><br><br>
<br><br><br><br><br><br><br>
-->
<table class="pmsPopupForm" id="changeTable">
<colgroup>
<!--
<col width="15%">
-->
<col width="*">
</colgroup>
<tr>
<td class="input_title" >
<label for="">설변구분</label>
</td>
</tr>
<tr>
<td class="input_sub_title" colspan="">
<select name="CHANGE_TYPE" id="CHANGE_TYPE" class="select2" type="select" required reqTitle="설변구분"><option value="">선택</option>${code_map.CHANGE_TYPE}</select>
</td>
</tr>
<tr>
<td class="input_title">
<label for="">설변사유</label>
</td>
</tr>
<tr>
<td class="input_sub_title">
<select name="CHANGE_OPTION" id="CHANGE_OPTION" class="select2" type="select" required reqTitle="설변사유"><option value="">선택</option>${code_map.CHANGE_OPTION}</select>
</td>
</tr>
</table>
<br>
<div id="structurePopupBtnW" style="padding-top:0px;">
<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;">

View File

@@ -1,8 +1,8 @@
<%
java.util.Map map = (java.util.HashMap)request.getAttribute("info");
%>
<frameset rows="6%, 88%, 6%;" border="0" noresize>
<frame src="/partMng/structureHeaderPopup.do">
<frameset rows="100px, *, 50px" border="0" noresize>
<frame src="/partMng/structureHeaderPopup.do?objId=<%=com.pms.common.utils.CommonUtils.checkNull(map.get("OBJID"))%>">
<frame src="/partMng/structureBottomPopupFS.do?objId=<%=com.pms.common.utils.CommonUtils.checkNull(map.get("OBJID"))%>">
<frame src="/partMng/structureBtnAreaPopup.do">
</frameset><noframes></noframes>

View File

@@ -2,30 +2,13 @@
<%@ page import="com.pms.common.utils.*"%>
<%@ page import="java.util.*" %>
<%@include file= "/init.jsp" %>
<%
ArrayList TREELIST = new ArrayList();
TREELIST = (ArrayList)request.getAttribute("tree");
int z = 0;
int Maxlevel = 0;
//int[] levelarr;
int[] levelarr = new int[TREELIST.size()];
//if(TREELIST != null){
//levelarr = new int[TREELIST.size()];
for(int i=0; i< TREELIST.size(); i++){
HashMap treemap = (HashMap)TREELIST.get(i);
int level = Integer.parseInt(CommonUtils.checkNull(treemap.get("LEVEL")));
// System.out.println("level : "+level);
levelarr[i] = level;
}
if(null != TREELIST && 0 < TREELIST.size()){
HashMap MaxlevelMap = (HashMap)TREELIST.get(0);
Maxlevel = Integer.parseInt(CommonUtils.checkNull(MaxlevelMap.get("MAX_LEVEL")));
}
//}
%>
<!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;
@@ -33,172 +16,348 @@ int[] levelarr = new int[TREELIST.size()];
}
#structureTableWrap1 {
top: 56px;
width: 99%;
}
#structureName {
margin-bottom: 10px;
font-weight: bold;
}
#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; }
/* 파일 아이콘 스타일은 basic.css에서 관리 */
</style>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title><%=Constants.SYSTEM_NAME%></title>
<script>
var _tabulGrid;
var selectedRowData = null;
$(function(){
if('${param.readonly}' == 'readonly'){
$("#structureName2").hide();
}
$(".qty").keyup(function(e){
var key = e.keyCode;
if(key == 13){
var leftObjId = $(this).attr("data-CHILD_OBJID");
var leftQtyParObjId = $(this).attr("data-PART_OBJID");
var leftPartLastObjId = $(this).attr("data-LAST_PART_OBJID");
var leftPartNo = $(this).attr("data-PART_NO");
var qty = $(this).val();
/* var isNumeric = $.isNumeric(qty);
if(!isNumeric){
Swal.fire("숫자만 입력 가능합니다. 수량을 1개로 조정합니다.");
$(this).val(1);
fn_saveQty(leftObjId, 1);
return;
} */
fn_saveQty(leftObjId, qty, leftPartNo, leftQtyParObjId, leftPartLastObjId);
}
});
$("#btnExcel").click(function() {
fn_excel();
});
//첨부팝업
$(".File").click(function(){
var popup_width = 800;
var popup_height = 335;
var objId = $(this).attr("data-OBJID");
var docType =$(this).attr("data-docType");
var docTypeName = $(this).attr("data-docTypeName");
var params = "?targetObjId="+objId+"&docType="+docType+"&docTypeName="+docTypeName;
var url = "/projectConcept/FileRegistPopup.do"+params;
fn_centerPopup(popup_width, popup_height, url);
// 도면 업로드 버튼 클릭
$("#btnDrawingUpload").click(function() {
$("#drawingFiles").click();
});
// 파일 선택 이벤트
$("#drawingFiles").change(function() {
fn_uploadDrawingFiles(this.files);
});
$(".seq").keyup(function(e){
var key = e.keyCode;
if(key == 13){
var leftObjId = $(this).attr("data-CHILD_OBJID");
var leftPartNo = $(this).attr("data-PART_NO");
var seq = $(this).val();
var partNo = $(this).attr("data-PART_NO");
var isNumeric = $.isNumeric(seq);
if(!isNumeric){
Swal.fire("숫자만 입력 가능합니다.");
$(this).val(1);
fn_saveSeq(leftObjId, 1,partNo);
return;
// Tabulator 초기화
fn_initGrid();
});
// Tabulator 그리드 초기화
function fn_initGrid() {
var maxLevel = ${empty MAXLEV ? 1 : MAXLEV};
// 컬럼 정의
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 + '" ' +
'data-OBJID="' + rowData.OBJID + '" ' +
'data-PART_NO="' + rowData.PART_NO + '" ' +
'data-PARENT_PART_NO="' + rowData.PARENT_PART_NO + '" ' +
'data-PART_NO_QTY="' + rowData.LAST_PART_OBJID + '" ' +
'data-PARENT_PARTS="' + rowData.PARENT_PARTS + '" ' +
'data-LAST_PART_OBJID="' + rowData.LAST_PART_OBJID + '" ' +
'data-PARENT_OBJID="' + rowData.PARENT_OBJID + '" ' +
'data-PART_OBJID="' + rowData.PART_OBJID + '" ' +
'data-BOM_LAST_PART_OBJID="' + rowData.BOM_LAST_PART_OBJID + '">';
},
cellClick: function(e, cell) {
var radio = $(e.target);
if(radio.is(':radio')) {
selectedRowData = cell.getData();
// 다른 라디오 버튼 해제
$('input[name=checkedPartNo]').not(radio).prop('checked', false);
}
}
fn_saveSeq(leftObjId, seq,partNo);
}
];
// 수준 컬럼 그룹 (헤더는 하나, 서브 컬럼은 여러개)
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',
formatter: function(cell) {
var rowData = cell.getData();
return '<a href="#" onclick="openPartMngPopup(\'' +
rowData.PART_OBJID + '\',\'' +
rowData.LAST_PART_OBJID + '\',\'' +
rowData.CHILD_OBJID + '\',\'' +
rowData.BOM_LAST_PART_OBJID + '\');">' +
rowData.PART_NO + '</a>';
}
},
{
headerHozAlign: 'center',
hozAlign: 'left',
width: 200,
title: '품명',
field: 'PART_NAME'
},
{
headerHozAlign: 'center',
hozAlign: 'center',
width: 60,
title: '수량',
field: 'QTY_TEMP',
editor: function(cell, onRendered, success, cancel) {
var rowData = cell.getData();
if(rowData.STATUS === 'adding') {
var input = $('<input type="text" style="width:100%; text-align:center;" maxlength="3"/>');
input.val(cell.getValue() || '0');
input.on('keydown', function(e) {
if(e.keyCode === 13) {
success(input.val());
} else if(e.keyCode === 27) {
cancel();
}
});
input.on('blur', function() {
success(input.val());
});
return input[0];
}
return false;
},
cellEdited: function(cell) {
var rowData = cell.getData();
fn_saveQty(rowData.CHILD_OBJID, cell.getValue(), rowData.PART_OBJID);
}
},
{
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 isEmpty = cell.getValue() == 0;
return '<a href="#" class="File file_' + (isEmpty ? 'empty_' : '') + 'icon" ' +
'data-OBJID="' + rowData.LAST_PART_OBJID + '" ' +
'data-docType="3D_CAD" ' +
'data-docTypeName="3D CAD 첨부파일"></a>';
}
},
{
headerHozAlign: 'center',
hozAlign: 'center',
width: 60,
title: '2D',
field: 'CU02_CNT',
formatter: function(cell) {
var rowData = cell.getData();
var isEmpty = cell.getValue() == 0;
return '<a href="#" class="File file_' + (isEmpty ? 'empty_' : '') + 'icon" ' +
'data-OBJID="' + rowData.LAST_PART_OBJID + '" ' +
'data-docType="2D_DRAWING_CAD" ' +
'data-docTypeName="2D(Drawing) CAD 첨부파일"></a>';
}
},
{
headerHozAlign: 'center',
hozAlign: 'center',
width: 60,
title: 'PDF',
field: 'CU03_CNT',
formatter: function(cell) {
var rowData = cell.getData();
var isEmpty = cell.getValue() == 0;
return '<a href="#" class="File file_' + (isEmpty ? 'empty_' : '') + 'icon" ' +
'data-OBJID="' + rowData.LAST_PART_OBJID + '" ' +
'data-docType="2D_PDF_CAD" ' +
'data-docTypeName="2D(PDF) CAD 첨부파일"></a>';
}
},
{
headerHozAlign: 'center',
hozAlign: 'left',
width: 100,
title: '재료',
field: 'MATERIAL'
},
{
headerHozAlign: 'center',
hozAlign: 'left',
width: 120,
title: '열처리경도',
field: 'HEAT_TREATMENT_HARDNESS'
},
{
headerHozAlign: 'center',
hozAlign: 'left',
width: 120,
title: '열처리방법',
field: 'HEAT_TREATMENT_METHOD'
},
{
headerHozAlign: 'center',
hozAlign: 'left',
width: 120,
title: '표면처리',
field: 'SURFACE_TREATMENT'
},
{
headerHozAlign: 'center',
hozAlign: 'left',
width: 120,
title: '공급업체',
field: 'MAKER'
},
{
headerHozAlign: 'center',
hozAlign: 'center',
width: 120,
title: '범주 이름',
field: 'PART_TYPE_TITLE'
}
);
_tabulGrid = new Tabulator("#structureGrid", {
layout: "fitColumns",
height: "650px",
pagination: false,
columns: columns,
rowFormatter: function(row) {
var data = row.getData();
$(row.getElement()).addClass('level-' + data.LEVEL);
},
ajaxURL: "/partMng/getStructureTreeJson.do",
ajaxParams: {
objId: "${info.OBJID}",
bomReportObjId: "${info.OBJID}",
search_type: "working" // adding 상태 포함해서 조회
},
ajaxResponse: function(url, params, response) {
// 서버 응답 데이터 가공
var processedData = [];
if(response && response.length > 0) {
var maxLevel = response[0].MAX_LEVEL || 1;
response.forEach(function(item) {
// Level 표시를 위한 동적 필드 생성
for(var i = 1; i <= maxLevel; i++) {
item['LEVEL_' + i] = (item.LEVEL == i) ? '*' : '';
}
processedData.push(item);
});
}
return processedData;
}
});
/*
$(".qty").blur(function(){
// 파일 아이콘 클릭 이벤트 (동적으로 생성된 요소)
$(document).on('click', '.File', function(e) {
e.preventDefault();
var popup_width = 800;
var popup_height = 335;
var objId = $(this).attr("data-OBJID");
var qty = $(this).val();
fn_saveQty(objId, qty);
var docType = $(this).attr("data-docType");
var docTypeName = $(this).attr("data-docTypeName");
var params = "?targetObjId=" + objId + "&docType=" + docType + "&docTypeName=" + docTypeName;
var url = "/projectConcept/FileRegistPopup.do" + params;
fn_centerPopup(popup_width, popup_height, url);
});
*/
$(document).ready(function(){
$(".dataTr").each(function(i){
var lev = $(this).attr("data-LEVEL");
if(lev == 1){
$(this).css("background-color", "#fde9d9");
}else if(lev == 2){
$(this).css("background-color", "#daeef3");
}else if(lev == 3){
$(this).css("background-color", "#e4dfec");
}else if(lev == 4){
$(this).css("background-color", "#ebf1de");
}else if(lev == 5){
$(this).css("background-color", "#f2f2f2");
}else if(lev == 6){
$(this).css("background-color", "#f2dcdb");
}else if(lev == 7){
$(this).css("background-color", "#eeece1");
}else if(lev == 8){
$(this).css("background-color", "#dce6f1");
}else if(lev == 9){
$(this).css("background-color", "#FFFFEB");
}else if(lev == 10){
$(this).css("background-color", "#ffffff");
}
});
});
});
}
function fn_saveQty(leftObjId,qty, leftQtyParObjId){
// 수량 저장
function fn_saveQty(leftObjId, qty, leftQtyParObjId){
$.ajax({
url: "/partMng/structureQtySave.do",
method: 'post',
//data: {"CHILD_OBJID":leftObjId,"QTY":qty}, //231211 아래로 변경(설변)
data: {"BOM_REPORT_OBJID":"${info.OBJID}","CHILD_OBJID":leftObjId,"QTY_TEMP":qty, "OBJID":leftQtyParObjId},
data: {
"BOM_REPORT_OBJID": "${info.OBJID}",
"CHILD_OBJID": leftObjId,
"QTY_TEMP": qty,
"OBJID": leftQtyParObjId
},
dataType: 'json',
success: function(data) {
if(data.result){
Swal.fire('저장하였습니다.');
_tabulGrid.replaceData(); // 그리드 새로고침
// 부모 창(E-BOM 목록) 새로고침
if(window.opener && window.opener.fn_search) {
window.opener.fn_search();
}
}
}
, error: function(jqxhr, status, error){
Swal.fire(jqxhr.statusText + ", " + status + ", " + error);
Swal.fire(jqxhr.status);
Swal.fire(jqxhr.responseText);
},
error: function(jqxhr, status, error){
Swal.fire('저장 중 오류가 발생했습니다.');
}
});
}
function fn_saveSeq(leftObjId,seq,partNo){
var foucs = "seq_"+partNo;
$.ajax({
url: "/partMng/structureSeqSave.do",
method: 'post',
data: {"BOM_REPORT_OBJID":"${info.OBJID}","CHILD_OBJID":leftObjId,"SEQ":seq},
dataType: 'json',
async : false,
success: function(data) {
Swal.fire('변경되었습니다');
var url = window.location.href;
url = url + "&foucs="+foucs;
location.href = url;
//Swal.fire("#"+seq+"_"+partNo);
//$("#"+seq+"_"+partNo).focus();
//parent.frames['leftFrame'].reload();
}
, error: function(jqxhr, status, error){
Swal.fire(jqxhr.statusText + ", " + status + ", " + error);
Swal.fire(jqxhr.status);
Swal.fire(jqxhr.responseText);
}
});
}
// Part 상세 팝업
function openPartMngPopup(objId, lastPartObjid, childObjid, BOM_LAST_PART_OBJID){
var popup_width = 800;
var popup_height = 500;
var popup_height = 500;
var hiddenForm = document.hiddenForm;
var url = "/partMng/partMngFormPopUp.do";
@@ -213,20 +372,201 @@ function openPartMngPopup(objId, lastPartObjid, childObjid, BOM_LAST_PART_OBJID)
hiddenForm.action = url;
hiddenForm.OBJID.value = fnc_checkNull(BOM_LAST_PART_OBJID, objId);
if('working' == '${param.actionType}')
hiddenForm.OBJID.value = lastPartObjid; //231211 최신 파트로 조회
hiddenForm.OBJID.value = lastPartObjid;
hiddenForm.LAST_PART_OBJID.value = lastPartObjid;
hiddenForm.CHILD_OBJID.value = childObjid;
hiddenForm.target = target;
hiddenForm.submit();
}
// Excel 다운로드
function fn_excel() {
document.form1.actionType.value = "excel";
var form = document.form1;
form.action="/partMng/structurePopupLeft.do";
form.action = "/partMng/structurePopupLeft.do";
form.submit();
}
// 선택된 행 데이터 가져오기 (Center 프레임에서 사용)
function getSelectedRowData() {
return selectedRowData;
}
// 필터 적용 함수
function fn_applyFilter() {
var partNo = $("#filterPartNo").val().trim();
var partName = $("#filterPartName").val().trim();
// 빈 값이 아닌 필터만 추가
var filters = [];
if(partNo) {
filters.push({field: "PART_NO", type: "like", value: partNo});
}
if(partName) {
filters.push({field: "PART_NAME", type: "like", value: partName});
}
// 필터가 있으면 적용, 없으면 전체 표시
if(filters.length > 0) {
_tabulGrid.setFilter(filters);
} else {
_tabulGrid.clearFilter();
}
}
// 필터 초기화 함수
function fn_resetFilter() {
$("#filterPartNo").val("");
$("#filterPartName").val("");
_tabulGrid.clearFilter();
}
// 도면 업로드 처리 함수
function fn_uploadDrawingFiles(files) {
if(!files || files.length === 0) {
Swal.fire('파일을 선택해주세요.');
return;
}
// 품번 목록 조회 (현재 BOM의 모든 품번)
var bomObjId = "${info.OBJID}";
if(!bomObjId) {
Swal.fire('BOM 정보를 찾을 수 없습니다.');
return;
}
// 파일 분류 및 처리
var filesByType = {
'3D': [], // stp 파일
'2D': [], // dwg 파일
'PDF': [] // pdf 파일
};
// 파일 확장자 확인 및 분류
for(var i = 0; i < files.length; i++) {
var file = files[i];
var fileName = file.name;
var lastDotIndex = fileName.lastIndexOf('.');
if(lastDotIndex === -1) {
continue; // 확장자가 없는 파일은 스킵
}
var ext = fileName.substring(lastDotIndex + 1).toLowerCase();
console.log('파일명: ' + fileName + ', 확장자: ' + ext); // 디버깅용
if(ext === 'stp' || ext === 'step') {
filesByType['3D'].push(file);
} else if(ext === 'dwg') {
filesByType['2D'].push(file);
} else if(ext === 'pdf') {
filesByType['PDF'].push(file);
}
}
// 업로드할 파일이 있는지 확인
var totalFiles = filesByType['3D'].length + filesByType['2D'].length + filesByType['PDF'].length;
if(totalFiles === 0) {
Swal.fire('업로드 가능한 파일 형식이 없습니다. (stp, dwg, pdf만 가능)');
return;
}
// 확인 메시지
var msg = '총 ' + totalFiles + '개의 파일을 업로드하시겠습니까?\n';
msg += '- 3D (STP): ' + filesByType['3D'].length + '개\n';
msg += '- 2D (DWG): ' + filesByType['2D'].length + '개\n';
msg += '- PDF: ' + filesByType['PDF'].length + '개';
Swal.fire({
title: '도면 다증 업로드',
text: msg,
icon: 'question',
showCancelButton: true,
confirmButtonText: '업로드',
cancelButtonText: '취소'
}).then(function(result) {
if(result.isConfirmed) {
fn_processDrawingUpload(bomObjId, filesByType);
}
});
}
// 실제 업로드 처리
function fn_processDrawingUpload(bomObjId, filesByType) {
// FormData 생성
var formData = new FormData();
formData.append('bomObjId', bomObjId);
// 모든 파일을 files 이름으로 추가 (MultipartRequest가 처리)
var allFiles = filesByType['3D'].concat(filesByType['2D']).concat(filesByType['PDF']);
for(var i = 0; i < allFiles.length; i++) {
formData.append('files', allFiles[i]);
}
// 로딩 표시 (구버전 SweetAlert2)
Swal.fire({
title: '업로드 중...',
text: '파일을 업로드하는 중입니다. 잠시만 기다려주세요.',
allowOutsideClick: false,
allowEscapeKey: false,
allowEnterKey: false,
showConfirmButton: false,
onOpen: function() {
Swal.showLoading();
}
});
// AJAX 업로드
$.ajax({
url: '/partMng/uploadDrawingFiles.do',
type: 'POST',
data: formData,
processData: false,
contentType: false,
success: function(response) {
Swal.close();
if(response.result === 'success') {
var successMsg = '도면 업로드가 완료되었습니다.\n\n';
successMsg += '- 성공: ' + response.successCount + '개\n';
if(response.failCount > 0) {
successMsg += '- 실패: ' + response.failCount + '개\n';
}
if(response.notFoundCount > 0) {
successMsg += '- 품번 미존재: ' + response.notFoundCount + '개\n';
}
Swal.fire({
title: '업로드 완료',
text: successMsg,
icon: response.failCount > 0 ? 'warning' : 'success'
}).then(function() {
// 그리드 새로고침
_tabulGrid.replaceData();
// 파일 input 초기화
$("#drawingFiles").val('');
});
} else {
Swal.fire({
title: '업로드 실패',
text: response.message || '도면 업로드 중 오류가 발생했습니다.',
icon: 'error'
});
}
},
error: function(xhr, status, error) {
Swal.close();
console.error('Upload error:', error);
console.error('Response:', xhr.responseText);
Swal.fire({
title: '업로드 실패',
text: '서버 오류가 발생했습니다: ' + error,
icon: 'error'
});
}
});
}
</script>
</head>
<body class="backcolor">
@@ -240,134 +580,27 @@ function fn_excel() {
<input type="hidden" name="objid" id="objid" value="${info.OBJID}" />
<input type="hidden" name="objId" id="objId" value="${info.OBJID}" />
<input type="hidden" name="actionType" value="" />
<div id="structureTableWrap1" style="width: 99%;">
<div id="structureName">(${info.CUSTOMER_NAME}_${info.CUSTOMER_PROJECT_NAME}_${info.UNIT_NAME})_${info.REV}<input type="button" value="Excel Download" class="plm_btns structure_btn" id="btnExcel" style="float:right;"></div>
<div id="structureName2"><font size="2px">※수량 변경 후, 엔터치시면 저장됩니다.(신규 추가 파트만 가능)</font></div>
<div class="plm_scroll_table" style="height:90%;overflow-y:scroll;">
<table id="structurePopupTableHead" class="plm_table">
<colgroup>
<% if(Maxlevel == 0){%>
<col width="25px" />
<%}else{%>
<col width="25px" />
<%}
for(int i=0; i< Maxlevel; i++){%>
<col width="25px" />
<%}%>
<col width="130px" />
<col width="160px" />
<col width="50px" />
<col width="30px" /> <!-- 3D -->
<col width="30px" /> <!-- 2D -->
<col width="30px" /> <!-- 2D PDF -->
<col width="70px" /> <!-- Material -->
<col width="190px" /> <!-- SPEC. -->
<col width="90px" /> <!-- MAKER. -->
<col width="70px" /> <!-- Revision -->
<col width="75px" /> <!-- EO no -->
<col width="75px" /> <!-- EO Date -->
<col width="70px" /> <!-- 부품 유향 -->
<!-- <col width="15%" /> -->
</colgroup>
<tr class="plm_thead">
<td colspan="${MAXLEV + 1}">Level</td>
<td>품번</td>
<td>품명</td>
<td>수량</td>
<td>3D</td>
<td>2D</td>
<td>PDF</td>
<td>재질</td>
<td>사양(규격)</td>
<td>MAKER</td>
<td>Revision</td>
<td>EO No</td>
<td>EO Date</td>
<td>PART 구분</td>
<!-- <td>순번</td> -->
</tr>
</table>
<table id="structurePopupTable2" class="plm_table">
<colgroup>
<% if(Maxlevel == 0){%>
<col width="25px" />
<%}else{%>
<col width="25px" />
<%}
for(int i=0; i< Maxlevel; i++){%>
<col width="25px" />
<%}%>
<col width="130px" />
<col width="160px" />
<col width="50px" />
<col width="30px" /> <!-- 3D -->
<col width="30px" /> <!-- 2D -->
<col width="30px" /> <!-- 2D PDF -->
<col width="70px" /> <!-- Material -->
<col width="190px" /> <!-- SPEC. -->
<col width="90px" /> <!-- MAKER. -->
<col width="70px" /> <!-- Revision -->
<col width="75px" /> <!-- EO no -->
<col width="75px" /> <!-- EO Date -->
<col width="70px" /> <!-- 부품 유향 -->
<!-- <col width="15%" /> -->
</colgroup>
<c:choose>
<c:when test="${!empty tree}">
<c:forEach var="item" items="${tree}" varStatus="status">
<tr class="dataTr" data-LEVEL="${item.LEVEL}">
<td><input type="radio" name="checkedPartNo" value="${item.CHILD_OBJID}" data-OBJID="${item.OBJID}" data-PART_NO="${item.PART_NO}"
data-PARENT_PART_NO="${item.PARENT_PART_NO}" data-PART_NO_QTY="${item.LAST_PART_OBJID}"
data-PARENT_PARTS="${item.PARENT_PARTS}" data-LAST_PART_OBJID="${item.LAST_PART_OBJID}"
data-PARENT_OBJID="${item.PARENT_OBJID}" data-PART_OBJID="${item.PART_OBJID}"
data-BOM_LAST_PART_OBJID="${item.BOM_LAST_PART_OBJID}"
>
</td>
<%
for(int i=0; i< Maxlevel; i++){
if(levelarr[z] == (i+1)){%>
<td>*</td>
<% }else{ %>
<td> </td>
<% }
}
z++;
%>
<td style="text-align:left !important;" id="lpadpartno"><pre><a href="#" onclick="openPartMngPopup('${item.PART_OBJID}','${item.LAST_PART_OBJID}','${item.CHILD_OBJID}','${item.BOM_LAST_PART_OBJID}');">${item.PART_NO}</a></pre></td>
<td class="align_l">${item.PART_NAME}</td>
<c:if test="${item.STATUS ne 'adding'}">
<td class="align_c">${item.QTY_TEMP}</td>
</c:if>
<c:if test="${item.STATUS eq 'adding'}">
<td class="align_c"><input type="text" name="qty_${item.PART_NO}" id="qty_${item.PART_NO}" class="qty" style="width:40px; text-align:center;" maxlength="3" value="${empty item.QTY_TEMP?'0':item.QTY_TEMP}" data-CHILD_OBJID="${item.CHILD_OBJID}" data-PART_NO="${item.PART_NO}" data-PARENT_PART_NO="${item.PARENT_PART_NO}" ></td>
</c:if>
<td><a href="#" class="File file_${item.CU01_CNT eq 0?'empty_':''}icon" data-OBJID="${item.LAST_PART_OBJID}" data-docType="3D_CAD" data-docTypeName="3D CAD 첨부파일"></a></td>
<td><a href="#" class="File file_${item.CU02_CNT eq 0?'empty_':''}icon" data-OBJID="${item.LAST_PART_OBJID}" data-docType="2D_DRAWING_CAD" data-docTypeName="2D(Drawing) CAD 첨부파일"></a></td>
<td><a href="#" class="File file_${item.CU03_CNT eq 0?'empty_':''}icon" data-OBJID="${item.LAST_PART_OBJID}" data-docType="2D_PDF_CAD" data-docTypeName="2D(PDF) CAD 첨부파일"></a></td>
<td title="${item.MATERIAL}" class="align_l" style="text-align: left; padding-left: 10px;">${item.MATERIAL}</td><!-- 재질 -->
<td title="${item.SPEC}" class="align_l" style="text-align: left; padding-left: 10px;">${item.SPEC}</td><!-- SPEC -->
<td title="${item.MAKER}" class="align_l" style="text-align: left; padding-left: 10px;">${item.MAKER}</td><!-- SPEC -->
<td title="${item.REVISION}" class="align_l" style="text-align: left; padding-left: 10px;">${item.REVISION}</td><!-- REVISION -->
<td title="${item.EO_NO}" class="align_c">${item.EO_NO}</td><!-- REVISION -->
<td title="${item.EO_DATE}" class="align_c">${item.EO_DATE}</td><!-- REVISION -->
<td title="${item.PART_TYPE_TITLE}" class="align_c">${item.PART_TYPE_TITLE}</td><!-- SIZE -->
</tr>
</c:forEach>
</c:when>
<c:otherwise>
<tr>
<td colspan="5" align="center">등록된 구조정보가 없습니다.</td>
</tr>
</c:otherwise>
</c:choose>
</table>
</div>
</div>
<!-- 필터 값을 저장하기 위한 hidden 필드 (top frame에서 값 전달) -->
<input type="hidden" id="filterPartNo" />
<input type="hidden" id="filterPartName" />
<div id="structureTableWrap1">
<div id="structureName">
<span style="font-weight: bold; color: #333;">
<c:choose>
<c:when test="${not empty info.PART_NO}">
${info.PART_NO}
</c:when>
<c:otherwise>
(${info.CUSTOMER_NAME}_${info.CUSTOMER_PROJECT_NAME}_${info.UNIT_NAME})_${info.REVISION}
</c:otherwise>
</c:choose>
</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,.pdf">
</div>
<div id="structureGrid"></div>
</div>
</form>
</body>
</html>
</html>

View File

@@ -9,14 +9,20 @@ java.text.SimpleDateFormat frm= new java.text.SimpleDateFormat ("yyyy_MM_dd_HH_m
Calendar cal = Calendar.getInstance();
String todayKor = frm.format(cal.getTime());
ArrayList list = (ArrayList)request.getAttribute("tree");
Map sumPriceMap = (Map)request.getAttribute("info");
// 품번 정보가 있으면 파일명에 사용
String partNo = CommonUtils.checkNull(sumPriceMap.get("PART_NO"));
String excelName = "구조등록";
String encodeName = excelName+todayKor+".xls";
if(!partNo.isEmpty()) {
excelName = partNo;
}
String encodeName = excelName+"_"+todayKor+".xls";
String fileName = java.net.URLEncoder.encode(encodeName,"UTF-8");
response.setHeader("Content-Disposition", "attachment;filename="+fileName+"");
response.setHeader("Content-Description", "JSP Generated Data");
ArrayList list = (ArrayList)request.getAttribute("tree");
Map sumPriceMap = (Map)request.getAttribute("info");
int z = 0;
int Maxlevel = 0;
@@ -50,7 +56,19 @@ if(null != list && 0 < list.size()){
<section class="min_part_search">
<div class="pdm_menu_name">
<h2>
<span>구조등록(${info.CUSTOMER_NAME}_${info.CUSTOMER_PROJECT_NAME}_${info.UNIT_NAME})</span>
<span>구조등록
<%
if(!partNo.isEmpty()) {
%>
(<%=partNo%>)
<%
} else {
%>
(${info.CUSTOMER_NAME}_${info.CUSTOMER_PROJECT_NAME}_${info.UNIT_NAME})_${info.REVISION}
<%
}
%>
</span>
</h2>
</div>
<div class="contents_page_basic_margin">

View File

@@ -8,6 +8,8 @@
<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;
@@ -15,6 +17,31 @@
#structureTableWrap2 .structure_btn {
top: 25px;
}
/* Tabulator 커스텀 스타일 */
.tabulator {
border: 1px solid #ddd;
}
.tabulator .tabulator-header {
background-color: #f0f0f0;
border-bottom: 2px solid #ddd;
}
.tabulator .tabulator-header .tabulator-col {
background-color: #f0f0f0;
border-right: 1px solid #ddd;
}
.tabulator .tabulator-tableHolder .tabulator-table {
background-color: #fff;
}
.tabulator .tabulator-row {
border-bottom: 1px solid #eee;
}
.tabulator .tabulator-row:hover {
background-color: #f5f5f5;
}
.tabulator .tabulator-cell {
padding: 8px;
border-right: 1px solid #eee;
}
</style>
<script>
$(function(){
@@ -72,49 +99,138 @@ $(function(){
fnc_productUPGNEWList("","${resultMap.PRODUCT_MGMT_SPEC}","search_product_mgmt_upg", "");
$('.select2').select2();
// Tabulator 초기화
fn_initRightGrid();
// 초기 로드 제거 - 검색 버튼 클릭 시에만 데이터 로드
// 사용자가 검색 조건 입력 후 조회하도록 유도
});
function fn_searchPart(){
$("#structurePopupTable2").empty();
var _rightGrid;
function fn_initRightGrid(){
var columns = [
{
formatter: "rowSelection",
titleFormatter: "rowSelection",
headerHozAlign: 'center',
hozAlign: 'center',
width: 50,
headerSort: false,
cellClick: function(e, cell){
e.stopPropagation();
}
},
{
headerHozAlign: 'center',
hozAlign: 'left',
width: 150,
title: '품번',
field: 'PART_NO',
formatter: function(cell) {
var rowData = cell.getData();
return '<a href="#" onclick="openPartMngPopup(\'' + rowData.OBJID + '\');">' + rowData.PART_NO + '</a>';
}
},
{
headerHozAlign: 'center',
hozAlign: 'left',
width: 180,
title: '품명',
field: 'PART_NAME'
},
{
headerHozAlign: 'center',
hozAlign: 'left',
width: 200,
title: '규격',
field: 'SPEC'
},
{
headerHozAlign: 'center',
hozAlign: 'left',
width: 120,
title: 'MAKER',
field: 'MAKER'
}
];
_rightGrid = new Tabulator("#rightPartGrid", {
layout: "fitColumns",
height: "600px",
pagination: false,
columns: columns,
placeholder: "검색 버튼을 클릭하여 품목을 조회하세요",
selectable: true, // 다중 선택 가능
selectableRangeMode: "click" // 클릭으로 선택
});
}
function fn_searchPart(){
// 전체 품목 데이터 조회 (검색 조건 적용)
$.ajax({
url: "/partMng/getPartMngList_ajax.do",
method: 'post',
data: $("#form1").serialize(),
dataType: 'json',
success: function(data) {
var trHtml = "";
trHtml += " <colgroup>";
trHtml += " <col width='5%' />";
trHtml += " <col width='30%' />";
trHtml += " <col width='30%' />";
trHtml += " <col width='45%' />";
trHtml += " <col width='15%' />";
trHtml += " </colgroup>";
if(data.length > 0){
$.each(data, function(i){
trHtml += " <tr>";
//trHtml += " <input type=\"checkbox\" name=\"partChks\" class=\"partChks\" value=\""+data[i].OBJID+"\" data-PART_TYPE='"+data[i].PART_TYPE+"' data-PART_NO='"+data[i].PART_NO+"' ></td>";
trHtml += " <td><input type=\"checkbox\" name=\"partChks\" class=\"partChks\" value=\""+data[i].OBJID+"\" data-PART_TYPE='"+data[i].PART_TYPE+"' data-PART_NO='"+data[i].PART_NO+"' data-PART_REV='"+data[i].REVISION+"'></td>";
trHtml += " <td class='align_l' title='"+data[i].PART_NO+"'><a href='#' onclick=\"openPartMngPopup('"+data[i].OBJID+"');\">"+data[i].PART_NO+"</a></td>";
trHtml += " <td class='align_l' title='"+data[i].PART_NAME+"'>"+data[i].PART_NAME+"</td>";
trHtml += " <td class='align_l' title='"+data[i].SPEC+"'>"+data[i].SPEC+"</td>";
trHtml += " <td class='align_l' title='"+data[i].MAKER+"'>"+data[i].MAKER+"</td>";
trHtml += " </tr>";
});
}else{
trHtml += " <tr>";
trHtml += " <td colspan=\"5\" align=\"center\">조회된 Part가 없습니다.</td>";
trHtml += " </tr>";
}
$("#structurePopupTable2").append(trHtml);
},
error: function(jqxhr, status, error){
}
});
url: "/partMng/getPartMngList_ajax.do",
type: "POST",
data: $("#form1").serialize(),
dataType: "json",
beforeSend: function(){
// 로딩 스피너 표시
Swal.fire({
title: '조회 중...',
html: '품목 데이터를 불러오는 중입니다.',
allowOutsideClick: false,
didOpen: () => {
Swal.showLoading();
}
});
if(_rightGrid){
_rightGrid.clearData();
}
},
success: function(response) {
// 로딩 스피너 닫기
Swal.close();
console.log('조회 완료:', response.data ? response.data.length + '건' : '0건');
// Tabulator에 데이터 설정
if(_rightGrid){
_rightGrid.setData(response.data || []);
}
// 결과 메시지
if(!response.data || response.data.length === 0){
Swal.fire({
icon: 'info',
title: '조회 결과 없음',
text: '검색 조건에 맞는 품목이 없습니다.'
});
} else {
// 성공 토스트 메시지
Swal.fire({
toast: true,
position: 'top-end',
icon: 'success',
title: response.data.length + '건 조회 완료',
showConfirmButton: false,
timer: 2000
});
}
},
error: function(jqxhr, status, error){
// 로딩 스피너 닫기
Swal.close();
Swal.fire({
icon: 'error',
title: '조회 실패',
text: '데이터 조회 중 오류가 발생했습니다.'
});
console.error(error);
}
});
}
/**
@@ -139,6 +255,19 @@ function openPartMngPopup(objId){
hiddenForm.submit();
}
// 선택된 행 데이터 반환 (Center 프레임에서 호출)
function getSelectedRows() {
if(!_rightGrid) return [];
return _rightGrid.getSelectedRows();
}
// 선택 해제 함수
function clearSelection() {
if(_rightGrid) {
_rightGrid.deselectRow();
}
}
</script>
</head>
<body class="backcolor">
@@ -147,85 +276,45 @@ function openPartMngPopup(objId){
<input type="hidden" name="ACTION_TYPE" id="ACTION_TYPE">
</form>
<form name="form1" id="form1" action="" method="post">
<input type="hidden" name="bomReportObjId" id="bomReportObjId" value="${param.OBJID}">
<input type="hidden" name="bomReportObjId" id="bomReportObjId" value="${param.objId}">
<input type="hidden" name="is_last" id="is_last" value="1">
<input type="hidden" name="searchTargetStatus" id="searchTargetStatus" value="${info.STATUS}">
<div id="structureName2"></div>
<div id="structureTableWrap2" style="">
<div class="searchIdName">
<!-- 검색 필터 영역 -->
<div id="plmSearchZon">
<table>
<!-- <tr>
<td class="input_title"><label for="">기종명</label></td>
<td class="input_sub_title" >
<select name="search_product_mgmt_objid" id="search_product_mgmt_objid" class="select2"></select>
</td>
<td class="input_title"><label for="">UPG No</label></td>
<td>
<select name="search_product_mgmt_upg" id="search_product_mgmt_upg" title="UPG" required type="select" class="select2"></select>
</td>
</tr> -->
<tr>
<td class="input_title"><label for="">품번</label></td>
<td class="input_sub_title" style="width:150px;">
<input type="text" name="search_part_no" id="search_part_no" value="" style="width:90%">
<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>
<td class="input_title"><label for="">품명</label></td>
<td>
<input type="text" name="search_part_name" id="search_part_name" value="" style="width:80%">
</td>
<td class="input_sub_title" style="float:right">
</td>
</tr>
<tr>
<td class="input_title"><label for="">규격</label></td>
<td class="input_sub_title" style="width:150px;">
<input type="text" name="search_spec" id="search_spec" value="" style="width:90%">
<td><label for="search_spec">규격</label></td>
<td>
<input type="text" name="search_spec" id="search_spec" value="">
</td>
<td class="label"><label for="search_maker">MAKER</label></td>
<td>
<input type="text" name="search_maker" id="search_maker" value="">
</td>
<td>
<input type="button" value="조회" class="plm_btns" id="btnSearch">
</td>
<td class="input_title"><label for="">MAKER</label></td>
<td>
<input type="text" name="search_maker" id="search_maker" value="" style="width:80%">
</td>
<td class="input_sub_title" style="float:right">
<input type="button" value="조회" class="plm_btns structure_btn" id="btnSearch" >
</td>
</tr>
</table>
</table>
</div>
<div class="plm_table_wrap">
<div style="overflow-y:scroll;">
<table id="structurePopupTableHead" class="plm_table">
<colgroup>
<col width="5%" />
<col width="30%" />
<col width="30%" />
<col width="45%" />
<col width="15%" />
</colgroup>
<tr class="plm_thead">
<td><input type="checkbox" name="" value="" id="allChk" class="checkBox"></td>
<td>품번</td>
<td>품명</td>
<td>규격</td>
<td>MAKER</td>
</tr>
</table>
</div>
<div class="plm_scroll_table" style="height:100%;">
<table id="structurePopupTable2" class="plm_table">
<colgroup>
<col width="5%" />
<col width="30%" />
<col width="30%" />
<col width="40%" />
<col width="20%" />
</colgroup>
<tr class="partListTr">
<td colspan="5" align="center">조회된 부품이 없습니다.</td>
</tr>
</table>
</div>
<div id="rightPartGrid"></div>
</div>
</div>
</form>

View File

@@ -6,15 +6,132 @@
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title><%=Constants.SYSTEM_NAME%></title>
<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, #filterVersion").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) -> parent(main) -> frames[1](bottomFs) -> frames[0](leftFrame)
var leftFrame = parent.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 {
// 프레임 구조: parent(headerFs) -> parent(main) -> frames[1](bottomFs) -> frames[0](leftFrame)
var leftFrame = parent.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" action="" method="post">
<form name="form1" id="form1" action="" method="post">
<div class="plm_menu_name">
<h2>
<span>구조등록</span>
<span>E-BOM 확인/수정</span>
<c:if test="${not empty partNo}">
<span style="font-size: 14px; font-weight: normal; color: #666; margin-left: 20px;">
품번: <strong style="color: #333;">${partNo}</strong>
<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,324 @@
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page import="com.pms.common.utils.*"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ 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>
.ebom-select-container {
padding: 10px;
}
.header-section {
background: #f5f5f5;
padding: 15px;
margin-bottom: 10px;
border: 1px solid #ddd;
display: flex;
justify-content: space-between;
align-items: center;
}
.header-section h3 {
margin: 0;
color: #333;
}
.current-ebom-section {
background: #f9f9f9;
padding: 20px;
margin-bottom: 10px;
border: 1px solid #ddd;
border-radius: 5px;
}
.current-ebom-section h4 {
margin: 0 0 15px 0;
color: #333;
font-size: 16px;
}
.ebom-info-table {
width: 100%;
border-collapse: collapse;
background: white;
margin-bottom: 15px;
}
.ebom-info-table th {
background: #f5f5f5;
padding: 10px;
text-align: left;
border: 1px solid #ddd;
width: 150px;
font-weight: bold;
}
.ebom-info-table td {
padding: 10px;
border: 1px solid #ddd;
}
.ebom-list-section {
margin-bottom: 10px;
height: 400px !important;
}
.ebom-list-section .plm_tab_div {
height: 200px !important;
}
.ebom-list-section #mainGrid {
height: 200px !important;
}
.ebom-preview-section {
padding: 10px;
background: #fafafa;
}
.ebom-preview-section h4 {
margin: 0 0 10px 0;
color: #555;
}
</style>
<script>
// 현재 할당된 E-BOM이 있는지 여부
var hasCurrentEbom = ${not empty currentEbom};
var currentEbomObjid = "${not empty currentEbom ? currentEbom.OBJID : ''}";
$(document).ready(function(){
$('.select2').select2();
// 날짜 선택기 초기화
fnc_datepick("search_fromDate", "search_toDate");
// Enter 키로 검색
$("input").keyup(function(e) {
if (e.keyCode == 13) {
fn_searchEbomList();
}
});
// 조회 버튼
$("#btnSearch").click(function(){
fn_searchEbomList();
});
// 변경 버튼
$("#btnChange").click(function(){
fn_showEbomList();
});
// 초기 상태 설정
if(hasCurrentEbom) {
// 할당된 E-BOM이 있으면 상세보기만 표시
fn_showCurrentEbom();
} else {
// 할당된 E-BOM이 없으면 목록 표시
fn_showEbomList();
}
});
// 현재 할당된 E-BOM 표시
function fn_showCurrentEbom() {
$("#currentEbomSection").show();
$("#ebomListSection").hide();
// 현재 E-BOM 미리보기 로드
$("#ebomPreviewFrame").attr("src", "/partMng/structurePopupLeft.do?readonly=readonly&objId=" + currentEbomObjid);
}
// E-BOM 목록 표시 (변경 모드)
function fn_showEbomList() {
$("#currentEbomSection").hide();
$("#ebomListSection").show();
// 목록 조회
fn_searchEbomList();
}
// 그리드 컬럼 정의
var columns = [
{headerHozAlign: 'center', hozAlign: 'center', width: 160, title: '제품구분', field: 'PRODUCT_NAME'},
{headerHozAlign: 'center', hozAlign: 'left', width: 210, title: '품번', field: 'PART_NO'},
{headerHozAlign: 'center', hozAlign: 'left', title: '품명', field: 'PART_NAME'},
{headerHozAlign: 'center', hozAlign: 'center', width: 130, title: '등록일', field: 'REG_DATE'},
{headerHozAlign: 'center', hozAlign: 'center', width: 110, title: 'Version', field: 'REVISION'},
{headerHozAlign: 'center', hozAlign: 'center', width: 110, title: '상태', field: 'STATUS'}
];
// E-BOM List 조회 (상태='Y'만)
function fn_searchEbomList() {
// 상태='Y' 조건 추가
$("#status").val("Y");
_tabulGrid = fnc_tabul_search(_tabul_layout_fitColumns, _tabulGrid, "/partMng/searchStructureGridList.do", columns, true);
// 행 클릭 이벤트 - 단일 선택만 가능
if(_tabulGrid) {
_tabulGrid.on("rowClick", function(e, row){
// 이전 선택 해제
_tabulGrid.deselectRow();
// 현재 행 선택
_tabulGrid.selectRow(row);
var data = row.getData();
fn_selectEbom(data.OBJID, data.PART_NO, data.PART_NAME);
});
}
}
// E-BOM 선택 시 미리보기
function fn_selectEbom(bomReportObjid, partNo, partName) {
$("#selectedBomObjid").val(bomReportObjid);
$("#selectedPartNo").text(partNo);
$("#selectedPartName").text(partName);
// 하단 iframe에 E-BOM 상세 로드
$("#ebomPreviewFrame").attr("src", "/partMng/structurePopupLeft.do?readonly=readonly&objId=" + bomReportObjid);
// 선택 표시
$("#selectedInfo").show();
}
// E-BOM 할당
function fn_assignEbom() {
var bomReportObjid = $("#selectedBomObjid").val();
var projectMgmtObjid = "${param.projectMgmtObjid}";
if(!bomReportObjid) {
alert("E-BOM을 선택해주세요.");
return;
}
var confirmMsg = hasCurrentEbom
? "선택한 E-BOM으로 변경하시겠습니까?"
: "선택한 E-BOM을 할당하시겠습니까?";
if(confirm(confirmMsg)) {
$.ajax({
url: "/productionplanning/assignEbomToMbom.do",
type: "POST",
data: {
projectMgmtObjid: projectMgmtObjid,
bomReportObjid: bomReportObjid
},
dataType: "json",
success: function(result) {
if(result.success) {
var successMsg = hasCurrentEbom
? "E-BOM이 변경되었습니다."
: "E-BOM이 할당되었습니다.";
alert(successMsg);
if(window.opener && window.opener.fn_search) {
window.opener.fn_search(); // 부모 창 새로고침
}
window.close();
} else {
alert(result.message || "할당에 실패했습니다.");
}
},
error: function(xhr, status, error) {
alert("오류가 발생했습니다: " + error);
}
});
}
}
</script>
</head>
<body>
<form name="form1" id="form1" method="post">
<input type="hidden" name="status" id="status" value="Y">
<input type="hidden" id="selectedBomObjid">
<div class="ebom-select-container">
<!-- 상단: 정보 및 버튼 -->
<div class="header-section">
<h3>E-BOM ${not empty currentEbom ? '상세 및 변경' : '선택'} - 품번: ${param.partNo} / 품명: ${param.partName}</h3>
<div>
<c:if test="${not empty currentEbom}">
<input type="button" value="E-BOM 변경" class="plm_btns" id="btnChange" style="background-color: #FF9800; color: white;">
</c:if>
<input type="button" value="닫기" class="plm_btns" onclick="window.close()">
</div>
</div>
<!-- 현재 할당된 E-BOM 정보 (할당된 경우만 표시) -->
<c:if test="${not empty currentEbom}">
<div id="currentEbomSection" class="current-ebom-section">
<h4>📋 현재 할당된 E-BOM 정보</h4>
<table class="ebom-info-table">
<tr>
<th>제품구분</th>
<td>${currentEbom.PRODUCT_NAME}</td>
<th>품번</th>
<td>${currentEbom.PART_NO}</td>
</tr>
<tr>
<th>품명</th>
<td>${currentEbom.PART_NAME}</td>
<th>Version</th>
<td>${currentEbom.REVISION}</td>
</tr>
<tr>
<th>등록일</th>
<td>${currentEbom.REG_DATE}</td>
<th>작성자</th>
<td>${currentEbom.DEPT_NAME} / ${currentEbom.WRITER_NAME}</td>
</tr>
</table>
<div style="text-align: center;">
<p style="color: #666; margin: 10px 0;">다른 E-BOM으로 변경하려면 "E-BOM 변경" 버튼을 클릭하세요.</p>
</div>
</div>
</c:if>
<!-- 중간: E-BOM List (처음에는 숨김 또는 표시) -->
<div id="ebomListSection" class="ebom-list-section" style="display:none;">
<div class="content-box">
<div class="content-box-s">
<div class="plm_menu_name_gdnsi">
<h2><span>E-BOM List (상태: Y)</span></h2>
<div class="btnArea">
<input type="button" value="조회" class="plm_btns" id="btnSearch">
<input type="button" value="E-BOM 할당" class="plm_btns" onclick="fn_assignEbom()" style="background-color: #4CAF50; color: white;">
</div>
</div>
<div id="plmSearchZon">
<table>
<tr>
<td><label for="product_cd">제품구분</label></td>
<td>
<select name="product_cd" id="product_cd" style="width:170px" class="select2" autocomplete="off">
<option value="">선택</option>
${code_map.product_cd}
</select>
</td>
<td class="label"><label for="">품번</label></td>
<td><input type="text" name="SEARCH_PART_NO" id="SEARCH_PART_NO"></td>
<td class="label"><label for="">품명</label></td>
<td><input type="text" name="SEARCH_PART_NAME" id="SEARCH_PART_NAME"></td>
<td class="align_r"><label>등록일</label></td>
<td>
<input type="text" name="search_fromDate" id="search_fromDate" style="width:90px;" autocomplete="off">~
<input type="text" name="search_toDate" id="search_toDate" style="width:90px;" autocomplete="off">
</td>
</tr>
</table>
</div>
<%@include file= "/WEB-INF/view/common/common_gridArea.jsp" %>
</div>
</div>
</div>
<!-- 하단: 미리보기 -->
<div class="ebom-preview-section">
<h4>E-BOM 미리보기 (읽기 전용)</h4>
<div style="width:100%; height:700px; border:1px solid #ddd; overflow: auto; background: white;">
<iframe id="ebomPreviewFrame" style="width:100%; height:100%; border:none; pointer-events: none;"></iframe>
</div>
</div>
</div>
</form>
</body>
</html>

View File

@@ -0,0 +1,326 @@
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<%@ page import="com.pms.common.utils.*"%>
<%@ page import="java.util.*" %>
<%@include file= "/init.jsp" %>
<c:set var="now" value="<%=new java.util.Date() %>"/>
<c:set var="sysYear"><fmt:formatDate value="${now}" pattern="yyyy" /></c:set>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title><%=Constants.SYSTEM_NAME%></title>
<script type="text/javascript" src="/js/tabulator/tabulator_custom.js"></script>
</head>
<body>
<script type="text/javascript">
// Tabulator 그리드 전역 변수
var _tabulGrid;
$(document).ready(function(){
_fnc_datepick(); // 날짜 선택기 초기화
$('.select2').select2(); // select2 초기화
// Enter 키로 검색
$("input").keyup(function(e) {
if (e.keyCode == 13) {
fn_search();
}
});
// 조회 버튼
$("#btnSearch").click(function(){
fn_search();
});
// 전체 체크박스
$(document).on('click', '#checkAll', function() {
$('.rowCheck').prop('checked', $(this).prop('checked'));
});
// 초기 조회
fn_search();
});
// 날짜 선택기 초기화 함수
function _fnc_datepick(){
var $dateinput = $("input.date_icon");
for(var i=0; i<$dateinput.length; i++){
$dateinput.eq(i).attr("size","10");
$dateinput.eq(i).datepicker({
changeMonth:true,
changeYear:true
});
}
}
// 그리드 컬럼 정의
var columns = [
{title:'OBJID', field:'OBJID', visible: false},
{title:'CONTRACT_OBJID', field:'CONTRACT_OBJID', visible: false},
{title:'BOM_REPORT_OBJID', field:'BOM_REPORT_OBJID', visible: false},
// 체크박스
{
headerHozAlign: 'center',
hozAlign: 'center',
width: 50,
title: '<input type="checkbox" id="checkAll">',
field: 'CHK',
formatter: function(cell, formatterParams, onRendered) {
return '<input type="checkbox" class="rowCheck" data-objid="' + cell.getRow().getData().OBJID + '">';
},
headerSort: false
},
// 프로젝트번호
{
headerHozAlign: 'center',
hozAlign: 'left',
width: 120,
title: '프로젝트번호',
field: 'PROJECT_NO'
},
// 주문유형
{
headerHozAlign: 'center',
hozAlign: 'center',
width: 100,
title: '주문유형',
field: 'CATEGORY_NAME'
},
// 제품구분
{
headerHozAlign: 'center',
hozAlign: 'center',
width: 100,
title: '제품구분',
field: 'PRODUCT_NAME'
},
// 국내/해외
{
headerHozAlign: 'center',
hozAlign: 'center',
width: 80,
title: '국내/해외',
field: 'AREA_NAME'
},
// 접수일
{
headerHozAlign: 'center',
hozAlign: 'center',
width: 100,
title: '접수일',
field: 'RECEIPT_DATE'
},
// 고객사
{
headerHozAlign: 'center',
hozAlign: 'left',
width: 150,
title: '고객사',
field: 'CUSTOMER_NAME'
},
// 유/무상
{
headerHozAlign: 'center',
hozAlign: 'center',
width: 80,
title: '유/무상',
field: 'PAID_TYPE_NAME'
},
// 품번
{
headerHozAlign: 'center',
hozAlign: 'left',
width: 150,
title: '품번',
field: 'PART_NO'
},
// 품명
{
headerHozAlign: 'center',
hozAlign: 'left',
width: 200,
title: '품명',
field: 'PART_NAME'
},
// S/N
{
headerHozAlign: 'center',
hozAlign: 'center',
width: 120,
title: 'S/N',
field: 'SERIAL_NO'
},
// 수주수량
{
headerHozAlign: 'center',
hozAlign: 'right',
width: 80,
title: '수주수량',
field: 'QUANTITY'
},
// 요청납기
{
headerHozAlign: 'center',
hozAlign: 'center',
width: 100,
title: '요청납기',
field: 'REQ_DEL_DATE'
},
// 고객사요청사항
{
headerHozAlign: 'center',
hozAlign: 'left',
width: 200,
title: '고객사요청사항',
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
{
headerHozAlign: 'center',
hozAlign: 'center',
width: 100,
title: 'M-BOM',
field: 'MBOM_STATUS',
formatter: fnc_subInfoValueFormatter,
cellClick: function(e, cell) {
var objid = fnc_checkNull(cell.getData().OBJID);
fn_openMBomPopup(objid);
}
},
// M-BOM Version
{
headerHozAlign: 'center',
hozAlign: 'center',
width: 80,
title: 'Version',
field: 'MBOM_VERSION'
},
// M-BOM 작성일
{
headerHozAlign: 'center',
hozAlign: 'center',
width: 100,
title: '작성일',
field: 'MBOM_REGDATE'
}
];
// 검색 함수
function fn_search(){
_tabulGrid = fnc_tabul_search(_tabul_layout_fitColumns, _tabulGrid, "/productionplanning/mBomMgmtGridList.do", columns, true);
}
// E-BOM 팝업
function fn_openEBomPopup(bomReportObjId) {
var popup_width = 1800;
var popup_height = 800;
var url = "/partMng/structurePopupLeft.do?readonly=readonly&objId=" + bomReportObjId;
fn_centerPopup(popup_width, popup_height, url, 'ebomPopup');
}
// E-BOM 선택 팝업
function fn_openEBomSelectPopup(projectMgmtObjid, partNo, partName, bomReportObjid) {
var popup_width = 1200;
var popup_height = 900;
var url = "/productionplanning/mBomEbomSelectPopup.do?projectMgmtObjid=" + projectMgmtObjid
+ "&partNo=" + encodeURIComponent(partNo)
+ "&partName=" + encodeURIComponent(partName);
// 이미 할당된 E-BOM이 있으면 bomReportObjid 추가
if(bomReportObjid) {
url += "&bomReportObjid=" + bomReportObjid;
}
fn_centerPopup(popup_width, popup_height, url, 'ebomSelectPopup');
}
// M-BOM 팝업
function fn_openMBomPopup(objId) {
var popup_width = 1800;
var popup_height = 800;
var url = "/productionplanning/mBomFormPopup.do?objId=" + objId;
fn_centerPopup(popup_width, popup_height, url, 'mbomPopup');
}
</script>
<form name="form1" id="form1" method="post">
<input type="hidden" name="actionType" id="actionType">
<div class="content-box">
<div class="content-box-s">
<div class="plm_menu_name_gdnsi">
<h2>
<span>생산관리_M-BOM관리</span>
</h2>
<div class="btnArea">
<input type="button" class="plm_btns" value="조회" id="btnSearch">
</div>
</div>
<!-- 검색 영역 -->
<div id="plmSearchZon">
<table>
<tr>
<td class="label"><label for="search_part_no">품번</label></td>
<td><input type="text" name="search_part_no" id="search_part_no" value="${param.search_part_no}"></td>
<td class="label"><label for="search_part_name">품명</label></td>
<td><input type="text" name="search_part_name" id="search_part_name" value="${param.search_part_name}"></td>
</tr>
</table>
</div>
<!-- 그리드 영역 -->
<%@include file= "/WEB-INF/view/common/common_gridArea.jsp" %>
</div>
</div>
</form>
</body>
</html>

View File

@@ -67,7 +67,8 @@ $(document).ready(function(){
var columns = [
{title:'CATEGORY_CD' ,field:'CATEGORY_CD' ,visible:false},
{headerHozAlign : 'center', hozAlign : 'left', width : '100', title : '프로젝트번호', field : 'PROJECT_NO' ,frozen:true,
{headerHozAlign : 'center', hozAlign : 'left', width : '140', title : '프로젝트번호', field : 'PROJECT_NO' ,frozen:true,
sorter:"string",
formatter:fnc_createGridAnchorTag,
cellClick:function(e, cell){
var objid = fnc_checkNull(cell.getData().OBJID);
@@ -77,54 +78,54 @@ var columns = [
{title:"프로젝트정보", headerHozAlign:'center', //고객정보
columns:[
{headerHozAlign : 'center', hozAlign : 'center', width : '80', title : '주문유형', field : 'CATEGORY_NAME' },
{headerHozAlign : 'center', hozAlign : 'left', width : '90', title : '제품구분', field : 'PRODUCT_NAME' },
{headerHozAlign : 'center', hozAlign : 'center', width : '80', title : '국내/해외', field : 'AREA_NAME' },
{headerHozAlign : 'center', hozAlign : 'center', width : '90', title : '접수일', field : 'REG_DATE' },
{headerHozAlign : 'center', hozAlign : 'left', width : '120', title : '고객사', field : 'CUSTOMER_NAME' },
{headerHozAlign : 'center', hozAlign : 'center', width : '80', title : '유/무상', field : 'FREE_OF_CHARGE'},
{headerHozAlign : 'center', hozAlign : 'left', width : '150', title : '품번', field : 'PRODUCT_ITEM_CODE' },
{headerHozAlign : 'center', hozAlign : 'left', width : '150', title : '품명', field : 'PRODUCT_ITEM_NAME' },
{headerHozAlign : 'center', hozAlign : 'left', width : '150', title : 'S/N', field : 'SERIAL_NO' },
{headerHozAlign : 'center', hozAlign : 'right', width : '90', title : '수주수량', field : 'CONTRACT_QTY' },
{headerHozAlign : 'center', hozAlign : 'center', width : '90', title : '요청납기', field : 'REQ_DEL_DATE' }
{headerHozAlign : 'center', hozAlign : 'center', width : '80', title : '주문유형', field : 'CATEGORY_NAME', sorter:"string" },
{headerHozAlign : 'center', hozAlign : 'left', width : '90', title : '제품구분', field : 'PRODUCT_NAME', sorter:"string" },
{headerHozAlign : 'center', hozAlign : 'center', width : '80', title : '국내/해외', field : 'AREA_NAME', sorter:"string" },
{headerHozAlign : 'center', hozAlign : 'center', width : '90', title : '접수일', field : 'REG_DATE', sorter:"date", sorterParams:{format:"YYYY-MM-DD"} },
{headerHozAlign : 'center', hozAlign : 'left', width : '120', title : '고객사', field : 'CUSTOMER_NAME', sorter:"string" },
{headerHozAlign : 'center', hozAlign : 'center', width : '80', title : '유/무상', field : 'FREE_OF_CHARGE', sorter:"string"},
{headerHozAlign : 'center', hozAlign : 'left', width : '150', title : '품번', field : 'PRODUCT_ITEM_CODE', sorter:"string" },
{headerHozAlign : 'center', hozAlign : 'left', width : '150', title : '품명', field : 'PRODUCT_ITEM_NAME', sorter:"string" },
{headerHozAlign : 'center', hozAlign : 'left', width : '150', title : 'S/N', field : 'SERIAL_NO', sorter:"string" },
{headerHozAlign : 'center', hozAlign : 'right', width : '90', title : '수주수량', field : 'CONTRACT_QTY', sorter:"number" },
{headerHozAlign : 'center', hozAlign : 'center', width : '90', title : '요청납기', field : 'REQ_DEL_DATE', sorter:"date", sorterParams:{format:"YYYY-MM-DD"} }
],
},
{
title:'설계', headerHozAlign:'center',
columns:[
{headerHozAlign : 'center', hozAlign : 'center', width : '90', title : 'E-BOM', field : 'EBOM_STATUS'}
{headerHozAlign : 'center', hozAlign : 'center', width : '90', title : 'E-BOM', field : 'EBOM_STATUS', sorter:"string"}
]
},
{
title:'생산관리', headerHozAlign:'center',
columns:[
{headerHozAlign : 'center', hozAlign : 'center', width : '90', title : 'M-BOM', field : 'MBOM_STATUS'}
{headerHozAlign : 'center', hozAlign : 'center', width : '90', title : 'M-BOM', field : 'MBOM_STATUS', sorter:"string"}
]
},
{
title:'구매', headerHozAlign:'center',
columns:[
{headerHozAlign : 'center', hozAlign : 'center', width : '90', title : '발주일', field : 'ORDER_DATE'},
{headerHozAlign : 'center', hozAlign : 'center', width : '90', title : '입고율', field : 'RECEIVING_RATE'}
{headerHozAlign : 'center', hozAlign : 'center', width : '90', title : '발주일', field : 'ORDER_DATE', sorter:"date", sorterParams:{format:"YYYY-MM-DD"}},
{headerHozAlign : 'center', hozAlign : 'center', width : '90', title : '입고율', field : 'RECEIVING_RATE', sorter:"number"}
]
},
{
title:'생산', headerHozAlign:'center',
columns:[
{headerHozAlign : 'center', hozAlign : 'center', width : '90', title : '제조1,2팀', field : 'PRODUCTION_TEAM_12'},
{headerHozAlign : 'center', hozAlign : 'center', width : '90', title : '제조3팀', field : 'PRODUCTION_TEAM_3'}
{headerHozAlign : 'center', hozAlign : 'center', width : '90', title : '제조1,2팀', field : 'PRODUCTION_TEAM_12', sorter:"string"},
{headerHozAlign : 'center', hozAlign : 'center', width : '90', title : '제조3팀', field : 'PRODUCTION_TEAM_3', sorter:"string"}
]
},
{
title:'출하', headerHozAlign:'center',
columns:[
{headerHozAlign : 'center', hozAlign : 'center', width : '90', title : '출하일', field : 'SHIPMENT_DATE'}
{headerHozAlign : 'center', hozAlign : 'center', width : '90', title : '출하일', field : 'SHIPMENT_DATE', sorter:"date", sorterParams:{format:"YYYY-MM-DD"}}
]
}
];

View File

@@ -22,7 +22,7 @@ $(document).ready(function(){
$("#btnUpload").click(function(){
var files = $("#file1")[0].files;
if(files.length > 0){
fnc_fileMultiUpload(files, null, "${param.targetObjId}", "${param.docType}", "${param.docTypeName}", null, "srAreaDraw");
fnc_fileMultiUpload(files, null, "${param.targetObjId}", "${param.docType}", "${param.docTypeName}", null, "refeshAttachFileArea");
//file객체 초기화
$("#file1").val("");
}else{
@@ -35,6 +35,14 @@ $(document).ready(function(){
function refeshAttachFileArea(){
srAreaDraw();
// 부모 창의 그리드도 새로고침
if(opener && typeof opener.fn_search == "function"){
opener.fn_search();
}
// Tabulator 그리드가 있는 경우
if(opener && opener._tabulGrid){
opener._tabulGrid.replaceData();
}
}
//형상 영역을 display 한다.
@@ -112,7 +120,15 @@ function fileDelete(fileObjId){
dataType:"json",
async:true,
success:function(data){
fn_fileCallback("sr","${docType}");
fn_fileCallback("sr","${docType}");
// 부모 창의 그리드 새로고침
if(opener && typeof opener.fn_search == "function"){
opener.fn_search();
}
// Tabulator 그리드가 있는 경우
if(opener && opener._tabulGrid){
opener._tabulGrid.replaceData();
}
},
error: function(jqxhr, status, error){
}
@@ -135,12 +151,13 @@ function fileDelete(fileObjId){
-->
<table class="pmsPopupForm">
<tr>
<td rowspan="2" class="input_title align_c">파일첨부</td>
<td colspan="5">
<!-- <div id="srDropZone" class="dropzone">Drag & Drop Files Here</div> -->
<!-- <input type="file" name="file1" id="file1" multiple>
<input type="button" id="btnUpload" value="Upload" class="upload_btns"> -->
<div id="srFileAreaTable" class="spec_data_in_table">
<td rowspan="2" class="input_title align_c">파일첨부</td>
<td colspan="5">
<div style="margin-bottom: 10px;">
<input type="file" name="file1" id="file1" multiple>
<input type="button" id="btnUpload" value="업로드" class="plm_btns">
</div>
<div id="srFileAreaTable" class="spec_data_in_table">
<div style="overflow-y:scroll;">
<table id="" class="fileListscrollThead" style="width:100% !important;">
<colgroup>

View File

@@ -296,6 +296,7 @@ div.plm_menu_name h2 span,
.input_title {
background: var(--label-bg);
color: var(--text-primary);
border-left: 1px solid var(--border-color);
border-right: 1px solid var(--border-color);
font-weight: 500;
position: relative;
@@ -644,7 +645,7 @@ div.plm_menu_name h2 span,
border-collapse: collapse;
background: var(--surface);
text-align: center;
font-size: 14px;
font-size: 13px;
}
.plm_table .plm_thead {
@@ -658,7 +659,7 @@ div.plm_menu_name h2 span,
border-right: 1px solid var(--border-color);
padding: 14px 12px;
font-weight: 600;
font-size: 14px;
font-size: 13px;
}
.plm_table .plm_thead td:last-child {
@@ -671,6 +672,7 @@ div.plm_menu_name h2 span,
border-right: 1px solid var(--border-color);
padding: 12px;
vertical-align: middle;
font-size: 13px;
}
.plm_table td:last-child {

View File

@@ -3,7 +3,7 @@
}
.ui-jqgrid .ui-jqgrid-view,.ui-jqgrid .ui-paging-info, .ui-jqgrid .ui-pg-table,
.ui-jqgrid .ui-pg-selbox {
font-size: 14px;
font-size: 13px;
}
.ui-jqgrid .ui-jqgrid-titlebar{
height: 40px;
@@ -19,11 +19,12 @@
}
.ui-jqgrid .ui-widget-header{
border-bottom:1px solid #D3D3D3;
border-bottom:1px solid #e0e0e0;
background: #f5f5f5;
}
.ui-jqgrid .ui-search-toolbar input[type="text"]{
font-size: 12px;
font-size: 13px;
border: 1px solid #CCCCCC;
border-radius: 4px;
}
@@ -41,17 +42,21 @@
/*** Column headers ******/
.ui-jqgrid .ui-jqgrid-hdiv {
/*background: #F9F9F9;*/
border: 1px solid #D3D3D3;
background: #f5f5f5;
border: 1px solid #e0e0e0;
line-height: 15px;
/*height: 45px;*/
font-weight: bold;
color: #777777;
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
color: #666666;
text-shadow: none;
font-size: 13px;
}
.ui-jqgrid .ui-jqgrid-htable th {
height: auto;
background: #f5f5f5 !important;
border-bottom: 1px solid #e0e0e0 !important;
color: #666666 !important;
}
.ui-jqgrid .ui-jqgrid-htable th div {
@@ -71,6 +76,7 @@
border-bottom: 1px solid #DDDDDD;
border-right:none;
padding:8px;
font-size: 13px;
}
@@ -160,5 +166,5 @@
font-weight: bold;
display: none;
border-width: 2px !important;
font-size:11px;
font-size:13px;
}

View File

@@ -24,8 +24,9 @@ echo ""
# 2단계: 도커 중지 및 삭제
echo -e "${YELLOW}[2/3] 기존 도커 컨테이너 중지 및 삭제 중...${NC}"
docker-compose -f docker-compose.dev.yml down 2>&1 | grep -v "level=warning"
if [ $? -ne 0 ]; then
podman-compose -f docker-compose.dev.yml down 2>&1 | grep -v "level=warning" || true
# PIPESTATUS[0]로 docker-compose의 실제 종료 코드 확인
if [ ${PIPESTATUS[0]} -ne 0 ]; then
echo -e "${RED}✗ 도커 중지 실패!${NC}"
exit 1
fi
@@ -34,14 +35,14 @@ echo ""
# 3단계: 도커 이미지 재빌드 및 시작
echo -e "${YELLOW}[3/3] 도커 이미지 재빌드 및 시작 중...${NC}"
docker-compose -f docker-compose.dev.yml build --no-cache 2>&1 | grep -v "level=warning" | tail -5
if [ $? -ne 0 ]; then
podman-compose -f docker-compose.dev.yml build --no-cache 2>&1 | grep -v "level=warning" | tail -5 || true
if [ ${PIPESTATUS[0]} -ne 0 ]; then
echo -e "${RED}✗ 도커 빌드 실패!${NC}"
exit 1
fi
docker-compose -f docker-compose.dev.yml up -d 2>&1 | grep -v "level=warning"
if [ $? -ne 0 ]; then
podman-compose -f docker-compose.dev.yml up -d 2>&1 | grep -v "level=warning" || true
if [ ${PIPESTATUS[0]} -ne 0 ]; then
echo -e "${RED}✗ 도커 시작 실패!${NC}"
exit 1
fi
@@ -53,7 +54,7 @@ echo -e "${YELLOW}Tomcat 시작 대기 중...${NC}"
sleep 10
# 상태 확인
docker ps | grep wace-plm-dev > /dev/null
podman ps | grep wace-plm-dev > /dev/null
if [ $? -eq 0 ]; then
echo ""
echo -e "${GREEN}================================${NC}"
@@ -64,11 +65,11 @@ if [ $? -eq 0 ]; then
echo ""
# 로그 확인 팁
echo -e "${YELLOW}💡 로그 확인: ${NC}docker logs -f wace-plm-dev"
echo -e "${YELLOW}💡 컨테이너 중지: ${NC}docker-compose -f docker-compose.dev.yml down"
echo -e "${YELLOW}💡 로그 확인: ${NC}podman logs -f wace-plm-dev"
echo -e "${YELLOW}💡 컨테이너 중지: ${NC}podman-compose -f docker-compose.dev.yml down"
else
echo -e "${RED}✗ 컨테이너가 실행되지 않았습니다!${NC}"
echo -e "${YELLOW}로그 확인: ${NC}docker logs wace-plm-dev"
echo -e "${YELLOW}로그 확인: ${NC}podman logs wace-plm-dev"
exit 1
fi

View File

@@ -672,11 +672,14 @@ public class MailUtil {
//◆첨부파일
if(attachFileList != null){
for(HashMap hmFile : attachFileList){
String filepath = CommonUtils.checkNull(hmFile.get(Constants.Db.COL_FILE_PATH)) +"\\"+ CommonUtils.checkNull(hmFile.get(Constants.Db.COL_FILE_SAVED_NAME));
String filepath = CommonUtils.checkNull(hmFile.get(Constants.Db.COL_FILE_PATH)) + File.separator + CommonUtils.checkNull(hmFile.get(Constants.Db.COL_FILE_SAVED_NAME));
File attach_file = new File(filepath);
//String attach_fileName = attach_file.getName();
String attach_fileName = CommonUtils.checkNull(hmFile.get(Constants.Db.COL_FILE_REAL_NAME));
//System.out.println("첨부파일 처리: " + filepath);
//System.out.println("파일 존재 여부: " + attach_file.exists());
//첨부파일용 MimeBodyPart 객체생성
MimeBodyPart mbp_attachFile = new MimeBodyPart();
mbp_attachFile.setFileName(MimeUtility.encodeText(attach_fileName, "euc-kr", "B"));
@@ -770,6 +773,142 @@ public class MailUtil {
return sendMailNew("", toUserEmail, subject, contents, null);
}
/**
* UTF-8 인코딩으로 메일 발송 (견적서 등 한글 내용이 많은 경우)
* 기존 sendMailWithAttachFile과 동일하지만 UTF-8 인코딩 사용
*/
public static boolean sendMailWithAttachFileUTF8(String fromUserId, String fromEmail
, ArrayList<String> toUserIdList, ArrayList<String> toEmailList, ArrayList<String> ccEmailList, ArrayList<String> bccEmailList
, String important, String subject, String contents
, ArrayList<HashMap> attachFileList
, String mailType
) {
if(Constants.Mail.sendMailSwitch == false){
System.out.println("MailUtil.sendMailWithAttachFileUTF8 ::: Constants.Mail.sendMailSwitch is FALSE");
return false;
}
try {
/*변수 초기화*/
fromUserId = CommonUtils.checkNull(fromUserId, "admin");
fromEmail = Constants.Mail.SMTP_USER;
important = CommonUtils.checkNull(important);
subject = CommonUtils.checkNull(subject , "empty subject" );
contents = CommonUtils.checkNull(contents, "empty contents");
mailType = CommonUtils.checkNull(mailType, "empty mailType");
System.out.println("MailUtil.sendMailWithAttachFileUTF8()..");
System.out.println("MailUtil.sendMailWithAttachFileUTF8(fromUserId ):"+fromUserId);
System.out.println("MailUtil.sendMailWithAttachFileUTF8(fromEmail ):"+fromEmail);
System.out.println("MailUtil.sendMailWithAttachFileUTF8(toEmailList ):"+toEmailList);
System.out.println("MailUtil.sendMailWithAttachFileUTF8(ccEmailList ):"+ccEmailList);
System.out.println("MailUtil.sendMailWithAttachFileUTF8(subject ):"+subject);
if(toEmailList == null){ return false; }
//◆◆◆ 1. mail session ◆◆◆
Properties prop = new Properties();
prop.put("mail.smtp.host", Constants.Mail.SMTP_HOST);
prop.put("mail.smtp.port", Constants.Mail.SMTP_PORT);
prop.put("mail.smtp.auth" , "true");
prop.put("mail.smtp.starttls.enable" , "true");
prop.put("mail.smtps.checkserveridentity", "true");
prop.put("mail.smtps.ssl.trust" , "*");
prop.put("mail.debug" , "true");
prop.put("mail.smtp.socketFactory.class" , "javax.net.ssl.SSLSocketFactory");
prop.put("mail.smtp.ssl.enable" , "true");
prop.put("mail.smtp.socketFactory.port" , Constants.Mail.SMTP_PORT);
prop.put("mail.smtp.ssl.trust" , "smtp.gmail.com");
prop.put("mail.transport.protocol", "smtp");
prop.put("mail.smtp.ssl.protocols", "TLSv1.2");
Session mailSession = Session.getDefaultInstance(prop, new javax.mail.Authenticator() {
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(Constants.Mail.SMTP_USER, Constants.Mail.SMTP_USER_PW);
}
});
mailSession.setDebug(true);
mailSession.setDebugOut(new PrintStream(new ByteArrayOutputStream()));
//◆◆◆ 2. message ◆◆◆
MimeMessage message = new MimeMessage(mailSession);
message.setFrom(new InternetAddress( fromEmail ));
// 수신자
if(toEmailList != null){
for (String _mail : toEmailList) {
if(CommonUtils.checkNull(_mail).equals("")){
continue;
}
message.addRecipient(Message.RecipientType.TO, new InternetAddress(_mail));
}
}
// 참조
if(ccEmailList != null){
for (String _mail : ccEmailList) {
if(CommonUtils.checkNull(_mail).equals("")){
continue;
}
message.addRecipient(Message.RecipientType.CC, new InternetAddress(_mail));
}
}
// 숨은참조
if(bccEmailList != null){
for (String _mail : bccEmailList) {
if(CommonUtils.checkNull(_mail).equals("")){
continue;
}
message.addRecipient(Message.RecipientType.BCC, new InternetAddress(_mail));
}
}
// 중요도
if("".equals(important)){
message.setHeader("Importance", "Normal");
}else{
message.setHeader("Importance", important);
}
// 제목 (UTF-8)
message.setSubject(subject, "UTF-8");
Multipart multipart = new MimeMultipart();
// 내용 (UTF-8)
MimeBodyPart mbp_content = new MimeBodyPart();
mbp_content.setContent(contents, "text/html;charset=UTF-8");
mbp_content.setHeader("Content-Transfer-Encoding", "base64");
multipart.addBodyPart(mbp_content);
// 첨부파일
if(attachFileList != null){
for(HashMap hmFile : attachFileList){
String filepath = CommonUtils.checkNull(hmFile.get(Constants.Db.COL_FILE_PATH)) + File.separator + CommonUtils.checkNull(hmFile.get(Constants.Db.COL_FILE_SAVED_NAME));
File attach_file = new File(filepath);
String attach_fileName = CommonUtils.checkNull(hmFile.get(Constants.Db.COL_FILE_REAL_NAME));
MimeBodyPart mbp_attachFile = new MimeBodyPart();
mbp_attachFile.setFileName(MimeUtility.encodeText(attach_fileName, "UTF-8", "B"));
mbp_attachFile.setDataHandler(new DataHandler(new FileDataSource(attach_file)));
mbp_attachFile.setHeader("Content-Type", Files.probeContentType(Paths.get(attach_file.getCanonicalPath())));
mbp_attachFile.setDescription(attach_fileName.split("\\.")[0], "UTF-8");
multipart.addBodyPart(mbp_attachFile);
}
}
message.setContent(multipart);
message.setSentDate(new Date());
// 메일 발송
Transport.send(message);
System.out.println("메일 발송 성공 (UTF-8)");
return true;
} catch (Exception e) {
System.out.println("메일 발송 실패 (UTF-8): " + e.getMessage());
e.printStackTrace();
return false;
}
}
/**
* 첨부파일 목록을 파일하나로 압축해서 반환
* @param zipFileName 압축파일명

View File

@@ -21,6 +21,8 @@ import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import com.oreilly.servlet.MultipartRequest;
import com.pms.common.FileRenameClass;
import com.pms.common.JsonUtil;
import com.pms.common.SqlMapConfig;
import com.pms.common.bean.PersonBean;
@@ -31,6 +33,9 @@ import com.pms.service.PartMgmtService;
import com.pms.service.PartMngService;
import com.pms.service.ProductMgmtService;
import com.pms.service.ProjectConceptService;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
@Controller
public class PartMngController {
@@ -455,6 +460,38 @@ public class PartMngController {
return "/partMng/partMngDetailPopUp";
}
/**
* PART Detail 수정 저장
* @param request
* @param paramMap
* @return
*/
@RequestMapping("/partMng/updatePartDetail.do")
@ResponseBody
public Map<String, Object> updatePartDetail(HttpServletRequest request, @RequestParam Map<String, Object> paramMap){
Map<String, Object> resultMap = new HashMap<String, Object>();
try{
// 품목 정보 업데이트
int result = partMngService.updatePartDetail(request, paramMap);
if(result > 0){
resultMap.put("result", "success");
resultMap.put("message", "저장되었습니다.");
}else{
resultMap.put("result", "fail");
resultMap.put("message", "저장에 실패했습니다.");
}
}catch(Exception e){
e.printStackTrace();
resultMap.put("result", "error");
resultMap.put("message", "저장 중 오류가 발생했습니다.");
}
return resultMap;
}
/**
* PART Detail PopUp
* @param request
@@ -575,17 +612,30 @@ public class PartMngController {
* @param paramMap
* @return
*/
/**
* PART 목록 조회 (전체 데이터 조회 - 재귀 CTE 제거로 성능 개선)
* @param request
* @param paramMap
* @return JSON 형태의 전체 데이터
*/
@RequestMapping("/partMng/getPartMngList_ajax.do")
public String getPartList_ajax(HttpServletRequest request, @RequestParam Map<String, Object> paramMap){
@ResponseBody
public Map<String, Object> getPartList_ajax(HttpServletRequest request, @RequestParam Map<String, Object> paramMap){
paramMap.put("status", "complete");
System.out.println("getPArtList_ajax paramMap : "+paramMap);
paramMap.put("IS_LAST","1");
//paramMap.put("STATUS", "release");
System.out.println("getPartList_ajax paramMap : "+paramMap);
// 전체 데이터 조회 (페이징 제거, 재귀 CTE 제거로 속도 대폭 향상)
List list = partMngService.getToConnectPartMngList(request, paramMap);
//List list = CommonUtils.keyChangeUpperList(partMngService.getPartMngList(request,paramMap));
request.setAttribute("RESULT", CommonUtils.getJsonArray(list));
return "/ajax/ajaxResult";
// 응답 데이터 구성
Map<String, Object> response = new HashMap<>();
response.put("data", list);
System.out.println("데이터 조회 완료 - " + list.size() + "");
return response;
}
@@ -875,6 +925,14 @@ public class PartMngController {
@RequestMapping("/partMng/structureHeaderPopup.do")
public String structureHeaderPopup(HttpServletRequest request, @RequestParam Map<String, Object> paramMap){
Map code_map = new HashMap();
try {
// 제품구분 (PART_TYPE)
code_map.put("product_cd", commonService.bizMakeOptionList("0000001", CommonUtils.nullToEmpty((String)paramMap.get("product")),"common.getCodeselect"));
} catch (Exception e) {
e.printStackTrace();
}
request.setAttribute("code_map", code_map);
return "/partMng/structurePopupTop";
}
@@ -928,6 +986,31 @@ public class PartMngController {
return "/partMng/structurePopupLeft"+("excel".equals(actionType) ? "Excel" : "");
}
/**
* 구조등록 좌측 프레임 JSON 데이터
* @param request
* @param paramMap
* @return
*/
@RequestMapping("/partMng/getStructureTreeJson.do")
@ResponseBody
public List<Map> getStructureTreeJson(HttpServletRequest request, @RequestParam Map<String, Object> paramMap){
List bomTreeList = null;
try{
Map info = partMngService.getBOMStructureStandardInfo(request,paramMap);
// search_type 파라미터를 info에 복사 (adding 상태 조회를 위해)
if(paramMap.containsKey("search_type")) {
info.put("search_type", paramMap.get("search_type"));
}
bomTreeList = partMngService.getBOMPartTreeList(info);
}catch(Exception e){
e.printStackTrace();
}
return bomTreeList != null ? bomTreeList : new ArrayList<Map>();
}
@RequestMapping("/partMng/structurePopupCenter.do")
public String structurePopupCenter(HttpServletRequest request, @RequestParam Map<String, Object> paramMap){
@@ -1079,9 +1162,17 @@ public class PartMngController {
//유닛명
code_map.put("unit_code",commonService.bizMakeOptionList("", CommonUtils.nullToEmpty((String)paramMap.get("unit_code")),"common.getUnitCodeList"));
if("Y".equals(search)){
if("Y".equals(search)){
String searchType = CommonUtils.checkNull(paramMap.get("searchType"), "ascending");
if("descending".equals(searchType)){
// 역전개 조회
resultList = partMngService.getStructureDescendingList(request,paramMap);
}else{
// 정전개 조회 (기본값)
resultList = partMngService.getStructureAscendingList(request,paramMap);
}
}
}catch(Exception e){
e.printStackTrace();
}
@@ -1538,37 +1629,58 @@ public class PartMngController {
String objid="";
try{
objid = CommonUtils.createObjId();
code_map.put("customer_cd",commonService.bizMakeOptionList("", (String)paramMap.get("customer_cd"),"common.getsupplyselect"));
code_map.put("project_name",commonService.bizMakeOptionList("", (String)paramMap.get("project_name"),"common.getProjectNameList"));
code_map.put("unit_code",commonService.bizMakeOptionList((String)paramMap.get("unit_code"), (String)paramMap.get("unit_code"),"common.getBomCodeList"));
//code_map.put("customer_cd",commonService.bizMakeOptionList("", (String)paramMap.get("customer_cd"),"common.getsupplyselect"));
//code_map.put("project_name",commonService.bizMakeOptionList("", (String)paramMap.get("project_name"),"common.getProjectNameList"));
//code_map.put("unit_code",commonService.bizMakeOptionList((String)paramMap.get("unit_code"), (String)paramMap.get("unit_code"),"common.getBomCodeList"));
//제품구분
code_map.put("product_cd", commonService.bizMakeOptionList("0000001", CommonUtils.nullToEmpty((String)paramMap.get("product_cd")),"common.getCodeselect")); //제품구분
sqlSession = SqlMapConfig.getInstance().getSqlSession();
Map sqlMap = new HashMap();
String unitCode = CommonUtils.checkNull(paramMap.get("unit_code"));
sqlMap.put("unitCode", unitCode);
Map unitInfoMap = (HashMap)sqlSession.selectOne("common.getUnitCodeList", sqlMap);
//String unitName = CommonUtils.checkNull(unitInfoMap.get("name")).replaceAll("\u00A0+", " ");
String UNIT_NAME = CommonUtils.checkNull(unitInfoMap.get("name"));
sqlParam.put("customer_cd", CommonUtils.checkNull(paramMap.get("customer_cd")));
//sqlParam.put("project_name", CommonUtils.checkNull(paramMap.get("project_name")));
sqlParam.put("unit_name_eq", UNIT_NAME);
regiestSameContractUnitNameBomList = partMngService.getBOMStandardStructureList(request, sqlParam);
sqlSession = SqlMapConfig.getInstance().getSqlSession();
// 통합 템플릿으로 변경하면서 unit_code 관련 로직은 사용하지 않음
/*
// UNIT_NAME 조회 (unit_code가 유효한 경우에만)
String UNIT_NAME = "";
String unitCode = CommonUtils.checkNull(paramMap.get("unit_code"));
if(sqlSession != null && !StringUtils.isBlank(unitCode) && !"undefined".equals(unitCode)){
try {
Map sqlMap = new HashMap();
sqlMap.put("unitCode", unitCode);
Map unitInfoMap = (HashMap)sqlSession.selectOne("common.getUnitCodeList", sqlMap);
if(unitInfoMap != null){
UNIT_NAME = CommonUtils.checkNull(unitInfoMap.get("name"));
}
} catch(Exception e) {
// unit_code 조회 실패시 빈 문자열 사용
System.out.println("Failed to get UNIT_NAME for unitCode: " + unitCode);
e.printStackTrace();
}
}
sqlParam.put("customer_cd", CommonUtils.checkNull(paramMap.get("customer_cd")));
//sqlParam.put("project_name", CommonUtils.checkNull(paramMap.get("project_name")));
sqlParam.put("unit_name_eq", UNIT_NAME);
regiestSameContractUnitNameBomList = partMngService.getBOMStandardStructureList(request, sqlParam);
*/
// unit_code 없이 BOM 구조 조회
sqlParam.put("customer_cd", CommonUtils.checkNull(paramMap.get("customer_cd")));
sqlParam.put("unit_name_eq", "");
regiestSameContractUnitNameBomList = partMngService.getBOMStandardStructureList(request, sqlParam);
//paramMap.put("product_code",(String)paramMap.get("product_code"));
//info = partMngService.getBOMStructureRev(request,paramMap);
bomContractInfo = partMngService.getBOMContractinfo(request,paramMap);
Map param = new HashMap();
//unit
param.put("parentCodeId", "0000059");
code_map.put("unit_cd", commonService.getJqGridSelectBoxJsonData("common.getCodeList2", param, "선택"));
param.put("parentCodeId", "0000062");
code_map.put("part_type", commonService.getJqGridSelectBoxJsonData("common.getCodeList2", param, "선택"));
code_map.put("sup_code", commonService.getJqGridSelectBoxJsonData("common.getmatersupplyselect", param, "선택"));
// part_type 셀렉트박스 데이터 전달
Map param = new HashMap();
param.put("parentCodeId", "0000062");
code_map.put("part_type", commonService.getJqGridSelectBoxJsonData("common.getCodeList2", param, "선택"));
code_map.put("sup_code", commonService.getJqGridSelectBoxJsonData("common.getmatersupplyselect", param, "선택"));
// 활성화된 BOM 목록 전달 (복사용) - bizMakeOptionList 사용
code_map.put("bom_list", commonService.bizMakeOptionList("", "", "partMng.getActiveBomList"));
bomInfo = partMngService.getBOMStructureStandardInfo(request, paramMap);
if(bomInfo == null) bomInfo = new HashMap();
@@ -1586,10 +1698,10 @@ public class PartMngController {
}else {
//신규등록일 때만 보이게
}
multiMasterList = commonService.selectList("purchaseOrder.selectPurchaseOrderMasterList", request, paramMap);
//multiMasterList = commonService.selectList("purchaseOrder.selectPurchaseOrderMasterList", request, paramMap);
//동시발주용 정보
request.setAttribute("multiMasterList", multiMasterList);
//request.setAttribute("multiMasterList", multiMasterList);
request.setAttribute("regiestSameContractUnitNameBomList", regiestSameContractUnitNameBomList); //동일 계약에 동일유닛네임으로 저장된 bom list
request.setAttribute("code_map",code_map);
@@ -1597,9 +1709,9 @@ public class PartMngController {
request.setAttribute("resultList",resultList);
request.setAttribute("info",bomContractInfo);
request.setAttribute("objid",objid);
request.setAttribute("CUSTOMER_OBJID",(String)paramMap.get("customer_cd"));
request.setAttribute("CONTRACT_OBJID",(String)paramMap.get("project_name"));
request.setAttribute("UNIT_CODE",(String)paramMap.get("unit_code"));
//request.setAttribute("CUSTOMER_OBJID",(String)paramMap.get("customer_cd"));
//request.setAttribute("CONTRACT_OBJID",(String)paramMap.get("project_name"));
//request.setAttribute("UNIT_CODE",(String)paramMap.get("unit_code"));
}catch(Exception e){
e.printStackTrace();
}finally {
@@ -1632,6 +1744,21 @@ public class PartMngController {
return parsingPartList;
}
/**
* BOM 복사를 위한 데이터 조회 (엑셀 파싱 형식과 동일하게 반환)
*/
@RequestMapping("/partMng/getBomDataForCopy.do")
@ResponseBody
public ArrayList getBomDataForCopy(HttpServletRequest request, @RequestParam Map<String, Object> paramMap){
ArrayList bomDataList = new ArrayList();
try{
bomDataList = (ArrayList)partMngService.getBomDataForCopy(request, paramMap);
}catch(Exception e){
e.printStackTrace();
}
return bomDataList;
}
/**
* BOM 파트&구조 저장
*/
@@ -1652,6 +1779,45 @@ public class PartMngController {
CommonUtils.setReqResult(request, "", "F", e, resultMap);
}
//return result;
return resultMap;
}
/**
* 품번 중복 체크 (PART_BOM_REPORT 테이블)
* @param request
* @param paramMap
* @return
*/
@RequestMapping("/partMng/checkDuplicatePartNo.do")
@ResponseBody
public Map<String, Object> checkDuplicatePartNo(HttpServletRequest request, @RequestParam Map<String, Object> paramMap){
Map<String, Object> resultMap = new HashMap<String, Object>();
try{
String partNo = CommonUtils.checkNull(paramMap.get("partNo"));
String bomReportObjid = CommonUtils.checkNull(paramMap.get("bomReportObjid"));
if(!"".equals(partNo)){
paramMap.put("partNo", partNo.trim());
paramMap.put("bomReportObjid", bomReportObjid);
// DB에서 중복 체크
int count = partMngService.checkDuplicatePartNo(request, paramMap);
resultMap.put("isDuplicate", count > 0);
}else{
resultMap.put("isDuplicate", false);
}
resultMap.put("result", "success");
}catch(Exception e){
e.printStackTrace();
resultMap.put("result", "error");
resultMap.put("message", "품번 중복 체크 중 오류가 발생했습니다.");
resultMap.put("isDuplicate", false);
}
return resultMap;
}
@@ -1986,4 +2152,380 @@ public class PartMngController {
return "/ajax/ajaxResult";
}
/**
* 도면 파일 일괄 업로드
* 파일명에 품번이 포함된 경우 자동 매칭하여 업로드
* - stp 파일 -> 3D CAD 컬럼
* - dwg 파일 -> 2D Drawing CAD 컬럼
* - pdf 파일 -> 2D PDF CAD 컬럼
*
* @param request
* @param session
* @param bomObjId BOM OBJID
* @return
*/
@RequestMapping(value="/partMng/uploadDrawingFiles.do", method=RequestMethod.POST)
@ResponseBody
public Map<String, Object> uploadDrawingFiles(
HttpServletRequest request,
HttpSession session) {
Map<String, Object> resultMap = new HashMap<>();
MultipartRequest multi = null;
FileRenameClass frc = null;
try {
PersonBean person = (PersonBean)session.getAttribute(Constants.PERSON_BEAN);
String userId = person != null ? person.getUserId() : "plmAdmin";
// MultipartRequest로 파일 업로드 처리
String storagePath = Constants.FILE_STORAGE;
int maxSize = 1024*1024*1024*9; // 9GB
File storage = new File(storagePath);
if(!storage.exists()) storage.mkdirs();
frc = new FileRenameClass();
multi = new MultipartRequest(request, storagePath, maxSize, "UTF-8", frc);
java.util.List fileList = frc.getFileList();
// MultipartRequest에서 bomObjId 파라미터 가져오기
String bomObjId = multi.getParameter("bomObjId");
if(bomObjId == null || bomObjId.isEmpty()) {
resultMap.put("result", "fail");
resultMap.put("message", "BOM ID가 전달되지 않았습니다.");
return resultMap;
}
// BOM 정보 조회
Map<String, Object> bomParam = new HashMap<>();
bomParam.put("objId", bomObjId);
Map bomInfo = partMngService.getBOMStructureStandardInfo(request, bomParam);
if(bomInfo == null) {
resultMap.put("result", "fail");
resultMap.put("message", "BOM 정보를 찾을 수 없습니다.");
return resultMap;
}
// 해당 BOM의 모든 파트 정보 조회
bomParam.put("SEARCH_BOM_OBJID", bomObjId);
ArrayList<Map> partList = commonService.selectList("partMng.partMngListByBom", request, bomParam);
if(partList == null || partList.isEmpty()) {
resultMap.put("result", "fail");
resultMap.put("message", "BOM에 등록된 파트가 없습니다.");
return resultMap;
}
// 품번 기준 파트 맵 생성
Map<String, Map> partNoMap = new HashMap<>();
for(Map part : partList) {
String partNo = CommonUtils.checkNull((String)part.get("PART_NO"));
if(!partNo.isEmpty()) {
partNoMap.put(partNo, part);
}
}
int successCount = 0;
int failCount = 0;
int notFoundCount = 0;
// 업로드된 파일 처리
if(fileList != null && !fileList.isEmpty()) {
for(Object fileObj : fileList) {
Map fileInfo = (Map)fileObj;
String originalFileName = CommonUtils.checkNull((String)fileInfo.get("realFileName"));
String savedFileName = CommonUtils.checkNull((String)fileInfo.get("savedFileName"));
String fileExt = CommonUtils.checkNull((String)fileInfo.get("fileExt"));
long fileSize = Long.parseLong(CommonUtils.checkNull(fileInfo.get("fileSize"), "0"));
// 확장자 대문자 변환 (이미 점 없이 저장됨)
fileExt = fileExt.toUpperCase();
System.out.println("========== 파일 업로드 처리 ==========");
System.out.println("원본 파일명: " + originalFileName);
System.out.println("저장 파일명: " + savedFileName);
System.out.println("확장자: " + fileExt);
System.out.println("===================================");
// 파일 확장자에 따른 문서 타입 결정
String docType = "";
String docTypeName = "";
if("STP".equals(fileExt) || "STEP".equals(fileExt)) {
docType = "3D_CAD";
docTypeName = "3D CAD 첨부파일";
} else if("DWG".equals(fileExt)) {
docType = "2D_DRAWING_CAD";
docTypeName = "2D(Drawing) CAD 첨부파일";
} else if("PDF".equals(fileExt)) {
docType = "2D_PDF_CAD";
docTypeName = "2D(PDF) CAD 첨부파일";
} else {
// 지원하지 않는 확장자는 스킵
System.out.println("지원하지 않는 확장자: " + fileExt + ", 파일명: " + originalFileName);
continue;
}
// 파일명에서 확장자 제거하여 품번 추출
String fileNameWithoutExt = originalFileName;
int lastDotIndex = originalFileName.lastIndexOf('.');
if(lastDotIndex > 0) {
fileNameWithoutExt = originalFileName.substring(0, lastDotIndex);
}
// 품번과 정확히 일치하는 경우만 매칭
String matchedPartNo = null;
System.out.println("품번 매칭 시작 - 파일명(확장자 제외): " + fileNameWithoutExt);
// 정확한 매칭 (품번과 파일명이 정확히 일치)
if(partNoMap.containsKey(fileNameWithoutExt)) {
matchedPartNo = fileNameWithoutExt;
System.out.println(" ✓ 품번 정확 매칭 성공: " + matchedPartNo);
}
if(matchedPartNo == null) {
System.out.println(" ✗ 품번 매칭 실패 - 파일명과 일치하는 품번이 없음");
notFoundCount++;
continue;
}
// 해당 파트에 파일 정보 저장
Map partInfo = partNoMap.get(matchedPartNo);
String partObjId = CommonUtils.checkNull((String)partInfo.get("OBJID"));
Map<String, Object> fileMap = new HashMap<>();
fileMap.put("OBJID", CommonUtils.createObjId());
fileMap.put("TARGET_OBJID", partObjId);
fileMap.put("SAVED_FILE_NAME", savedFileName);
fileMap.put("REAL_FILE_NAME", originalFileName);
fileMap.put("DOC_TYPE", docType);
fileMap.put("DOC_TYPE_NAME", docTypeName);
fileMap.put("FILE_SIZE", String.valueOf(fileSize));
fileMap.put("FILE_EXT", fileExt);
fileMap.put("FILE_PATH", storagePath);
fileMap.put("WRITER", userId);
try {
// 파일 정보 DB 저장
partMngService.insertDrawingFile(fileMap);
successCount++;
} catch(Exception e) {
e.printStackTrace();
failCount++;
}
}
}
resultMap.put("result", "success");
resultMap.put("successCount", successCount);
resultMap.put("failCount", failCount);
resultMap.put("notFoundCount", notFoundCount);
resultMap.put("message", "업로드가 완료되었습니다.");
} catch(Exception e) {
e.printStackTrace();
resultMap.put("result", "error");
resultMap.put("message", "업로드 중 오류가 발생했습니다: " + e.getMessage());
} finally {
if(frc != null) {
frc.clear();
}
}
return resultMap;
}
/**
* PART 목록 화면에서 도면 파일 일괄 업로드
* BOM 정보 없이 전체 파트를 대상으로 파일명 매칭
*
* @param request
* @param session
* @return
*/
@RequestMapping(value="/partMng/uploadDrawingFilesForPartList.do", method=RequestMethod.POST)
@ResponseBody
public Map<String, Object> uploadDrawingFilesForPartList(
HttpServletRequest request,
HttpSession session) {
Map<String, Object> resultMap = new HashMap<>();
MultipartRequest multi = null;
FileRenameClass frc = null;
try {
PersonBean person = (PersonBean)session.getAttribute(Constants.PERSON_BEAN);
String userId = person != null ? person.getUserId() : "plmAdmin";
// MultipartRequest로 파일 업로드 처리
String storagePath = Constants.FILE_STORAGE;
int maxSize = 1024*1024*1024*9; // 9GB
File storage = new File(storagePath);
if(!storage.exists()) storage.mkdirs();
frc = new FileRenameClass();
multi = new MultipartRequest(request, storagePath, maxSize, "UTF-8", frc);
java.util.List fileList = frc.getFileList();
// 화면에서 전달된 품번 목록 가져오기
String partNoListJson = multi.getParameter("partNoList");
if(partNoListJson == null || partNoListJson.isEmpty()) {
resultMap.put("result", "fail");
resultMap.put("message", "품번 목록이 전달되지 않았습니다.");
return resultMap;
}
// JSON 파싱 (Gson 사용)
Gson gson = new Gson();
Type listType = new TypeToken<ArrayList<String>>(){}.getType();
java.util.List<String> partNoList = gson.fromJson(partNoListJson, listType);
if(partNoList.isEmpty()) {
resultMap.put("result", "fail");
resultMap.put("message", "페이지에 표시된 파트가 없습니다.");
return resultMap;
}
System.out.println("========== 화면에서 전달된 품번 목록 ==========");
System.out.println("품번 개수: " + partNoList.size());
System.out.println("품번 목록: " + partNoList);
System.out.println("==========================================");
// 전달된 품번 목록에 해당하는 파트만 조회
Map<String, Object> partParam = new HashMap<>();
partParam.put("IS_LAST", "1");
partParam.put("PART_NO_LIST", partNoList);
ArrayList<Map> partList = commonService.selectList("partMng.partMngListByPartNos", request, partParam);
if(partList == null || partList.isEmpty()) {
resultMap.put("result", "fail");
resultMap.put("message", "조회된 파트가 없습니다.");
return resultMap;
}
// 품번 기준 파트 맵 생성 (화면에 표시된 파트만)
Map<String, Map> partNoMap = new HashMap<>();
for(Map part : partList) {
String partNo = CommonUtils.checkNull((String)part.get("PART_NO"));
if(!partNo.isEmpty()) {
partNoMap.put(partNo, part);
}
}
System.out.println("매칭 가능한 품번 개수: " + partNoMap.size());
int successCount = 0;
int failCount = 0;
int notFoundCount = 0;
// 업로드된 파일 처리
if(fileList != null && !fileList.isEmpty()) {
for(Object fileObj : fileList) {
Map fileInfo = (Map)fileObj;
String originalFileName = CommonUtils.checkNull((String)fileInfo.get("realFileName"));
String savedFileName = CommonUtils.checkNull((String)fileInfo.get("savedFileName"));
String fileExt = CommonUtils.checkNull((String)fileInfo.get("fileExt"));
long fileSize = Long.parseLong(CommonUtils.checkNull(fileInfo.get("fileSize"), "0"));
// 확장자 대문자 변환
fileExt = fileExt.toUpperCase();
System.out.println("========== 파트 목록 파일 업로드 처리 ==========");
System.out.println("원본 파일명: " + originalFileName);
System.out.println("저장 파일명: " + savedFileName);
System.out.println("확장자: " + fileExt);
System.out.println("==========================================");
// 파일 확장자에 따른 문서 타입 결정
String docType = "";
String docTypeName = "";
if("STP".equals(fileExt) || "STEP".equals(fileExt)) {
docType = "3D_CAD";
docTypeName = "3D CAD 첨부파일";
} else if("DWG".equals(fileExt)) {
docType = "2D_DRAWING_CAD";
docTypeName = "2D(Drawing) CAD 첨부파일";
} else if("PDF".equals(fileExt)) {
docType = "2D_PDF_CAD";
docTypeName = "2D(PDF) CAD 첨부파일";
} else {
// 지원하지 않는 확장자는 스킵
System.out.println("지원하지 않는 확장자: " + fileExt + ", 파일명: " + originalFileName);
continue;
}
// 파일명에서 확장자 제거하여 품번 추출
String fileNameWithoutExt = originalFileName;
int lastDotIndex = originalFileName.lastIndexOf('.');
if(lastDotIndex > 0) {
fileNameWithoutExt = originalFileName.substring(0, lastDotIndex);
}
// 품번과 정확히 일치하는 경우만 매칭
String matchedPartNo = null;
System.out.println("품번 매칭 시작 - 파일명(확장자 제외): " + fileNameWithoutExt);
// 정확한 매칭 (품번과 파일명이 정확히 일치)
if(partNoMap.containsKey(fileNameWithoutExt)) {
matchedPartNo = fileNameWithoutExt;
System.out.println(" ✓ 품번 정확 매칭 성공: " + matchedPartNo);
}
if(matchedPartNo == null) {
System.out.println(" ✗ 품번 매칭 실패 - 파일명과 일치하는 품번이 없음");
notFoundCount++;
continue;
}
// 해당 파트에 파일 정보 저장
Map partInfo = partNoMap.get(matchedPartNo);
String partObjId = CommonUtils.checkNull((String)partInfo.get("OBJID"));
Map<String, Object> fileMap = new HashMap<>();
fileMap.put("OBJID", CommonUtils.createObjId());
fileMap.put("TARGET_OBJID", partObjId);
fileMap.put("SAVED_FILE_NAME", savedFileName);
fileMap.put("REAL_FILE_NAME", originalFileName);
fileMap.put("DOC_TYPE", docType);
fileMap.put("DOC_TYPE_NAME", docTypeName);
fileMap.put("FILE_SIZE", String.valueOf(fileSize));
fileMap.put("FILE_EXT", fileExt);
fileMap.put("FILE_PATH", storagePath);
fileMap.put("WRITER", userId);
try {
// 파일 정보 DB 저장
partMngService.insertDrawingFile(fileMap);
successCount++;
} catch(Exception e) {
e.printStackTrace();
failCount++;
}
}
}
resultMap.put("result", "success");
resultMap.put("successCount", successCount);
resultMap.put("failCount", failCount);
resultMap.put("notFoundCount", notFoundCount);
resultMap.put("message", "업로드가 완료되었습니다.");
} catch(Exception e) {
e.printStackTrace();
resultMap.put("result", "error");
resultMap.put("message", "업로드 중 오류가 발생했습니다: " + e.getMessage());
} finally {
if(frc != null) {
frc.clear();
}
}
return resultMap;
}
}

View File

@@ -862,16 +862,112 @@ public class ProductionPlanningController extends BaseService {
return resultMap;
}
/**
* 작업일지 팀장 확인
* @param request
* @param paramMap
* @return
*/
@ResponseBody
@RequestMapping("/productionplanning/workDiaryConfirm.do")
public Map workDiaryConfirm(HttpServletRequest request, @RequestParam Map<String, Object> paramMap){
Map resultMap = productionPlanningService.workDiaryConfirm(request, paramMap);
return resultMap;
/**
* 작업일지 팀장 확인
* @param request
* @param paramMap
* @return
*/
@ResponseBody
@RequestMapping("/productionplanning/workDiaryConfirm.do")
public Map workDiaryConfirm(HttpServletRequest request, @RequestParam Map<String, Object> paramMap){
Map resultMap = productionPlanningService.workDiaryConfirm(request, paramMap);
return resultMap;
}
/**
* 생산관리 -> M-BOM 관리 목록
* @param request
* @param paramMap
* @return
*/
@RequestMapping("/productionplanning/mBomMgmt.do")
public String mBomMgmt(HttpServletRequest request, @RequestParam Map paramMap){
Map code_map = new HashMap();
try{
request.setAttribute("code_map", code_map);
}catch(Exception e){
e.printStackTrace();
}
return "/productionplanning/mBomMgmtList";
}
/**
* 생산관리 -> M-BOM 관리 그리드 목록
* @param request
* @param paramMap
* @return
*/
@ResponseBody
@RequestMapping("/productionplanning/mBomMgmtGridList.do")
public Map mBomMgmtGridList(HttpServletRequest request, @RequestParam Map<String, Object> paramMap){
commonService.selectListPagingNew("productionplanning.mBomMgmtGridList", request, paramMap);
return paramMap;
}
/**
* M-BOM E-BOM 선택 팝업
* @param request
* @param paramMap
* @return
*/
@RequestMapping("/productionplanning/mBomEbomSelectPopup.do")
public String mBomEbomSelectPopup(HttpServletRequest request, @RequestParam Map<String, Object> paramMap) {
try {
// 필요한 코드 맵 설정 (structureList.jsp 참고)
Map<String, Object> codeMap = new HashMap<>();
// 제품구분 코드 - structureList와 동일하게 설정
codeMap.put("product_cd", commonService.bizMakeOptionList("0000001", CommonUtils.nullToEmpty((String)paramMap.get("product_cd")), "common.getCodeselect"));
request.setAttribute("code_map", codeMap);
// 현재 할당된 E-BOM 정보 조회
String bomReportObjid = CommonUtils.checkNull(paramMap.get("bomReportObjid"));
if(!"".equals(bomReportObjid)) {
// E-BOM 정보 조회
Map<String, Object> ebomInfo = productionPlanningService.getEbomInfo(bomReportObjid);
request.setAttribute("currentEbom", ebomInfo);
}
} catch(Exception e) {
e.printStackTrace();
}
return "/productionplanning/mBomEbomSelectPopup";
}
/**
* E-BOM을 M-BOM(PROJECT_MGMT)에 할당
* @param request
* @param paramMap
* @return
*/
@ResponseBody
@RequestMapping("/productionplanning/assignEbomToMbom.do")
public Map<String, Object> assignEbomToMbom(HttpServletRequest request, @RequestParam Map<String, Object> paramMap) {
Map<String, Object> resultMap = new HashMap<>();
try {
String projectMgmtObjid = CommonUtils.checkNull(paramMap.get("projectMgmtObjid"));
String bomReportObjid = CommonUtils.checkNull(paramMap.get("bomReportObjid"));
if(projectMgmtObjid.isEmpty() || bomReportObjid.isEmpty()) {
resultMap.put("success", false);
resultMap.put("message", "필수 파라미터가 누락되었습니다.");
return resultMap;
}
// PROJECT_MGMT 테이블의 PART_OBJID 업데이트
int updateResult = productionPlanningService.assignEbomToProject(projectMgmtObjid, bomReportObjid);
if(updateResult > 0) {
resultMap.put("success", true);
resultMap.put("message", "E-BOM이 할당되었습니다.");
} else {
resultMap.put("success", false);
resultMap.put("message", "할당에 실패했습니다.");
}
} catch(Exception e) {
e.printStackTrace();
resultMap.put("success", false);
resultMap.put("message", "오류가 발생했습니다: " + e.getMessage());
}
return resultMap;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -2869,4 +2869,138 @@
</if>
ORDER BY SUBSTRING(PROJECT_NO,POSITION('-' IN PROJECT_NO)+1) DESC
</select>
<!-- M-BOM 관리 목록 조회 -->
<select id="mBomMgmtGridList" parameterType="map" resultType="com.pms.common.UpperKeyMap">
SELECT
PM.OBJID,
PM.CONTRACT_OBJID,
PM.PROJECT_NO,
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,
TO_CHAR(PM.REGDATE, 'YYYY-MM-DD') AS RECEIPT_DATE,
CM.CUSTOMER_OBJID,
COALESCE(
(SELECT SUPPLY_NAME FROM SUPPLY_MNG WHERE OBJID = CM.CUSTOMER_OBJID::NUMERIC LIMIT 1),
''
) AS CUSTOMER_NAME,
CM.PAID_TYPE,
COALESCE(
(SELECT CODE_NAME FROM COMM_CODE WHERE CODE_ID = CM.PAID_TYPE LIMIT 1),
CASE
WHEN CM.PAID_TYPE = 'paid' THEN '유상'
WHEN CM.PAID_TYPE = 'free' THEN '무상'
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에서 조회
(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'
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(
(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(
(SELECT PBR.STATUS
FROM PART_BOM_REPORT PBR
WHERE PBR.OBJID::VARCHAR = 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
LIMIT 1),
''
) AS EBOM_REGDATE,
COALESCE(PM.MBOM_STATUS, '') AS MBOM_STATUS,
'1.0' AS MBOM_VERSION,
TO_CHAR(PM.REGDATE, 'YYYY-MM-DD') AS MBOM_REGDATE
FROM
PROJECT_MGMT PM
LEFT JOIN CONTRACT_MGMT CM ON PM.CONTRACT_OBJID = CM.OBJID
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}) || '%'
</if>
<!-- 품명 검색 (대소문자 구분 없음) -->
<if test="search_part_name != null and search_part_name != ''">
AND UPPER(PM.PART_NAME) LIKE '%' || UPPER(#{search_part_name}) || '%'
</if>
ORDER BY PM.REGDATE DESC
</select>
<!-- E-BOM을 PROJECT_MGMT에 할당 -->
<update id="assignEbomToProject" parameterType="map">
UPDATE PROJECT_MGMT
SET
PART_OBJID = #{bomReportObjid}
WHERE OBJID = #{projectMgmtObjid}
</update>
<!-- E-BOM 정보 조회 -->
<select id="getEbomInfo" parameterType="map" resultType="com.pms.common.UpperKeyMap">
SELECT
T.OBJID,
T.PRODUCT_CD,
CODE_NAME(T.PRODUCT_CD) 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
FROM
PART_BOM_REPORT T
LEFT JOIN USER_INFO UI ON UI.USER_ID = T.WRITER
WHERE T.OBJID::VARCHAR = #{objid}
</select>
</mapper>

View File

@@ -3916,22 +3916,46 @@
ELSE O.PAID_TYPE
END
FROM CONTRACT_MGMT AS O WHERE O.OBJID = T.CONTRACT_OBJID) AS FREE_OF_CHARGE
,(SELECT SUM(O.QUANTITY) FROM CONTRACT_ITEM AS O WHERE O.CONTRACT_OBJID = T.CONTRACT_OBJID) AS CONTRACT_QTY
,(SELECT STRING_AGG(O.PART_NO, ', ' ORDER BY O.SEQ) FROM CONTRACT_ITEM AS O WHERE O.CONTRACT_OBJID = T.CONTRACT_OBJID AND O.PART_NO IS NOT NULL AND O.PART_NO != '') AS PRODUCT_ITEM_CODE
,(SELECT STRING_AGG(O.PART_NAME, ', ' ORDER BY O.SEQ) FROM CONTRACT_ITEM AS O WHERE O.CONTRACT_OBJID = T.CONTRACT_OBJID AND O.PART_NAME IS NOT NULL AND O.PART_NAME != '') AS PRODUCT_ITEM_NAME
,(SELECT STRING_AGG(S.SERIAL_NO, ', ' ORDER BY S.SEQ)
FROM CONTRACT_ITEM AS I
LEFT JOIN CONTRACT_ITEM_SERIAL AS S ON S.ITEM_OBJID = I.OBJID AND S.STATUS = 'ACTIVE'
WHERE I.CONTRACT_OBJID = T.CONTRACT_OBJID
AND S.SERIAL_NO IS NOT NULL) AS SERIAL_NO
-- 영업관리_주문서관리_수주등록
,EBOM_STATUS
,MBOM_STATUS
,(SELECT O.PURCHASE_DATE FROM PURCHASE_ORDER_MASTER AS O WHERE O.CONTRACT_MGMT_OBJID = T.CONTRACT_OBJID LIMIT 1) AS ORDER_DATE
,RECEIVING_RATE
,PRODUCTION_TEAM_12
,PRODUCTION_TEAM_3
,(SELECT O.SHIPPING_DATE::TEXT FROM SHIPMENT_LOG AS O WHERE O.TARGET_OBJID = T.CONTRACT_OBJID LIMIT 1) AS SHIPMENT_DATE
,COALESCE(T.QUANTITY::numeric, 0) AS CONTRACT_QTY
,T.PART_NO AS PRODUCT_ITEM_CODE
,T.PART_NAME AS PRODUCT_ITEM_NAME
-- S/N: 해당 품목의 시리얼 번호 (여러 개일 경우 "S/N 외 N건" 형식)
,(SELECT
CASE
WHEN COUNT(*) = 0 THEN ''
WHEN COUNT(*) = 1 THEN MIN(S.SERIAL_NO)
ELSE MIN(S.SERIAL_NO) || ' 외 ' || (COUNT(*) - 1)::TEXT || '건'
END
FROM CONTRACT_ITEM AS I
LEFT JOIN CONTRACT_ITEM_SERIAL AS S ON S.ITEM_OBJID = I.OBJID AND S.STATUS = 'ACTIVE'
WHERE I.CONTRACT_OBJID = T.CONTRACT_OBJID
AND I.PART_OBJID = T.PART_OBJID
AND S.SERIAL_NO IS NOT NULL) AS SERIAL_NO
-- 요청납기: CONTRACT_ITEM 우선, 없으면 PROJECT_MGMT.DUE_DATE, 없으면 CONTRACT_MGMT.due_date
,COALESCE(
(SELECT CI.DUE_DATE
FROM CONTRACT_ITEM CI
WHERE CI.CONTRACT_OBJID = T.CONTRACT_OBJID
AND CI.PART_OBJID = T.PART_OBJID
AND CI.STATUS = 'ACTIVE'),
T.DUE_DATE,
(SELECT CM.due_date FROM CONTRACT_MGMT CM WHERE CM.OBJID = T.CONTRACT_OBJID)
) AS REQ_DEL_DATE
-- 영업관리_주문서관리_수주등록
,EBOM_STATUS
,MBOM_STATUS
-- 발주일: CONTRACT_MGMT 테이블에서 가져오기 (영업관리_주문서관리와 동일)
,(SELECT O.ORDER_DATE FROM CONTRACT_MGMT AS O WHERE O.OBJID = T.CONTRACT_OBJID) AS ORDER_DATE
,RECEIVING_RATE
,PRODUCTION_TEAM_12
,PRODUCTION_TEAM_3
-- 출하일: sales_registration 테이블에서 가져오기 (영업관리_판매관리와 동일)
,COALESCE(
(SELECT TO_CHAR(SR.shipping_date, 'YYYY-MM-DD')
FROM sales_registration SR
WHERE SR.project_no = T.PROJECT_NO),
''
) AS SHIPMENT_DATE
,(((SELECT SUM(COALESCE(DESIGN_RATE,'0')::INTEGER) / COUNT(1) FROM PMS_WBS_TASK AS O WHERE O.CONTRACT_OBJID = T.OBJID)
+(SELECT SUM(COALESCE(PURCHASE_RATE,'0')::INTEGER) / COUNT(1) FROM PMS_WBS_TASK AS O WHERE O.CONTRACT_OBJID = T.OBJID)
+(SELECT SUM(COALESCE(PRODUCE_RATE,'0')::INTEGER) / COUNT(1) FROM PMS_WBS_TASK AS O WHERE O.CONTRACT_OBJID = T.OBJID)

View File

@@ -2071,14 +2071,38 @@ public class ContractMgmtController {
}
/**
* 견적서 메일 발송 (AJAX)
* PDF 청크 업로드 (AJAX)
* @param request
* @param paramMap - objId (CONTRACT_OBJID)
* @param paramMap - sessionId, chunkIndex, totalChunks, chunk
* @return
*/
@ResponseBody
@RequestMapping(value="/contractMgmt/uploadPdfChunk.do", method=RequestMethod.POST)
public Map uploadPdfChunk(HttpServletRequest request,
@RequestParam Map<String, Object> paramMap){
Map resultMap = new HashMap();
try {
resultMap = contractMgmtService.uploadPdfChunk(request, paramMap);
} catch (Exception e) {
e.printStackTrace();
resultMap.put("result", "error");
resultMap.put("message", "청크 업로드 중 오류가 발생했습니다: " + e.getMessage());
}
return resultMap;
}
/**
* 견적서 메일 발송 (AJAX) - PDF 세션 ID 방식
* @param request
* @param paramMap - objId (CONTRACT_OBJID), pdfSessionId (업로드된 PDF 세션 ID)
* @return
*/
@ResponseBody
@RequestMapping(value="/contractMgmt/sendEstimateMail.do", method=RequestMethod.POST)
public Map sendEstimateMail(HttpServletRequest request, @RequestParam Map<String, Object> paramMap){
public Map sendEstimateMail(HttpServletRequest request,
@RequestParam Map<String, Object> paramMap){
Map resultMap = new HashMap();
try {

View File

@@ -816,6 +816,7 @@
<select id="getSalesMgmtGridList" parameterType="map" resultType="map">
/* salesNcollectMgmt.getSalesMgmtGridList - sales_registration LEFT JOIN으로 최적화 */
SELECT
T.OBJID,
T.PROJECT_NO,
T.CONTRACT_OBJID,
CODE_NAME(T.CATEGORY_CD) AS ORDER_TYPE,
@@ -832,7 +833,7 @@
FROM CONTRACT_MGMT CM WHERE CM.OBJID = T.CONTRACT_OBJID) AS PAYMENT_TYPE,
T.PART_NO AS PRODUCT_NO,
T.PART_NAME AS PRODUCT_NAME,
-- S/N: CONTRACT_ITEM_SERIAL에서만 조회 (품목별로 정확하게 매칭)
-- S/N: 해당 품목의 시리얼 번호 (여러 개일 경우 "S/N 외 N건" 형식)
(SELECT
CASE
WHEN COUNT(*) = 0 THEN ''
@@ -840,11 +841,10 @@
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
LEFT JOIN CONTRACT_ITEM_SERIAL CIS ON CI.OBJID = CIS.ITEM_OBJID AND UPPER(CIS.STATUS) = 'ACTIVE'
WHERE CI.CONTRACT_OBJID = T.CONTRACT_OBJID
AND CI.PART_OBJID = T.PART_OBJID
AND CI.STATUS = 'ACTIVE'
AND UPPER(CIS.STATUS) = 'ACTIVE'
AND CIS.SERIAL_NO IS NOT NULL) AS SERIAL_NO,
COALESCE(T.QUANTITY::numeric, 0) AS ORDER_QUANTITY,
-- 요청납기: CONTRACT_ITEM 우선, 없으면 PROJECT_MGMT, 없으면 CONTRACT_MGMT
@@ -898,6 +898,8 @@
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 != ''">
AND T.CATEGORY_CD = #{orderType}
</if>
@@ -914,6 +916,9 @@
AND SUPPLY_NAME LIKE CONCAT('%', #{customer}, '%')
)
</if>
<if test="customer_objid != null and customer_objid != ''">
AND T.CUSTOMER_OBJID = #{customer_objid}
</if>
<if test="paymentType != null and paymentType != ''">
AND EXISTS (
SELECT 1 FROM CONTRACT_MGMT CM
@@ -975,7 +980,8 @@
<if test="incoterms != null and incoterms != ''">
/* INCOTERMS 필드 없음 - 검색 조건 무시 */
</if>
ORDER BY T.REGDATE DESC
-- 등록일 기준 최신순 정렬 (프로젝트 번호는 보조 정렬)
ORDER BY T.REGDATE DESC, T.PROJECT_NO DESC
</select>
<!-- 매출관리 그리드 목록 개수 - project_mgmt 테이블 기반 -->
@@ -986,6 +992,8 @@
COUNT(1)::integer AS TOTAL_CNT
FROM PROJECT_MGMT AS T
WHERE 1 = 1
AND T.PROJECT_NO IS NOT NULL
AND T.PROJECT_NO != ''
<if test="orderType != null and orderType != ''">
AND T.CATEGORY_CD = #{orderType}
</if>
@@ -1002,6 +1010,9 @@
AND SUPPLY_NAME LIKE CONCAT('%', #{customer}, '%')
)
</if>
<if test="customer_objid != null and customer_objid != ''">
AND T.CUSTOMER_OBJID = #{customer_objid}
</if>
<if test="paymentType != null and paymentType != ''">
AND EXISTS (
SELECT 1 FROM CONTRACT_MGMT CM
@@ -1321,16 +1332,19 @@
FROM CONTRACT_MGMT CM WHERE CM.OBJID = T.CONTRACT_OBJID) AS PAYMENT_TYPE,
T.PART_NO AS PRODUCT_NO,
T.PART_NAME AS PRODUCT_NAME,
-- S/N 정보 (CONTRACT_ITEM_SERIAL에서만 조회, 품목별로 정확하게 매칭)
(SELECT STRING_AGG(CIS.SERIAL_NO, ',' ORDER BY CIS.SEQ)
FROM CONTRACT_ITEM CI
LEFT JOIN CONTRACT_ITEM_SERIAL CIS ON CI.OBJID = CIS.ITEM_OBJID
WHERE CI.CONTRACT_OBJID = T.CONTRACT_OBJID
AND CI.PART_OBJID = T.PART_OBJID
AND CI.STATUS = 'ACTIVE'
AND UPPER(CIS.STATUS) = 'ACTIVE'
AND CIS.SERIAL_NO IS NOT NULL
) AS SERIAL_NO,
-- S/N: 해당 품목의 시리얼 번호 (여러 개일 경우 "S/N 외 N건" 형식)
(SELECT
CASE
WHEN COUNT(*) = 0 THEN ''
WHEN COUNT(*) = 1 THEN MIN(CIS.SERIAL_NO)
ELSE MIN(CIS.SERIAL_NO) || ' 외 ' || (COUNT(*) - 1)::TEXT || '건'
END
FROM CONTRACT_ITEM CI
LEFT JOIN CONTRACT_ITEM_SERIAL CIS ON CI.OBJID = CIS.ITEM_OBJID AND UPPER(CIS.STATUS) = 'ACTIVE'
WHERE CI.CONTRACT_OBJID = T.CONTRACT_OBJID
AND CI.PART_OBJID = T.PART_OBJID
AND CI.STATUS = 'ACTIVE'
AND CIS.SERIAL_NO IS NOT NULL) AS SERIAL_NO,
COALESCE(T.QUANTITY::numeric, 0) AS ORDER_QUANTITY,
-- 요청납기: CONTRACT_ITEM 우선, 없으면 PROJECT_MGMT, 없으면 CONTRACT_MGMT
COALESCE(

View File

@@ -1551,9 +1551,9 @@ public class ContractMgmtService {
String customerName = CommonUtils.checkNull(contractInfo.get("customer_name"));
String subject = "[" + customerName + "] " + contractNo + " 견적서 [OBJID:" + objId + "]";
// 5. 메일 내용 생성
// 5. 메일 내용 생성 (간단한 텍스트 버전)
String contents = makeEstimateMailContents(contractInfo, estimateTemplate, estimateItems);
// 6. 수신자 정보 설정
ArrayList<String> toEmailList = new ArrayList<String>();
String customerEmail = CommonUtils.checkNull(contractInfo.get("customer_email"));
@@ -1571,6 +1571,26 @@ public class ContractMgmtService {
if(!"".equals(writerEmail)){
ccEmailList.add(writerEmail);
}
// 7-1. PDF 파일 처리 (세션 ID로 업로드된 경우)
ArrayList<HashMap> attachFileList = new ArrayList<HashMap>();
String pdfSessionId = CommonUtils.checkNull(paramMap.get("pdfSessionId"));
if(!"".equals(pdfSessionId)) {
// 세션 ID로 저장된 PDF 파일 가져오기
File pdfFile = getPdfFromSession(pdfSessionId, estimateTemplate);
if(pdfFile != null && pdfFile.exists()) {
HashMap<String, String> fileMap = new HashMap<String, String>();
fileMap.put(Constants.Db.COL_FILE_REAL_NAME, pdfFile.getName());
fileMap.put(Constants.Db.COL_FILE_SAVED_NAME, pdfFile.getName());
fileMap.put(Constants.Db.COL_FILE_PATH, pdfFile.getParent());
attachFileList.add(fileMap);
System.out.println("PDF 파일 첨부 완료: " + pdfFile.getAbsolutePath());
} else {
System.out.println("PDF 파일을 찾을 수 없습니다: " + pdfSessionId);
}
}
// 8. 메일 발송
PersonBean person = (PersonBean)request.getSession().getAttribute(Constants.PERSON_BEAN);
@@ -1581,11 +1601,13 @@ public class ContractMgmtService {
System.out.println("To Email: " + toEmailList);
System.out.println("CC Email: " + ccEmailList);
System.out.println("Subject: " + subject);
System.out.println("첨부파일 개수: " + attachFileList.size());
System.out.println("========================");
boolean mailSent = false;
try {
mailSent = MailUtil.sendMailWithAttachFile(
// UTF-8 인코딩 메서드 사용 (견적서는 한글 내용이 많음)
mailSent = MailUtil.sendMailWithAttachFileUTF8(
fromUserId,
null, // fromEmail (자동으로 SMTP 설정 사용)
new ArrayList<String>(), // toUserIdList (빈 리스트)
@@ -1595,7 +1617,7 @@ public class ContractMgmtService {
null, // important
subject,
contents,
null, // attachFileList (TODO: PDF 첨부 구현)
attachFileList.size() > 0 ? attachFileList : null, // PDF 첨부
"CONTRACT_ESTIMATE"
);
@@ -1643,25 +1665,27 @@ private String makeEstimateMailContents(Map contractInfo, Map estimateTemplate,
contents.append("<meta charset='UTF-8'>");
contents.append("<style>");
contents.append("body { font-family: 'Malgun Gothic', '맑은 고딕', Arial, sans-serif; margin: 0; padding: 20px; background-color: #f5f5f5; }");
contents.append(".estimate-container { max-width: 1200px; background: white; margin: 0 auto; padding: 40px; box-shadow: 0 0 10px rgba(0,0,0,0.1); }");
contents.append(".title { text-align: center; font-size: 24pt; font-weight: bold; letter-spacing: 10px; margin-bottom: 40px; border-bottom: 3px solid #333; padding-bottom: 20px; }");
contents.append(".estimate-container { width: 210mm; background: white; margin: 0 auto; padding: 20mm; box-shadow: 0 0 10px rgba(0,0,0,0.1); box-sizing: border-box; }");
contents.append(".title { text-align: center; font-size: 28pt; font-weight: bold; letter-spacing: 20px; margin-bottom: 40px; padding: 10px 0; }");
contents.append(".header-section { width: 100%; margin-bottom: 30px; overflow: hidden; }");
contents.append(".header-left { float: left; width: 48%; }");
contents.append(".header-right { float: right; width: 48%; }");
contents.append(".info-table { width: 100%; border-collapse: collapse; }");
contents.append(".info-table td { padding: 10px; border: 1px solid #000; font-size: 10pt; vertical-align: middle; }");
contents.append(".info-table .label { background-color: #f0f0f0; font-weight: bold; width: 120px; text-align: center; }");
contents.append(".company-info { border: 2px solid #000; padding: 20px; text-align: center; min-height: 200px; box-sizing: border-box; }");
contents.append(".company-stamp-img { max-width: 100%; height: auto; margin: 0 auto 15px; display: block; }");
contents.append(".manager-info { margin-top: 15px; padding-top: 15px; border-top: 1px solid #ddd; text-align: left; font-size: 9pt; line-height: 1.8; }");
contents.append(".items-table { width: 100%; border-collapse: collapse; margin: 20px 0; clear: both; }");
contents.append(".items-table th, .items-table td { border: 1px solid #000; padding: 8px; font-size: 9pt; }");
contents.append(".items-table th { background-color: #e0e0e0; font-weight: bold; text-align: center; }");
contents.append(".header-right { float: right; width: 48%; text-align: right; }");
contents.append(".info-table { width: 100%; border-collapse: collapse; margin-bottom: 20px; }");
contents.append(".info-table td { padding: 5px 8px; border: 1px solid #000; font-size: 9pt; }");
contents.append(".info-table .label { background-color: #f0f0f0; font-weight: bold; width: 80px; text-align: center; }");
contents.append(".company-info { display: inline-block; text-align: right; }");
contents.append(".company-stamp { width: 120px; height: 120px; border: 2px solid #e74c3c; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; margin-bottom: 10px; position: relative; }");
contents.append(".company-stamp-text { writing-mode: vertical-rl; font-size: 16pt; font-weight: bold; color: #e74c3c; letter-spacing: 3px; }");
contents.append(".company-details { font-size: 9pt; line-height: 1.6; margin-top: 10px; }");
contents.append(".greeting-section { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 10px; padding: 0px 5px; }");
contents.append(".greeting-left { line-height: 1.6; font-size: 10pt; }");
contents.append(".greeting-right { text-align: right; font-size: 9pt; line-height: 1.8; }");
contents.append(".items-table { width: 100%; border-collapse: collapse; margin-bottom: 30px; }");
contents.append(".items-table th, .items-table td { border: 1px solid #000; padding: 3px 5px; text-align: center; font-size: 9pt; line-height: 1.3; }");
contents.append(".items-table th { background-color: #f0f0f0; font-weight: bold; }");
contents.append(".text-left { text-align: left; }");
contents.append(".text-right { text-align: right; }");
contents.append(".text-center { text-align: center; }");
contents.append(".note-section { margin-top: 30px; padding: 20px; background-color: #f9f9f9; border: 1px solid #ddd; clear: both; }");
contents.append(".note-section h4 { margin-top: 0; font-size: 11pt; }");
contents.append(".note-section div { margin: 10px 0; font-size: 9pt; line-height: 1.6; }");
contents.append("</style>");
contents.append("</head>");
contents.append("<body>");
@@ -1671,7 +1695,11 @@ private String makeEstimateMailContents(Map contractInfo, Map estimateTemplate,
contents.append("<div class='title'>견 적 서</div>");
// estimateTemplate 키는 대문자/소문자 모두 체크
String recipient = CommonUtils.checkNull(estimateTemplate.get("recipient"));
// 수신처는 contractInfo에서 고객사명 가져오기
String recipient = CommonUtils.checkNull(contractInfo.get("customer_name"));
if("".equals(recipient)) recipient = CommonUtils.checkNull(contractInfo.get("CUSTOMER_NAME"));
// 없으면 estimateTemplate에서 가져오기
if("".equals(recipient)) recipient = CommonUtils.checkNull(estimateTemplate.get("recipient"));
if("".equals(recipient)) recipient = CommonUtils.checkNull(estimateTemplate.get("RECIPIENT"));
String contactPerson = CommonUtils.checkNull(estimateTemplate.get("contact_person"));
@@ -1680,7 +1708,9 @@ private String makeEstimateMailContents(Map contractInfo, Map estimateTemplate,
String estimateNo = CommonUtils.checkNull(estimateTemplate.get("estimate_no"));
if("".equals(estimateNo)) estimateNo = CommonUtils.checkNull(estimateTemplate.get("ESTIMATE_NO"));
String executorDate = CommonUtils.checkNull(estimateTemplate.get("executor_date"));
String executorDate = CommonUtils.checkNull(estimateTemplate.get("executor"));
if("".equals(executorDate)) executorDate = CommonUtils.checkNull(estimateTemplate.get("EXECUTOR"));
if("".equals(executorDate)) executorDate = CommonUtils.checkNull(estimateTemplate.get("executor_date"));
if("".equals(executorDate)) executorDate = CommonUtils.checkNull(estimateTemplate.get("EXECUTOR_DATE"));
// 담당자 정보
@@ -1697,10 +1727,10 @@ private String makeEstimateMailContents(Map contractInfo, Map estimateTemplate,
// 왼쪽: 기본 정보 테이블
contents.append("<div class='header-left'>");
contents.append("<table class='info-table'>");
contents.append("<tr><td class='label'>시행일자</td><td>" + executorDate + "</td></tr>");
contents.append("<tr><td class='label'>수신처</td><td>" + recipient + "</td></tr>");
contents.append("<tr><td class='label'>수신인</td><td>" + contactPerson + "</td></tr>");
contents.append("<tr><td class='label'>견적번호</td><td>" + estimateNo + "</td></tr>");
contents.append("<tr><td class='label'>영업번호</td><td>" + CommonUtils.checkNull(contractInfo.get("contract_no")) + "</td></tr>");
contents.append("</table>");
contents.append("</div>");
@@ -1725,49 +1755,54 @@ private String makeEstimateMailContents(Map contractInfo, Map estimateTemplate,
String stampBase64 = !"".equals(companyStampPath) ? encodeImageToBase64(companyStampPath) : "";
if(!"".equals(stampBase64)) {
// 이미지가 있으면 Base64로 인코딩된 이미지 표시
contents.append("<img src='" + stampBase64 + "' alt='회사 도장' class='company-stamp-img'>");
// 이미지가 있으면 Base64로 인코딩된 이미지 표시
contents.append("<img src='" + stampBase64 + "' alt='회사 도장' style='max-width: 150px; height: auto; margin: 0 auto 15px; display: block;'>");
} else {
// 이미지가 없으면 텍스트로 표시
contents.append("<div style='width:120px; height:120px; margin:0 auto 15px; border:2px solid #cc0000; border-radius:50%; display:inline-flex; align-items:center; justify-content:center; background:linear-gradient(135deg, #fff 0%, #f5f5f5 100%);'>");
contents.append("<div style='text-align:center; font-size:11pt; font-weight:bold; color:#cc0000; line-height:1.3;'>");
contents.append("(주)알피에스<br>대표이사<br>이 종 현");
contents.append("</div>");
contents.append("</div>");
contents.append("<div style='font-size:10pt; margin-bottom:5px;'>RPS CO., LTD</div>");
contents.append("<div style='font-size:9pt; color:#666; margin-bottom:10px;'>대 표 이 사 이 종 현</div>");
contents.append("<div style='font-size:9pt; line-height:1.8;'>");
contents.append("<div>대전광역시 유성구 국제과학10로 8</div>");
contents.append("<div>TEL:(042)602-3300 FAX:(042)672-3399</div>");
contents.append("<div class='company-stamp'>");
contents.append("<div class='company-stamp-text'>(주)알피에스<br>대표이사<br>이 종 현</div>");
contents.append("</div>");
}
// 담당자 정보는 항상 표시
contents.append("<div class='manager-info'>");
contents.append("<div><strong>담당자 :</strong> " + managerName + "</div>");
if(!"".equals(managerContact)) {
contents.append("<div><strong>연락처 :</strong> " + managerContact + "</div>");
}
// 회사 주소 및 연락처
contents.append("<div class='company-details'>");
contents.append("대전광역시 유성구 국제과학10로 8<br>");
contents.append("TEL:(042)602-3300 FAX:(042)672-3399");
contents.append("</div>");
contents.append("</div>"); // company-info 닫기
contents.append("</div>"); // header-right 닫기
contents.append("</div>"); // header-section 닫기
// 인사말 및 담당자 정보
contents.append("<div class='greeting-section'>");
contents.append("<div class='greeting-left'>");
contents.append("견적을 요청해 주셔서 대단히 감사합니다.<br>");
contents.append("하기와 같이 견적서를 제출합니다.");
contents.append("</div>");
contents.append("<div class='greeting-right'>");
contents.append("담당자 : " + managerName + "<br>");
if(!"".equals(managerContact)) {
contents.append("연락처 : " + managerContact + "<br>");
}
contents.append("<span style='font-size: 10pt; margin-top: 5px; display: inline-block;'>부가세 별도</span>");
contents.append("</div>");
contents.append("</div>");
// 견적 품목
if(estimateItems != null && !estimateItems.isEmpty()){
contents.append("<table class='items-table'>");
contents.append("<thead>");
contents.append("<tr>");
contents.append("<th style='width:5%;'>No.</th>");
contents.append("<th style='width:20%;'>품</th>");
contents.append("<th style='width:22%;'>규</th>");
contents.append("<th style='width:7%;'>수량</th>");
contents.append("<th style='width:8%;'>단위</th>");
contents.append("<th style='width:11%;'>단</th>");
contents.append("<th style='width:11%;'>금</th>");
contents.append("<th style='width:16%;'>비고</th>");
contents.append("<th style='width:6%;'>번호<br>NO.</th>");
contents.append("<th style='width:20%;'>품 명<br>DESCRIPTION</th>");
contents.append("<th style='width:22%;'>규 격<br>SPECIFICATION</th>");
contents.append("<th style='width:7%;'>수량<br>Q'TY</th>");
contents.append("<th style='width:8%;'>단위<br>UNIT</th>");
contents.append("<th style='width:11%;'>단 가<br>UNIT<br>PRICE</th>");
contents.append("<th style='width:11%;'>금 액<br>AMOUNT</th>");
contents.append("<th style='width:15%;'>비고</th>");
contents.append("</tr>");
contents.append("</thead>");
contents.append("<tbody>");
@@ -1782,16 +1817,16 @@ private String makeEstimateMailContents(Map contractInfo, Map estimateTemplate,
} catch(Exception e){}
}
contents.append("<tr>");
contents.append("<td class='text-center'>" + (i + 1) + "</td>");
contents.append("<td>" + CommonUtils.checkNull(item.get("description")) + "</td>");
contents.append("<td>" + CommonUtils.checkNull(item.get("specification")) + "</td>");
contents.append("<td class='text-center'>" + CommonUtils.checkNull(item.get("quantity")) + "</td>");
contents.append("<td class='text-center'>" + CommonUtils.checkNull(item.get("unit")) + "</td>");
contents.append("<td class='text-right'>" + CommonUtils.checkNull(item.get("unit_price")) + "</td>");
contents.append("<td class='text-right'>" + CommonUtils.checkNull(item.get("amount")) + "</td>");
contents.append("<td>" + CommonUtils.checkNull(item.get("note")) + "</td>");
contents.append("</tr>");
contents.append("<tr>");
contents.append("<td class='text-center'>" + (i + 1) + "</td>");
contents.append("<td class='text-left'>" + CommonUtils.checkNull(item.get("description")) + "</td>");
contents.append("<td class='text-left'>" + CommonUtils.checkNull(item.get("specification")) + "</td>");
contents.append("<td class='text-center'>" + CommonUtils.checkNull(item.get("quantity")) + "</td>");
contents.append("<td class='text-center'>" + CommonUtils.checkNull(item.get("unit")) + "</td>");
contents.append("<td class='text-right'>" + CommonUtils.checkNull(item.get("unit_price")) + "</td>");
contents.append("<td class='text-right'>" + CommonUtils.checkNull(item.get("amount")) + "</td>");
contents.append("<td class='text-left'>" + CommonUtils.checkNull(item.get("note")) + "</td>");
contents.append("</tr>");
}
// 계 (총 합계) - 통화 기호 포함
@@ -1826,58 +1861,60 @@ private String makeEstimateMailContents(Map contractInfo, Map estimateTemplate,
// 원화환산 공급가액 - 제거 (화면에서도 숨김 처리)
// 테이블 내 비고
// 테이블 내 비고 (항상 표시)
String noteRemarks = CommonUtils.checkNull(estimateTemplate.get("note_remarks"));
if("".equals(noteRemarks)) noteRemarks = CommonUtils.checkNull(estimateTemplate.get("NOTE_REMARKS"));
if(!"".equals(noteRemarks)){
contents.append("<tr>");
contents.append("<td colspan='8' style='height:100px; vertical-align:top; padding:10px; text-align:left;'>");
contents.append("<div style='font-weight:bold; margin-bottom:10px;'>&lt;비고&gt;</div>");
contents.append("<div style='white-space:pre-wrap;'>" + noteRemarks + "</div>");
contents.append("</td>");
contents.append("</tr>");
}
System.out.println("===== 비고 내용 확인 =====");
System.out.println("noteRemarks: [" + noteRemarks + "]");
System.out.println("========================");
contents.append("<tr>");
contents.append("<td colspan='8' style='height:100px; vertical-align:top; padding:10px; text-align:left;'>");
contents.append("<div style='font-weight:bold; margin-bottom:10px;'>&lt;비고&gt;</div>");
contents.append("<div style='white-space:pre-wrap;'>" + noteRemarks + "</div>");
contents.append("</td>");
contents.append("</tr>");
// 참조사항 (NOTE1~4)
String note1 = CommonUtils.checkNull(estimateTemplate.get("note1"));
if("".equals(note1)) note1 = CommonUtils.checkNull(estimateTemplate.get("NOTE1"));
if("".equals(note1)) note1 = "1. 견적유효기간: 일";
String note2 = CommonUtils.checkNull(estimateTemplate.get("note2"));
if("".equals(note2)) note2 = CommonUtils.checkNull(estimateTemplate.get("NOTE2"));
if("".equals(note2)) note2 = "2. 납품기간: 발주 후 1주 이내";
String note3 = CommonUtils.checkNull(estimateTemplate.get("note3"));
if("".equals(note3)) note3 = CommonUtils.checkNull(estimateTemplate.get("NOTE3"));
if("".equals(note3)) note3 = "3. VAT 별도";
String note4 = CommonUtils.checkNull(estimateTemplate.get("note4"));
if("".equals(note4)) note4 = CommonUtils.checkNull(estimateTemplate.get("NOTE4"));
if("".equals(note4)) note4 = "4. 결제 조건 : 기존 결제조건에 따름.";
// 참조사항 행 추가
contents.append("<tr>");
contents.append("<td colspan='8' style='vertical-align: top; padding: 10px; text-align: left;'>");
contents.append("<div style='font-weight: bold; margin-bottom: 10px;'>&lt;참조사항&gt;</div>");
contents.append("<div style='margin-bottom: 5px;'>" + note1 + "</div>");
contents.append("<div style='margin-bottom: 5px;'>" + note2 + "</div>");
contents.append("<div style='margin-bottom: 5px;'>" + note3 + "</div>");
contents.append("<div style='margin-bottom: 5px;'>" + note4 + "</div>");
contents.append("</td>");
contents.append("</tr>");
// 하단 회사명 행 추가
contents.append("<tr>");
contents.append("<td colspan='8' style='text-align: right; padding: 15px; font-size: 10pt; font-weight: bold; border: none;'>");
contents.append("㈜알피에스");
contents.append("</td>");
contents.append("</tr>");
contents.append("</tbody>");
contents.append("</table>");
}
// 참조사항 (NOTE1~4)
String note1 = CommonUtils.checkNull(estimateTemplate.get("note1"));
if("".equals(note1)) note1 = CommonUtils.checkNull(estimateTemplate.get("NOTE1"));
String note2 = CommonUtils.checkNull(estimateTemplate.get("note2"));
if("".equals(note2)) note2 = CommonUtils.checkNull(estimateTemplate.get("NOTE2"));
String note3 = CommonUtils.checkNull(estimateTemplate.get("note3"));
if("".equals(note3)) note3 = CommonUtils.checkNull(estimateTemplate.get("NOTE3"));
String note4 = CommonUtils.checkNull(estimateTemplate.get("note4"));
if("".equals(note4)) note4 = CommonUtils.checkNull(estimateTemplate.get("NOTE4"));
if(!"".equals(note1) || !"".equals(note2) || !"".equals(note3) || !"".equals(note4)){
contents.append("<div class='note-section'>");
contents.append("<h4>※ 참조사항</h4>");
int noteNum = 1;
if(!"".equals(note1)) {
contents.append("<div>" + noteNum + ". " + note1 + "</div>");
noteNum++;
}
if(!"".equals(note2)) {
contents.append("<div>" + noteNum + ". " + note2 + "</div>");
noteNum++;
}
if(!"".equals(note3)) {
contents.append("<div>" + noteNum + ". " + note3 + "</div>");
noteNum++;
}
if(!"".equals(note4)) {
contents.append("<div>" + noteNum + ". " + note4 + "</div>");
}
contents.append("</div>");
}
contents.append("</div>"); // estimate-container 닫기
contents.append("</body>");
contents.append("</html>");
@@ -2370,6 +2407,7 @@ private String encodeImageToBase64(String imagePath) {
return partList;
}
/**
* 품목 목록 조회
* @param contractObjId 견적 OBJID
@@ -2411,4 +2449,143 @@ private String encodeImageToBase64(String imagePath) {
return itemList;
}
/**
* PDF 청크 업로드 처리
* @param request
* @param paramMap - sessionId, chunkIndex, totalChunks, chunk
* @return
*/
public Map uploadPdfChunk(HttpServletRequest request, Map<String, Object> paramMap) {
Map resultMap = new HashMap();
try {
System.out.println("===== uploadPdfChunk 호출 =====");
System.out.println("전달된 파라미터: " + paramMap);
System.out.println("==============================");
String sessionId = CommonUtils.checkNull(paramMap.get("sessionId"));
String chunkIndexStr = CommonUtils.checkNull(paramMap.get("chunkIndex"));
String totalChunksStr = CommonUtils.checkNull(paramMap.get("totalChunks"));
String chunk = CommonUtils.checkNull(paramMap.get("chunk"));
if("".equals(sessionId) || "".equals(chunkIndexStr) || "".equals(totalChunksStr) || "".equals(chunk)) {
System.out.println("필수 파라미터 누락");
resultMap.put("result", "error");
resultMap.put("message", "필수 파라미터가 누락되었습니다.");
return resultMap;
}
int chunkIndex = Integer.parseInt(chunkIndexStr);
int totalChunks = Integer.parseInt(totalChunksStr);
System.out.println("청크 업로드: " + sessionId + " [" + (chunkIndex + 1) + "/" + totalChunks + "]");
System.out.println("청크 크기: " + chunk.length() + " bytes");
// 임시 디렉토리에 청크 저장
String tempDir = System.getProperty("java.io.tmpdir");
String chunkDir = tempDir + File.separator + "pdf_chunks" + File.separator + sessionId;
File chunkDirFile = new File(chunkDir);
if(!chunkDirFile.exists()) {
chunkDirFile.mkdirs();
}
// 청크 파일 저장
String chunkFileName = "chunk_" + chunkIndex + ".txt";
File chunkFile = new File(chunkDir + File.separator + chunkFileName);
java.io.FileWriter fw = new java.io.FileWriter(chunkFile);
fw.write(chunk);
fw.close();
// 마지막 청크인 경우 모든 청크를 합쳐서 PDF 파일 생성
if(chunkIndex == totalChunks - 1) {
System.out.println("모든 청크 수신 완료, PDF 파일 생성 시작");
// 모든 청크 읽어서 합치기
StringBuilder fullBase64 = new StringBuilder();
for(int i = 0; i < totalChunks; i++) {
File cf = new File(chunkDir + File.separator + "chunk_" + i + ".txt");
java.io.BufferedReader br = new java.io.BufferedReader(new java.io.FileReader(cf));
String line;
while((line = br.readLine()) != null) {
fullBase64.append(line);
}
br.close();
}
// Base64 디코딩하여 PDF 파일 생성 (Apache Commons Codec 사용)
byte[] pdfBytes = org.apache.commons.codec.binary.Base64.decodeBase64(fullBase64.toString());
String pdfFileName = sessionId + ".pdf";
File pdfFile = new File(tempDir + File.separator + pdfFileName);
java.io.FileOutputStream fos = new java.io.FileOutputStream(pdfFile);
fos.write(pdfBytes);
fos.close();
pdfFile.deleteOnExit();
System.out.println("PDF 파일 생성 완료: " + pdfFile.getAbsolutePath());
// 청크 파일들 삭제
for(int i = 0; i < totalChunks; i++) {
File cf = new File(chunkDir + File.separator + "chunk_" + i + ".txt");
cf.delete();
}
chunkDirFile.delete();
}
resultMap.put("result", "success");
} catch(Exception e) {
System.out.println("청크 업로드 중 오류: " + e.getMessage());
e.printStackTrace();
resultMap.put("result", "error");
resultMap.put("message", e.getMessage());
}
return resultMap;
}
/**
* 세션 ID로 저장된 PDF 파일 가져오기
* @param sessionId
* @param estimateTemplate
* @return
*/
private File getPdfFromSession(String sessionId, Map estimateTemplate) {
try {
String tempDir = System.getProperty("java.io.tmpdir");
File pdfFile = new File(tempDir + File.separator + sessionId + ".pdf");
if(pdfFile.exists()) {
// 견적번호로 파일명 변경
String estimateNo = CommonUtils.checkNull(estimateTemplate.get("estimate_no"));
if("".equals(estimateNo)) estimateNo = CommonUtils.checkNull(estimateTemplate.get("ESTIMATE_NO"));
if("".equals(estimateNo)) estimateNo = "견적서";
java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyyMMddHHmmss");
String timestamp = sdf.format(new java.util.Date());
String newFileName = estimateNo + "_" + timestamp + ".pdf";
File renamedFile = new File(tempDir + File.separator + newFileName);
// 파일 복사
java.nio.file.Files.copy(pdfFile.toPath(), renamedFile.toPath(),
java.nio.file.StandardCopyOption.REPLACE_EXISTING);
renamedFile.deleteOnExit();
// 원본 파일 삭제
pdfFile.delete();
return renamedFile;
}
return null;
} catch(Exception e) {
System.out.println("PDF 파일 가져오기 중 오류: " + e.getMessage());
e.printStackTrace();
return null;
}
}
}

View File

@@ -341,24 +341,33 @@ public class CommonService extends BaseService {
* @param fileMap
*/
public void setFileDownloadLog(HttpServletRequest request, Map fileMap){
HttpSession session = request.getSession();
PersonBean person = (PersonBean)session.getAttribute(Constants.PERSON_BEAN);
String objId = CommonUtils.createObjId();
String systemName = Constants.SYSTEM_NAME;
String userId = CommonUtils.checkNull(person.getUserId());
String fileObjId = CommonUtils.checkNull(fileMap.get("OBJID"));
String remoteAddr = CommonUtils.checkNull(request.getRemoteAddr());
Map paramMap = new HashMap();
paramMap.put("objId", objId);
paramMap.put("systemName", systemName);
paramMap.put("userId", userId);
paramMap.put("fileObjId", fileObjId);
paramMap.put("remoteAddr", remoteAddr);
SqlSession sqlSession = SqlMapConfig.getInstance().getSqlSession();
sqlSession.insert("common.insertFileDownloadLog", paramMap);
SqlSession sqlSession = null;
try{
HttpSession session = request.getSession();
PersonBean person = (PersonBean)session.getAttribute(Constants.PERSON_BEAN);
String objId = CommonUtils.createObjId();
String systemName = Constants.SYSTEM_NAME;
String userId = CommonUtils.checkNull(person.getUserId());
String fileObjId = CommonUtils.checkNull(fileMap.get("OBJID"));
String remoteAddr = CommonUtils.checkNull(request.getRemoteAddr());
Map paramMap = new HashMap();
paramMap.put("objId", objId);
paramMap.put("systemName", systemName);
paramMap.put("userId", userId);
paramMap.put("fileObjId", fileObjId);
paramMap.put("remoteAddr", remoteAddr);
sqlSession = SqlMapConfig.getInstance().getSqlSession();
sqlSession.insert("common.insertFileDownloadLog", paramMap);
}catch(Exception e){
e.printStackTrace();
}finally{
if(sqlSession != null){
sqlSession.close();
}
}
}
/**

File diff suppressed because it is too large Load Diff

View File

@@ -692,4 +692,61 @@ public class ProductionPlanningService {
}
return resultMap;
}
/**
* E-BOM을 PROJECT_MGMT에 할당
* @param projectMgmtObjid
* @param bomReportObjid
* @return
* @throws Exception
*/
public int assignEbomToProject(String projectMgmtObjid, String bomReportObjid) throws Exception {
SqlSession sqlSession = null;
try {
sqlSession = SqlMapConfig.getInstance().getSqlSession();
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("projectMgmtObjid", projectMgmtObjid);
paramMap.put("bomReportObjid", bomReportObjid);
int result = sqlSession.update("productionplanning.assignEbomToProject", paramMap);
sqlSession.commit();
return result;
} catch(Exception e) {
if(sqlSession != null) {
sqlSession.rollback();
}
throw e;
} finally {
if(sqlSession != null) {
sqlSession.close();
}
}
}
/**
* E-BOM 정보 조회
* @param bomReportObjid
* @return
*/
public Map<String, Object> getEbomInfo(String bomReportObjid) {
Map<String, Object> resultMap = new HashMap<>();
SqlSession sqlSession = null;
try {
sqlSession = SqlMapConfig.getInstance().getSqlSession();
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("objid", bomReportObjid);
resultMap = sqlSession.selectOne("productionplanning.getEbomInfo", paramMap);
} catch(Exception e) {
e.printStackTrace();
} finally {
if(sqlSession != null) {
sqlSession.close();
}
}
return resultMap;
}
}

View File

@@ -5,10 +5,24 @@
<!-- PLM DataSource JNDI Resource -->
<!-- Using environment variables for database connection -->
<!--
개선된 Connection Pool 설정 (커넥션 누수 방지):
- maxTotal: 최대 커넥션 수 (기본 200 유지)
- maxIdle: 유휴 커넥션 수 (기본 50 유지)
- maxWaitMillis: 커넥션 획득 대기 시간 (무한 대기 -> 10초로 제한)
- testOnBorrow: 커넥션 대여 시 유효성 검사
- validationQuery: 유효성 검사 쿼리
- removeAbandonedOnBorrow: 누수된 커넥션 자동 회수 활성화
- removeAbandonedTimeout: 60초 이상 사용 중인 커넥션 회수
- logAbandoned: 누수된 커넥션 로그 기록 (모니터링용)
-->
<Resource name="plm" auth="Container"
type="javax.sql.DataSource" driverClassName="org.postgresql.Driver"
url="${DB_URL}"
username="${DB_USERNAME}" password="${DB_PASSWORD}"
maxTotal="200" maxIdle="50" maxWaitMillis="-1"/>
maxTotal="200" maxIdle="50" maxWaitMillis="10000"
testOnBorrow="true" validationQuery="SELECT 1"
removeAbandonedOnBorrow="true" removeAbandonedTimeout="60"
logAbandoned="true"/>
</Context>

View File

@@ -15,11 +15,15 @@
<Service name="Catalina">
<!-- URIEncoding을 UTF-8로 설정하여 한글 파라미터 처리 -->
<!-- maxPostSize: POST 요청 최대 크기 (청크 업로드를 위해 2MB로 설정) -->
<!-- maxHttpHeaderSize: HTTP 헤더 최대 크기 -->
<Connector port="8080"
protocol="HTTP/1.1"
connectionTimeout="20000"
connectionTimeout="60000"
redirectPort="8443"
URIEncoding="UTF-8" />
URIEncoding="UTF-8"
maxPostSize="2097152"
maxHttpHeaderSize="65536" />
<Engine name="Catalina" defaultHost="localhost">
<Realm className="org.apache.catalina.realm.LockOutRealm">