diff --git a/WebContent/WEB-INF/dispatcher-servlet.xml b/WebContent/WEB-INF/dispatcher-servlet.xml index 8a5b95d..e5406f5 100644 --- a/WebContent/WEB-INF/dispatcher-servlet.xml +++ b/WebContent/WEB-INF/dispatcher-servlet.xml @@ -42,6 +42,7 @@ + diff --git a/WebContent/WEB-INF/view/contractMgmt/estimateList_new.jsp b/WebContent/WEB-INF/view/contractMgmt/estimateList_new.jsp index 1948ab4..5d0bfa1 100644 --- a/WebContent/WEB-INF/view/contractMgmt/estimateList_new.jsp +++ b/WebContent/WEB-INF/view/contractMgmt/estimateList_new.jsp @@ -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 = $('', { + 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 { diff --git a/WebContent/WEB-INF/view/contractMgmt/estimateTemplate1.jsp b/WebContent/WEB-INF/view/contractMgmt/estimateTemplate1.jsp index 361887f..fe38b37 100644 --- a/WebContent/WEB-INF/view/contractMgmt/estimateTemplate1.jsp +++ b/WebContent/WEB-INF/view/contractMgmt/estimateTemplate1.jsp @@ -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 += ''; itemsHtml += ''; + // 참조사항 행 추가 + itemsHtml += ''; + itemsHtml += ''; + itemsHtml += '<참조사항>'; + itemsHtml += ''; + itemsHtml += ''; + itemsHtml += ''; + itemsHtml += ''; + itemsHtml += ''; + itemsHtml += ''; + + // 하단 회사명 행 추가 + itemsHtml += ''; + itemsHtml += ''; + itemsHtml += '㈜알피에스'; + itemsHtml += ''; + itemsHtml += ''; + // 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() { + @@ -1135,6 +1165,7 @@ function fn_save() { + ㈊알피에스RPS CO., LTD대표이사이동준 - - 담당자 : - 연락처 : - + @@ -1173,15 +1201,19 @@ function fn_save() { - + + + 견적을 요청해 주셔서 대단히 감사합니다. 하기와 같이 견적서를 제출합니다. - - - - 부가세 별도 + + + 담당자 : + 연락처 : + 부가세 별도 + @@ -1245,31 +1277,274 @@ function fn_save() { + + + + <참조사항> + + + + + + + + + + ㈜알피에스 + + - - - - <참조사항> - - - - - - - - 행 추가 인쇄 + PDF 다운로드 저장 닫기 + + + + +