Merge remote-tracking branch 'origin/main' into V2025121808

This commit is contained in:
leeheejin
2025-12-19 11:23:36 +09:00
9 changed files with 855 additions and 44 deletions

View File

@@ -201,8 +201,12 @@ input.date_icon {
}
</style>
<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/2.5.1/jspdf.umd.min.js"></script>
<script type="text/javascript">
var grid;
// PDF 생성용 플래그
window.dataLoaded = false;
$(document).ready(function(){
@@ -526,6 +530,199 @@ var gridFn = {
$("#TOTAL_PRICE_ALL").val(numberWithCommas(totalWithVat));
}
}
// 라이브러리 로드 완료 후 dataLoaded 플래그 설정
$(window).on('load', function() {
setTimeout(function() {
if(typeof html2canvas !== 'undefined' && typeof jspdf !== 'undefined') {
window.dataLoaded = true;
console.log('발주서 일반양식 - dataLoaded = true');
} else {
// jsPDF 전역 객체 확인
if(typeof html2canvas !== 'undefined' && typeof window.jspdf !== 'undefined') {
window.dataLoaded = true;
console.log('발주서 일반양식 - dataLoaded = true (jspdf)');
} else {
console.log('라이브러리 로드 대기중...');
setTimeout(function() {
window.dataLoaded = true;
console.log('발주서 일반양식 - dataLoaded = true (지연)');
}, 1000);
}
}
}, 500);
});
// PDF를 Base64로 생성하여 콜백으로 전달하는 함수
function fn_generateAndUploadPdf(callback) {
console.log('fn_generateAndUploadPdf 호출됨 (일반 발주서)');
// 라이브러리 로드 확인
if(typeof html2canvas === 'undefined') {
console.error('html2canvas 라이브러리가 로드되지 않았습니다.');
if(callback && typeof callback === 'function') {
callback(null);
}
return;
}
var jsPDF = window.jspdf ? window.jspdf.jsPDF : null;
if(!jsPDF) {
console.error('jsPDF 라이브러리가 로드되지 않았습니다.');
if(callback && typeof callback === 'function') {
callback(null);
}
return;
}
// 버튼 영역 임시 숨김
$('#btnSave, #btnDown, input[value="닫기"]').closest('div').hide();
// select2 컨테이너 숨김 (중복 표시 방지)
$('.select2-container').hide();
// input/select 값을 텍스트로 변환 (html2canvas가 input value를 캡처하지 못하는 문제 해결)
var inputBackups = [];
// input 요소 처리 (form1 내부 + 그리드 포함)
$('input[type="text"], input:not([type])').each(function(){
var $input = $(this);
var value = $input.val();
if(value) {
inputBackups.push({
element: $input,
html: $input[0].outerHTML,
parent: $input.parent()
});
var $span = $('<span class="pdf-temp-span">').text(value).css({
'display': 'inline-block',
'white-space': 'nowrap',
'text-align': $input.css('text-align') || 'left',
'font-size': $input.css('font-size') || '12px',
'font-weight': $input.css('font-weight') || 'normal',
'padding': '2px 5px',
'color': $input.css('color') || '#000',
'background': 'transparent'
});
$input.replaceWith($span);
inputBackups[inputBackups.length - 1].span = $span;
}
});
// select 요소 처리 (select2 포함)
$('select').each(function(){
var $select = $(this);
var value = $select.find('option:selected').text();
if(value && value.trim() !== '' && value !== '선택') {
inputBackups.push({
element: $select,
html: $select[0].outerHTML,
parent: $select.parent(),
wasHidden: $select.is(':hidden')
});
var $span = $('<span class="pdf-temp-span">').text(value).css({
'display': 'inline-block',
'white-space': 'nowrap',
'text-align': 'left',
'font-size': '12px',
'padding': '2px 5px',
'color': '#000',
'background': 'transparent'
});
$select.after($span).hide();
inputBackups[inputBackups.length - 1].span = $span;
}
});
// 캡처할 영역 (form1)
var captureElement = document.getElementById('form1');
// html2canvas로 캡처
html2canvas(captureElement, {
scale: 2,
useCORS: true,
logging: false,
backgroundColor: '#ffffff',
windowWidth: captureElement.scrollWidth,
windowHeight: captureElement.scrollHeight
}).then(function(canvas) {
console.log('Canvas 캡처 완료');
// 임시 span 제거 및 원본 복원
$('.pdf-temp-span').remove();
for(var i = 0; i < inputBackups.length; i++) {
var backup = inputBackups[i];
if(backup.element && backup.element.is('select')) {
// select는 숨겨둔 것만 다시 표시
backup.element.show();
} else if(backup.span && backup.parent) {
// input은 span을 원본으로 교체
backup.span.replaceWith(backup.html);
}
}
// select2 컨테이너 다시 표시
$('.select2-container').show();
// 버튼 영역 다시 표시
$('#btnSave, #btnDown, input[value="닫기"]').closest('div').show();
try {
// Canvas를 JPEG 이미지로 변환
var imgData = canvas.toDataURL('image/jpeg', 0.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;
// 이미지 추가
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) {
// span을 다시 input으로 복원
for(var i = 0; i < inputBackups.length; i++) {
var backup = inputBackups[i];
if(backup.span) {
backup.span.replaceWith(backup.html);
}
}
$('#btnSave, #btnDown, input[value="닫기"]').closest('div').show();
console.error('Canvas 캡처 오류:', error);
if(callback && typeof callback === 'function') {
callback(null);
}
});
}
</script>
</head>
<body>

View File

@@ -180,8 +180,12 @@ input.date_icon {
}
</style>
<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/2.5.1/jspdf.umd.min.js"></script>
<script type="text/javascript">
var grid;
// PDF 생성용 플래그
window.dataLoaded = false;
$(document).ready(function(){
@@ -484,6 +488,198 @@ var gridFn = {
$("#TOTAL_SUPPLY_PRICE").val(numberWithCommas(totalSupplyPrice));
}
}
// 라이브러리 로드 완료 후 dataLoaded 플래그 설정
$(window).on('load', function() {
setTimeout(function() {
if(typeof html2canvas !== 'undefined' && typeof jspdf !== 'undefined') {
window.dataLoaded = true;
console.log('발주서 외주양식 - dataLoaded = true');
} else {
if(typeof html2canvas !== 'undefined' && typeof window.jspdf !== 'undefined') {
window.dataLoaded = true;
console.log('발주서 외주양식 - dataLoaded = true (jspdf)');
} else {
console.log('라이브러리 로드 대기중...');
setTimeout(function() {
window.dataLoaded = true;
console.log('발주서 외주양식 - dataLoaded = true (지연)');
}, 1000);
}
}
}, 500);
});
// PDF를 Base64로 생성하여 콜백으로 전달하는 함수
function fn_generateAndUploadPdf(callback) {
console.log('fn_generateAndUploadPdf 호출됨 (외주 발주서)');
// 라이브러리 로드 확인
if(typeof html2canvas === 'undefined') {
console.error('html2canvas 라이브러리가 로드되지 않았습니다.');
if(callback && typeof callback === 'function') {
callback(null);
}
return;
}
var jsPDF = window.jspdf ? window.jspdf.jsPDF : null;
if(!jsPDF) {
console.error('jsPDF 라이브러리가 로드되지 않았습니다.');
if(callback && typeof callback === 'function') {
callback(null);
}
return;
}
// 버튼 영역 임시 숨김
$('.btn-area, #btnSave, #btnDown, input[value="닫기"]').closest('div').hide();
// select2 컨테이너 숨김 (중복 표시 방지)
$('.select2-container').hide();
// input/select 값을 텍스트로 변환 (html2canvas가 input value를 캡처하지 못하는 문제 해결)
var inputBackups = [];
// input 요소 처리 (form1 내부 + 그리드 포함)
$('input[type="text"], input:not([type])').each(function(){
var $input = $(this);
var value = $input.val();
if(value) {
inputBackups.push({
element: $input,
html: $input[0].outerHTML,
parent: $input.parent()
});
var $span = $('<span class="pdf-temp-span">').text(value).css({
'display': 'inline-block',
'white-space': 'nowrap',
'text-align': $input.css('text-align') || 'left',
'font-size': $input.css('font-size') || '12px',
'font-weight': $input.css('font-weight') || 'normal',
'padding': '2px 5px',
'color': $input.css('color') || '#000',
'background': 'transparent'
});
$input.replaceWith($span);
inputBackups[inputBackups.length - 1].span = $span;
}
});
// select 요소 처리 (select2 포함)
$('select').each(function(){
var $select = $(this);
var value = $select.find('option:selected').text();
if(value && value.trim() !== '' && value !== '선택') {
inputBackups.push({
element: $select,
html: $select[0].outerHTML,
parent: $select.parent(),
wasHidden: $select.is(':hidden')
});
var $span = $('<span class="pdf-temp-span">').text(value).css({
'display': 'inline-block',
'white-space': 'nowrap',
'text-align': 'left',
'font-size': '12px',
'padding': '2px 5px',
'color': '#000',
'background': 'transparent'
});
$select.after($span).hide();
inputBackups[inputBackups.length - 1].span = $span;
}
});
// 캡처할 영역 (form1)
var captureElement = document.getElementById('form1');
// html2canvas로 캡처
html2canvas(captureElement, {
scale: 2,
useCORS: true,
logging: false,
backgroundColor: '#ffffff',
windowWidth: captureElement.scrollWidth,
windowHeight: captureElement.scrollHeight
}).then(function(canvas) {
console.log('Canvas 캡처 완료');
// 임시 span 제거 및 원본 복원
$('.pdf-temp-span').remove();
for(var i = 0; i < inputBackups.length; i++) {
var backup = inputBackups[i];
if(backup.element && backup.element.is('select')) {
// select는 숨겨둔 것만 다시 표시
backup.element.show();
} else if(backup.span && backup.parent) {
// input은 span을 원본으로 교체
backup.span.replaceWith(backup.html);
}
}
// select2 컨테이너 다시 표시
$('.select2-container').show();
// 버튼 영역 다시 표시
$('.btn-area, #btnSave, #btnDown, input[value="닫기"]').closest('div').show();
try {
// Canvas를 JPEG 이미지로 변환
var imgData = canvas.toDataURL('image/jpeg', 0.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;
// 이미지 추가
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) {
// span을 다시 input으로 복원
for(var i = 0; i < inputBackups.length; i++) {
var backup = inputBackups[i];
if(backup.span) {
backup.span.replaceWith(backup.html);
}
}
$('.btn-area, #btnSave, #btnDown, input[value="닫기"]').closest('div').show();
console.error('Canvas 캡처 오류:', error);
if(callback && typeof callback === 'function') {
callback(null);
}
});
}
</script>
</head>
<body>

View File

@@ -190,12 +190,13 @@ String purchaseOrderObjId = request.getParameter("purchaseOrderObjId");
</div>
<div class="attachment-status">
<i class="fa fa-file-excel-o"></i>
<strong>첨부파일:</strong> 발주서 및 도면 파일이 자동으로 첨부됩니다.
<i class="fa fa-file-pdf-o"></i>
<strong>첨부파일:</strong> 발주서(PDF) 및 도면 파일이 자동으로 첨부됩니다.
</div>
<form id="mailForm">
<input type="hidden" id="purchaseOrderObjId" name="purchaseOrderObjId" value="<%=purchaseOrderObjId%>"/>
<input type="hidden" id="pdfSessionId" name="pdfSessionId" value=""/>
<!-- 공급업체 담당자 선택 -->
<div class="form-group">
@@ -261,6 +262,9 @@ function fn_loadPurchaseOrderInfo(){
if(data.result === "success" && data.purchaseOrderInfo){
purchaseOrderInfo = data.purchaseOrderInfo;
// 양식 타입 확인 (디버깅용)
console.log("발주서 양식 타입 (FORM_TYPE):", purchaseOrderInfo.FORM_TYPE);
// 발주서 정보 표시
$("#displayPoNo").text(fnc_checkNull(purchaseOrderInfo.PURCHASE_ORDER_NO) || '-');
$("#displayPartnerName").text(fnc_checkNull(purchaseOrderInfo.PARTNER_NAME) || '-');
@@ -471,24 +475,197 @@ function fn_sendMail(){
cancelButtonText: '취소'
}).then((result) => {
if(result.isConfirmed){
fn_submitMailForm();
// PDF 생성 및 발송 시작
fn_generatePdfAndSend();
}
});
}
// 메일 발송 요청
function fn_submitMailForm(){
// PDF 생성 및 발송
function fn_generatePdfAndSend(){
var purchaseOrderObjId = $("#purchaseOrderObjId").val();
Swal.fire({
title: '발송 중...',
text: '발주서 메일을 발송하고 있습니다.',
title: 'PDF 생성 중...',
text: '발주서를 PDF로 변환하고 있습니다.',
allowOutsideClick: false,
onOpen: () => {
Swal.showLoading();
}
});
// 발주서 양식 타입 확인 (일반/외주)
var formType = fnc_checkNull(purchaseOrderInfo.FORM_TYPE) || 'general';
var url = "";
if(formType === 'outsourcing') {
url = "/purchaseOrder/purchaseOrderFormPopup_outsourcing.do?actType=VIEW&PURCHASE_ORDER_MASTER_OBJID=" + purchaseOrderObjId;
} else {
url = "/purchaseOrder/purchaseOrderFormPopup_general.do?actType=VIEW&PURCHASE_ORDER_MASTER_OBJID=" + purchaseOrderObjId;
}
// 숨겨진 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;
// 데이터 로딩 완료 대기 (최대 60초)
var checkDataLoaded = function(attempts) {
if(attempts > 600) {
$('#pdfGeneratorFrame').remove();
Swal.close();
Swal.fire({
title: '타임아웃',
text: '발주서 데이터 로딩 시간이 초과되었습니다. (60초)',
icon: 'error'
});
return;
}
if(iframeWindow.dataLoaded === true) {
// PDF 생성 함수 호출
if(typeof iframeWindow.fn_generateAndUploadPdf === 'function'){
iframeWindow.fn_generateAndUploadPdf(function(pdfBase64){
$('#pdfGeneratorFrame').remove();
if(pdfBase64){
// PDF 업로드 후 메일 발송
fn_uploadPdfAndSendMail(purchaseOrderObjId, pdfBase64);
} else {
Swal.close();
Swal.fire({
title: '오류',
text: 'PDF 생성에 실패했습니다.',
icon: 'error'
});
}
});
} else {
$('#pdfGeneratorFrame').remove();
Swal.close();
Swal.fire({
title: '오류',
text: '발주서 페이지 로드에 실패했습니다.',
icon: 'error'
});
}
} else {
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'
});
}
});
// 타임아웃 설정 (120초)
setTimeout(function(){
if($('#pdfGeneratorFrame').length > 0){
$('#pdfGeneratorFrame').remove();
Swal.close();
Swal.fire({
title: '타임아웃',
text: 'PDF 생성 시간이 초과되었습니다. (120초)',
icon: 'error'
});
}
}, 120000);
}
// PDF 업로드 및 메일 발송
function fn_uploadPdfAndSendMail(purchaseOrderObjId, pdfBase64){
Swal.update({
text: 'PDF 업로드 중...'
});
// 청크 업로드
var chunkSize = 100 * 1024;
var totalChunks = Math.ceil(pdfBase64.length / chunkSize);
var uploadedChunks = 0;
var sessionId = 'pdf_po_' + purchaseOrderObjId + '_' + new Date().getTime();
function uploadChunk(chunkIndex) {
var start = chunkIndex * chunkSize;
var end = Math.min(start + chunkSize, pdfBase64.length);
var chunk = pdfBase64.substring(start, end);
$.ajax({
url: "/purchaseOrder/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 {
// 모든 청크 업로드 완료, 메일 발송
$("#pdfSessionId").val(sessionId);
fn_submitMailForm();
}
} else {
Swal.close();
Swal.fire({
title: '업로드 실패',
text: 'PDF 업로드 중 오류가 발생했습니다.',
icon: 'error'
});
}
},
error: function(){
Swal.close();
Swal.fire({
title: '오류',
text: 'PDF 업로드 중 시스템 오류가 발생했습니다.',
icon: 'error'
});
}
});
}
uploadChunk(0);
}
// 메일 발송 요청
function fn_submitMailForm(){
Swal.update({
text: '메일 발송 중...'
});
var formData = {
objId: $("#purchaseOrderObjId").val(),
pdfSessionId: $("#pdfSessionId").val(),
toEmails: $("#toEmails").val(),
ccEmails: $("#ccEmails").val(),
subject: $("#subject").val(),