From 7ae57f971965efbf48b6c192eec326eade0d2e3c Mon Sep 17 00:00:00 2001 From: hjjeong Date: Tue, 11 Nov 2025 14:05:06 +0900 Subject: [PATCH] =?UTF-8?q?=EC=9E=A5=EB=B9=84=EA=B2=AC=EC=A0=81=EC=84=9C?= =?UTF-8?q?=20=ED=85=9C=ED=94=8C=EB=A6=BF=20=EC=88=98=EC=A0=95,=20pdf=20?= =?UTF-8?q?=EB=B3=80=ED=99=98=20=EB=A9=94=EC=9D=BC=20=EB=B0=9C=EC=86=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../contractMgmt/estimateMailFormPopup.jsp | 12 +- .../view/contractMgmt/estimateTemplate2.jsp | 1456 ++++++++++++++--- src/com/pms/common/utils/MailUtil.java | 81 +- .../controller/ContractMgmtController.java | 76 +- src/com/pms/salesmgmt/mapper/contractMgmt.xml | 101 +- .../service/ContractMgmtService.java | 260 ++- 6 files changed, 1674 insertions(+), 312 deletions(-) diff --git a/WebContent/WEB-INF/view/contractMgmt/estimateMailFormPopup.jsp b/WebContent/WEB-INF/view/contractMgmt/estimateMailFormPopup.jsp index b4fc64d..ba83123 100644 --- a/WebContent/WEB-INF/view/contractMgmt/estimateMailFormPopup.jsp +++ b/WebContent/WEB-INF/view/contractMgmt/estimateMailFormPopup.jsp @@ -452,14 +452,14 @@ function fn_generatePdf(contractObjId, templateObjId, templateType){ try { var iframeWindow = this.contentWindow; - // 데이터 로딩 완료 대기 + // 데이터 로딩 완료 대기 (최대 120초 = 1200번 시도) var checkDataLoaded = function(attempts) { - if(attempts > 100) { + if(attempts > 1200) { $('#pdfGeneratorFrame').remove(); Swal.close(); Swal.fire({ title: '타임아웃', - text: '견적서 데이터 로딩 시간이 초과되었습니다.', + text: '견적서 데이터 로딩 시간이 초과되었습니다. (120초)', icon: 'error' }); return; @@ -512,18 +512,18 @@ function fn_generatePdf(contractObjId, templateObjId, templateType){ } }); - // 타임아웃 설정 (30초) + // 타임아웃 설정 (180초 = 3분 - 장비 견적서는 데이터가 많아서 시간이 더 필요) setTimeout(function(){ if($('#pdfGeneratorFrame').length > 0){ $('#pdfGeneratorFrame').remove(); Swal.close(); Swal.fire({ title: '타임아웃', - text: 'PDF 생성 시간이 초과되었습니다.', + text: 'PDF 생성 시간이 초과되었습니다. (180초)\n견적서 데이터가 많은 경우 시간이 오래 걸릴 수 있습니다.', icon: 'error' }); } - }, 30000); + }, 180000); } // PDF 업로드 및 메일 발송 diff --git a/WebContent/WEB-INF/view/contractMgmt/estimateTemplate2.jsp b/WebContent/WEB-INF/view/contractMgmt/estimateTemplate2.jsp index 2e12b8f..ee532cc 100644 --- a/WebContent/WEB-INF/view/contractMgmt/estimateTemplate2.jsp +++ b/WebContent/WEB-INF/view/contractMgmt/estimateTemplate2.jsp @@ -7,6 +7,9 @@ PersonBean person = (PersonBean)session.getAttribute(Constants.PERSON_BEAN); String userId = CommonUtils.checkNull(person.getUserId()); String objId = CommonUtils.checkNull(request.getParameter("objId")); +String templateObjId = CommonUtils.checkNull(request.getParameter("templateObjId")); +String apprStatus = CommonUtils.checkNull((String)request.getAttribute("apprStatus")); +boolean isApproved = "결재완료".equals(apprStatus); %> @@ -50,7 +53,7 @@ body { display: flex; justify-content: space-between; align-items: flex-start; - margin-bottom: 20px; + /* margin-bottom: 20px; */ } .logo-section { @@ -166,16 +169,26 @@ body { text-align: center; } -.detail-row { - height: 60px; +.subtotal-row input { + background-color: transparent; + border: none; + text-align: center; + width: 100%; + font-weight: bold; +} + +.detail-row td { + vertical-align: top; + padding: 5px 8px; } .notes-section { - margin-top: 20px; - padding: 15px; + /* margin-top: 20px; */ + /* padding: 15px; */ border: 1px solid #ddd; font-size: 9pt; line-height: 1.8; + background-color: white; } .notes-section ul { @@ -209,10 +222,15 @@ textarea { width: 100%; font-family: inherit; font-size: inherit; + white-space: pre-wrap; } -.editable { - background-color: #fffef0; +textarea.item-spec { + resize: none; + overflow: hidden; + min-height: 40px; + line-height: 1.4; + white-space: pre-wrap; } .highlight { @@ -226,17 +244,46 @@ textarea { display: inline-block; } -.btn-area { - text-align: center; - margin-top: 20px; - padding: 10px; +.estimate-btn-area { + position: fixed; + bottom: 0; + left: 0; + right: 0; + text-align: right; + padding: 15px 30px; + background-color: #ffffff; + border-top: 3px solid #007bff; + box-shadow: 0 -2px 10px rgba(0,0,0,0.1); + z-index: 1000; } -.plm_btns { - padding: 10px 30px; - margin: 0 5px; - font-size: 14px; +.estimate-btn { + display: inline-block; + padding: 5px 15px; + margin: 0 2px; + font-size: 12px; + font-weight: normal; cursor: pointer; + border: 1px solid #60a5fa; + background-color: #60a5fa; + color: white; + border-radius: 3px; + transition: all 0.3s; + line-height: 1; + vertical-align: middle; + white-space: nowrap; +} + +.estimate-btn:hover { + background-color: #1e3a8a; + border-color: #1e3a8a; + box-shadow: 0 2px 6px rgba(0,123,255,0.4); +} + +.estimate-btn:active { + background-color: #1e3a8a; + border-color: #1e3a8a; + box-shadow: 0 1px 3px rgba(96,165,250,0.3); } @media print { @@ -248,16 +295,119 @@ textarea { border: none !important; overflow: hidden; resize: none; + background: white !important; } input[type="text"] { border: none !important; + background: white !important; + } + + .vat-badge { + background-color: #FFFF00 !important; + -webkit-print-color-adjust: exact; + print-color-adjust: exact; + } + + .highlight { + background-color: #FFFF00 !important; + -webkit-print-color-adjust: exact; + print-color-adjust: exact; + } + + .subtotal-row { + background-color: #FFFF00 !important; + -webkit-print-color-adjust: exact; + print-color-adjust: exact; + } + + .category-row { + background-color: #f0f0f0 !important; + -webkit-print-color-adjust: exact; + print-color-adjust: exact; + } + + .items-table th { + background-color: #E8E8E8 !important; + -webkit-print-color-adjust: exact; + print-color-adjust: exact; + } + + .model-header { + background-color: #90EE90 !important; + -webkit-print-color-adjust: exact; + print-color-adjust: exact; } } + + diff --git a/src/com/pms/common/utils/MailUtil.java b/src/com/pms/common/utils/MailUtil.java index 021e215..b03bf31 100644 --- a/src/com/pms/common/utils/MailUtil.java +++ b/src/com/pms/common/utils/MailUtil.java @@ -783,6 +783,21 @@ public class MailUtil { , ArrayList attachFileList , String mailType ) { + // 기본적으로 실제 제목을 mail_log에도 사용 + return sendMailWithAttachFileUTF8(fromUserId, fromEmail, toUserIdList, toEmailList, ccEmailList, bccEmailList, + important, subject, contents, attachFileList, mailType, subject); + } + + /** + * 메일 발송 (UTF-8, 파일 첨부 가능) - mail_log용 제목 별도 지정 가능 + */ + public static boolean sendMailWithAttachFileUTF8(String fromUserId, String fromEmail + , ArrayList toUserIdList, ArrayList toEmailList, ArrayList ccEmailList, ArrayList bccEmailList + , String important, String subject, String contents + , ArrayList attachFileList + , String mailType + , String subjectForLog // mail_log에 저장될 제목 (OBJID 포함) + ) { if(Constants.Mail.sendMailSwitch == false){ System.out.println("MailUtil.sendMailWithAttachFileUTF8 ::: Constants.Mail.sendMailSwitch is FALSE"); @@ -798,6 +813,11 @@ public class MailUtil { contents = CommonUtils.checkNull(contents, "empty contents"); mailType = CommonUtils.checkNull(mailType, "empty mailType"); + // mail_log용 제목이 없으면 실제 제목 사용 + if(subjectForLog == null || "".equals(subjectForLog.trim())) { + subjectForLog = subject; + } + System.out.println("MailUtil.sendMailWithAttachFileUTF8().."); System.out.println("MailUtil.sendMailWithAttachFileUTF8(fromUserId ):"+fromUserId); System.out.println("MailUtil.sendMailWithAttachFileUTF8(fromEmail ):"+fromEmail); @@ -896,18 +916,65 @@ public class MailUtil { message.setContent(multipart); message.setSentDate(new Date()); - // 메일 발송 - Transport.send(message); - System.out.println("메일 발송 성공 (UTF-8)"); + //◆◆◆ 3. db log & send mail ◆◆◆ + SqlSession sqlSession = null; + boolean mailSendSuccess = false; - return true; + Map paramMap = new HashMap(); + try{ + if(Constants.Mail.dbLogWrite){ + sqlSession = SqlMapConfig.getInstance().getSqlSession(); + + String objId = CommonUtils.createObjId(); + paramMap.put("objId" , objId); + paramMap.put("systemName" , Constants.SYSTEM_NAME); + paramMap.put("sendUserId" , fromUserId); + paramMap.put("fromAddr" , fromEmail); + paramMap.put("receptionUserId", toUserIdList.toString()); + paramMap.put("receiverTo" , toEmailList.toString()); + paramMap.put("title" , subjectForLog); // mail_log용 제목 (OBJID 포함) + paramMap.put("contents" , contents); + paramMap.put("mailType" , mailType); + + sqlSession.insert("mail.insertMailLog", paramMap); + System.out.println("메일 발송 로그 기록 (UTF-8) =============================================="); + System.out.println("paramMap >>> "+paramMap); + } + + //◆◆◆ send mail ◆◆◆ + Transport.send(message); + mailSendSuccess = true; + System.out.println("메일 발송 성공 (UTF-8)"); - } catch (Exception e) { - System.out.println("메일 발송 실패 (UTF-8): " + e.getMessage()); - e.printStackTrace(); + if(Constants.Mail.dbLogWrite){ + System.out.println("메일 발송후 paramMap >> "+paramMap); + sqlSession.update("mail.updateMailSendedSuccess", paramMap); + } + + }catch(Exception sqle){ + System.out.println("메일 발송 실패 (UTF-8): " + sqle.getMessage()); + sqle.printStackTrace(); + + if(Constants.Mail.dbLogWrite){ + try{ + sqlSession.update("mail.updateMailSendedFail", paramMap); + }catch(Exception e2){ + e2.printStackTrace(); + } + } return false; + }finally{ + if(sqlSession != null) sqlSession.close(); } + + return mailSendSuccess; + + } catch (Exception e) { + System.out.println("메일 발송 실패 (UTF-8): " + e.getMessage()); + e.printStackTrace(); + return false; } +} /** * 첨부파일 목록을 파일하나로 압축해서 반환 diff --git a/src/com/pms/salesmgmt/controller/ContractMgmtController.java b/src/com/pms/salesmgmt/controller/ContractMgmtController.java index b5d61fb..1c433d1 100644 --- a/src/com/pms/salesmgmt/controller/ContractMgmtController.java +++ b/src/com/pms/salesmgmt/controller/ContractMgmtController.java @@ -1950,22 +1950,56 @@ public class ContractMgmtController { try{ Map estimate = null; List items = new ArrayList(); + Map code_map = new HashMap(); // templateObjId가 있으면 기존 견적서 조회 (견적현황에서 클릭한 경우) if(!"".equals(templateObjId) && !"-1".equals(templateObjId)){ paramMap.put("templateObjId", templateObjId); estimate = contractMgmtService.getEstimateTemplateByObjId(paramMap); items = contractMgmtService.getEstimateTemplateItemsByTemplateObjId(paramMap); + // Map 키를 대문자로 변환 + if(estimate != null) { + estimate = CommonUtils.toUpperCaseMapKey(estimate); + } + if(items != null && items.size() > 0) { + items = CommonUtils.keyChangeUpperList((ArrayList)items); + } } // objId만 있으면 CONTRACT 정보로 견적서 신규 작성 else if(!"".equals(objId) && !"-1".equals(objId)){ - // 기존 견적서 데이터 조회 + // 계약 정보 조회 estimate = contractMgmtService.getEstimateTemplateInfo(paramMap); - items = contractMgmtService.getEstimateTemplateItems(paramMap); + // 계약 품목 조회 (CONTRACT_ITEM에서) + paramMap.put("contractObjId", objId); + items = contractMgmtService.getContractItems(paramMap); + // Map 키를 대문자로 변환 + if(estimate != null) { + estimate = CommonUtils.toUpperCaseMapKey(estimate); + } + if(items != null && items.size() > 0) { + items = CommonUtils.keyChangeUpperList((ArrayList)items); + } } - request.setAttribute("estimate", estimate); - request.setAttribute("items", items); + // 고객사 코드맵 추가 + String customer_cd = ""; + if(estimate != null) { + // templateObjId가 있으면 RECIPIENT 사용 (저장된 견적서) + if(!"".equals(templateObjId) && !"-1".equals(templateObjId)) { + if(estimate.get("RECIPIENT") != null) { + customer_cd = CommonUtils.nullToEmpty((String)estimate.get("RECIPIENT")); + } + } + // 신규 작성이면 CUSTOMER_OBJID 사용 + else if(estimate.get("CUSTOMER_OBJID") != null) { + customer_cd = CommonUtils.nullToEmpty((String)estimate.get("CUSTOMER_OBJID")); + } + } + code_map.put("customer_cd", commonService.bizMakeOptionList("", customer_cd, "common.getsupplyselect")); + + request.setAttribute("estimate", estimate); + request.setAttribute("items", items); + request.setAttribute("code_map", code_map); } catch (Exception e) { e.printStackTrace(); @@ -2040,7 +2074,7 @@ public class ContractMgmtController { } /** - * 견적서 저장 (AJAX) + * 일반 견적서 저장 (AJAX) - Template 1 * @param request * @param paramMap * @return @@ -2055,7 +2089,7 @@ public class ContractMgmtController { paramMap.put("userId", person.getUserId()); // 합계 정보 로그 (디버깅용) - System.out.println("견적서 저장 - 합계: " + paramMap.get("total_amount") + ", 원화환산: " + paramMap.get("total_amount_krw")); + System.out.println("일반 견적서 저장 - 합계: " + paramMap.get("total_amount") + ", 원화환산: " + paramMap.get("total_amount_krw")); contractMgmtService.saveEstimateTemplate(request, paramMap); @@ -2070,6 +2104,36 @@ public class ContractMgmtController { return resultMap; } + /** + * 장비 견적서 저장 (AJAX) - Template 2 + * @param request + * @param paramMap + * @return + */ + @ResponseBody + @RequestMapping(value="/contractMgmt/saveEstimate2.do", method=RequestMethod.POST) + public Map saveEstimate2(HttpServletRequest request, @RequestParam Map paramMap){ + Map resultMap = new HashMap(); + + try { + PersonBean person = (PersonBean)request.getSession().getAttribute(Constants.PERSON_BEAN); + paramMap.put("userId", person.getUserId()); + + System.out.println("장비 견적서 저장"); + + contractMgmtService.saveEstimateTemplate2(request, paramMap); + + resultMap.put("result", "success"); + + } catch (Exception e) { + e.printStackTrace(); + resultMap.put("result", "error"); + resultMap.put("message", e.getMessage()); + } + + return resultMap; + } + /** * PDF 청크 업로드 (AJAX) * @param request diff --git a/src/com/pms/salesmgmt/mapper/contractMgmt.xml b/src/com/pms/salesmgmt/mapper/contractMgmt.xml index 2bf0f36..8ad35f6 100644 --- a/src/com/pms/salesmgmt/mapper/contractMgmt.xml +++ b/src/com/pms/salesmgmt/mapper/contractMgmt.xml @@ -3860,7 +3860,14 @@ ORDER BY ASM.SUPPLY_NAME FROM CONTRACT_MGMT AS T WHERE - OBJID::VARCHAR = #{objId} + + + OBJID::VARCHAR = (SELECT CONTRACT_OBJID FROM ESTIMATE_TEMPLATE WHERE OBJID = #{templateObjId}) + + + OBJID::VARCHAR = #{objId} + + @@ -3925,6 +3932,12 @@ ORDER BY ASM.SUPPLY_NAME ET.TOTAL_AMOUNT_KRW, ET.MANAGER_NAME, ET.MANAGER_CONTACT, + ET.PART_NAME, + ET.PART_OBJID, + ET.NOTES_CONTENT, + ET.VALIDITY_PERIOD, + ET.CATEGORIES_JSON, + ET.GROUP1_SUBTOTAL, ET.WRITER, ET.REGDATE, ET.CHG_USER_ID, @@ -3950,12 +3963,19 @@ ORDER BY ASM.SUPPLY_NAME ESTIMATE_TEMPLATE ET LEFT JOIN CONTRACT_MGMT CM ON ET.CONTRACT_OBJID = CM.OBJID WHERE - ET.CONTRACT_OBJID = #{objId} - - AND ET.TEMPLATE_TYPE = #{template_type} - - ORDER BY ET.REGDATE DESC - LIMIT 1 + + + ET.OBJID = #{templateObjId} + + + ET.CONTRACT_OBJID = #{objId} + + AND ET.TEMPLATE_TYPE = #{template_type} + + ORDER BY ET.REGDATE DESC + LIMIT 1 + + @@ -3982,6 +4002,12 @@ ORDER BY ASM.SUPPLY_NAME ET.MANAGER_NAME, ET.MANAGER_CONTACT, ET.SHOW_TOTAL_ROW, + ET.PART_NAME, + ET.PART_OBJID, + ET.NOTES_CONTENT, + ET.VALIDITY_PERIOD, + ET.CATEGORIES_JSON, + ET.GROUP1_SUBTOTAL, ET.WRITER, TO_CHAR(ET.REGDATE, 'YYYY-MM-DD HH24:MI') AS REGDATE, ET.CHG_USER_ID, @@ -4196,6 +4222,67 @@ WHERE OBJID = #{template_objid} + + + INSERT INTO ESTIMATE_TEMPLATE ( + OBJID, + CONTRACT_OBJID, + TEMPLATE_TYPE, + EXECUTOR_DATE, + RECIPIENT, + PART_NAME, + PART_OBJID, + NOTES_CONTENT, + VALIDITY_PERIOD, + CATEGORIES_JSON, + GROUP1_SUBTOTAL, + TOTAL_AMOUNT, + TOTAL_AMOUNT_KRW, + WRITER, + REGDATE, + CHG_USER_ID, + CHGDATE + ) VALUES ( + #{template_objid}, + #{contract_objid}, + '2', + #{executor_date}, + #{recipient}, + #{part_name}, + #{part_objid}, + #{notes_content}, + #{validity_period}, + #{categories_json}, + #{group1_subtotal}, + #{total_amount}, + #{total_amount_krw}, + #{writer}, + NOW(), + #{chg_user_id}, + NOW() + ) + + + + + UPDATE ESTIMATE_TEMPLATE + SET + EXECUTOR_DATE = #{executor_date}, + RECIPIENT = #{recipient}, + PART_NAME = #{part_name}, + PART_OBJID = #{part_objid}, + NOTES_CONTENT = #{notes_content}, + VALIDITY_PERIOD = #{validity_period}, + CATEGORIES_JSON = #{categories_json}, + GROUP1_SUBTOTAL = #{group1_subtotal}, + TOTAL_AMOUNT = #{total_amount}, + TOTAL_AMOUNT_KRW = #{total_amount_krw}, + CHG_USER_ID = #{chg_user_id}, + CHGDATE = NOW() + WHERE + OBJID = #{template_objid} + +