feature/new-work #53
@@ -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">
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;"><참조사항></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;"><참조사항></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"><참조사항></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>
|
||||
|
||||
|
||||
@@ -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 압축파일명
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;'><비고></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;'><비고></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;'><참조사항></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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user