일반견적서 pdf 변환 메일 첨부 수정
This commit is contained in:
@@ -43,16 +43,6 @@
|
||||
<property name="order" value="1"/>
|
||||
</bean>
|
||||
|
||||
<!-- MultipartResolver 설정 (파일 업로드) -->
|
||||
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
|
||||
<!-- 최대 업로드 크기: 50MB -->
|
||||
<property name="maxUploadSize" value="52428800"/>
|
||||
<!-- 메모리에서 처리할 최대 크기: 1MB -->
|
||||
<property name="maxInMemorySize" value="1048576"/>
|
||||
<!-- 기본 인코딩 -->
|
||||
<property name="defaultEncoding" value="UTF-8"/>
|
||||
</bean>
|
||||
|
||||
<bean id="jacksonMessageConverter" class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter"></bean>
|
||||
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
|
||||
<property name="messageConverters">
|
||||
|
||||
@@ -744,35 +744,61 @@ function fn_generatePdfAndSendMail(contractObjId, templateObjId, templateType){
|
||||
try {
|
||||
var iframeWindow = this.contentWindow;
|
||||
|
||||
// iframe 내의 PDF 생성 함수 호출
|
||||
if(typeof iframeWindow.fn_generateAndUploadPdf === 'function'){
|
||||
iframeWindow.fn_generateAndUploadPdf(function(pdfBlob){
|
||||
// iframe 제거
|
||||
// 데이터 로딩 완료를 기다림 (최대 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 생성 시작');
|
||||
|
||||
if(pdfBlob){
|
||||
// PDF Blob과 함께 메일 발송 요청
|
||||
fn_sendMailWithPdf(contractObjId, pdfBlob);
|
||||
// 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: 'PDF 생성에 실패했습니다.',
|
||||
text: '견적서 페이지 로드에 실패했습니다.',
|
||||
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();
|
||||
@@ -800,35 +826,111 @@ function fn_generatePdfAndSendMail(contractObjId, templateObjId, templateType){
|
||||
}, 30000);
|
||||
}
|
||||
|
||||
// PDF Blob과 함께 메일 발송 (FormData 사용)
|
||||
function fn_sendMailWithPdf(contractObjId, pdfBlob){
|
||||
// PDF Base64와 함께 메일 발송 (청크 방식)
|
||||
function fn_sendMailWithPdf(contractObjId, pdfBase64){
|
||||
console.log('===== 메일 발송 시작 =====');
|
||||
console.log('contractObjId:', contractObjId);
|
||||
console.log('PDF Blob 크기:', pdfBlob ? pdfBlob.size : 0, 'bytes');
|
||||
console.log('PDF Base64 길이:', pdfBase64 ? pdfBase64.length : 0);
|
||||
console.log('========================');
|
||||
|
||||
Swal.fire({
|
||||
title: '메일 발송 중...',
|
||||
text: '잠시만 기다려주세요.',
|
||||
text: 'PDF 업로드 중...',
|
||||
allowOutsideClick: false,
|
||||
onOpen: () => {
|
||||
Swal.showLoading();
|
||||
}
|
||||
});
|
||||
|
||||
// FormData 생성
|
||||
var formData = new FormData();
|
||||
formData.append('objId', contractObjId);
|
||||
formData.append('pdfFile', pdfBlob, 'estimate.pdf');
|
||||
// 청크 크기: 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: formData,
|
||||
processData: false, // FormData 사용 시 필수
|
||||
contentType: false, // FormData 사용 시 필수
|
||||
data: {
|
||||
objId: contractObjId,
|
||||
pdfSessionId: sessionId
|
||||
},
|
||||
dataType: "json",
|
||||
timeout: 60000, // 60초 타임아웃
|
||||
timeout: 60000,
|
||||
success: function(data){
|
||||
console.log('메일 발송 응답:', data);
|
||||
Swal.close();
|
||||
@@ -839,7 +941,6 @@ function fn_sendMailWithPdf(contractObjId, pdfBlob){
|
||||
icon: 'success',
|
||||
confirmButtonText: '확인'
|
||||
}).then(() => {
|
||||
// 메일 발송 상태 업데이트를 위해 목록 새로고침
|
||||
fn_search();
|
||||
});
|
||||
} else {
|
||||
@@ -854,10 +955,9 @@ function fn_sendMailWithPdf(contractObjId, pdfBlob){
|
||||
error: function(xhr, status, error){
|
||||
Swal.close();
|
||||
console.error("메일 발송 오류:", xhr, status, error);
|
||||
console.error("xhr.responseText:", xhr.responseText);
|
||||
Swal.fire({
|
||||
title: '오류',
|
||||
text: '메일 발송 중 시스템 오류가 발생했습니다: ' + status,
|
||||
text: '메일 발송 중 시스템 오류가 발생했습니다.',
|
||||
icon: 'error',
|
||||
confirmButtonText: '확인'
|
||||
});
|
||||
|
||||
@@ -819,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);
|
||||
@@ -1004,9 +1008,14 @@ function fn_loadTemplateData(templateObjId){
|
||||
|
||||
// 결재상태에 따라 버튼 제어
|
||||
fn_controlButtons();
|
||||
|
||||
// 데이터 로딩 완료 플래그 설정
|
||||
window.dataLoaded = true;
|
||||
console.log("견적서 데이터 로딩 완료");
|
||||
}
|
||||
} else {
|
||||
console.error("데이터 로드 실패:", data);
|
||||
window.dataLoaded = false;
|
||||
Swal.fire("데이터를 불러오는데 실패했습니다.");
|
||||
}
|
||||
},
|
||||
@@ -1340,19 +1349,43 @@ function fn_generatePdf() {
|
||||
// 버튼 영역 임시 숨김
|
||||
$('.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, // 고해상도
|
||||
scale: 2, // 적절한 해상도 (파일 크기 최적화)
|
||||
useCORS: true,
|
||||
logging: true, // 디버깅을 위해 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를 이미지로 변환
|
||||
var imgData = canvas.toDataURL('image/png');
|
||||
// Canvas를 JPEG 이미지로 변환 (PNG보다 파일 크기 작음)
|
||||
var imgData = canvas.toDataURL('image/jpeg', 0.85); // 85% 품질
|
||||
|
||||
// PDF 생성 (A4 크기)
|
||||
var pdf = new jsPDF('p', 'mm', 'a4');
|
||||
@@ -1362,15 +1395,15 @@ function fn_generatePdf() {
|
||||
var heightLeft = imgHeight;
|
||||
var position = 0;
|
||||
|
||||
// 첫 페이지 추가
|
||||
pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight);
|
||||
// 첫 페이지 추가 (JPEG 압축)
|
||||
pdf.addImage(imgData, 'JPEG', 0, position, imgWidth, imgHeight, undefined, 'FAST');
|
||||
heightLeft -= pageHeight;
|
||||
|
||||
// 페이지가 넘어가면 추가 페이지 생성
|
||||
while (heightLeft >= 0) {
|
||||
position = heightLeft - imgHeight;
|
||||
pdf.addPage();
|
||||
pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight);
|
||||
pdf.addImage(imgData, 'JPEG', 0, position, imgWidth, imgHeight, undefined, 'FAST');
|
||||
heightLeft -= pageHeight;
|
||||
}
|
||||
|
||||
@@ -1409,7 +1442,7 @@ function fn_generatePdf() {
|
||||
});
|
||||
}
|
||||
|
||||
// PDF를 Blob으로 생성하여 서버로 전송하는 함수
|
||||
// PDF를 Base64로 생성하여 서버로 전송하는 함수
|
||||
function fn_generateAndUploadPdf(callback) {
|
||||
console.log('fn_generateAndUploadPdf 호출됨');
|
||||
|
||||
@@ -1425,21 +1458,46 @@ function fn_generateAndUploadPdf(callback) {
|
||||
// 버튼 영역 임시 숨김
|
||||
$('.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,
|
||||
scale: 2, // 적절한 해상도 (파일 크기 최적화)
|
||||
useCORS: true,
|
||||
logging: 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를 이미지로 변환
|
||||
var imgData = canvas.toDataURL('image/png');
|
||||
// Canvas를 JPEG 이미지로 변환 (PNG보다 파일 크기 작음)
|
||||
var imgData = canvas.toDataURL('image/jpeg', 0.85); // 85% 품질
|
||||
console.log('이미지 변환 완료');
|
||||
|
||||
// PDF 생성
|
||||
@@ -1450,25 +1508,26 @@ function fn_generateAndUploadPdf(callback) {
|
||||
var heightLeft = imgHeight;
|
||||
var position = 0;
|
||||
|
||||
pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight);
|
||||
// JPEG 이미지 추가 (압축됨)
|
||||
pdf.addImage(imgData, 'JPEG', 0, position, imgWidth, imgHeight, undefined, 'FAST');
|
||||
heightLeft -= pageHeight;
|
||||
|
||||
while (heightLeft >= 0) {
|
||||
position = heightLeft - imgHeight;
|
||||
pdf.addPage();
|
||||
pdf.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight);
|
||||
pdf.addImage(imgData, 'JPEG', 0, position, imgWidth, imgHeight, undefined, 'FAST');
|
||||
heightLeft -= pageHeight;
|
||||
}
|
||||
|
||||
console.log('PDF 생성 완료');
|
||||
|
||||
// PDF를 Blob으로 변환 (Base64 대신 바이너리)
|
||||
var pdfBlob = pdf.output('blob');
|
||||
console.log('PDF Blob 생성 완료, 크기:', pdfBlob.size, 'bytes');
|
||||
// PDF를 Base64로 변환
|
||||
var pdfBase64 = pdf.output('dataurlstring').split(',')[1];
|
||||
console.log('PDF Base64 생성 완료, 길이:', pdfBase64.length);
|
||||
|
||||
// 콜백 함수 호출 (메일 발송 등에 사용)
|
||||
if(callback && typeof callback === 'function') {
|
||||
callback(pdfBlob);
|
||||
callback(pdfBase64);
|
||||
}
|
||||
} catch(pdfError) {
|
||||
console.error('PDF 생성 중 오류:', pdfError);
|
||||
|
||||
Reference in New Issue
Block a user