diff --git a/.playwright-mcp/structure_popup_main.png b/.playwright-mcp/structure_popup_main.png new file mode 100644 index 0000000..d398cfc Binary files /dev/null and b/.playwright-mcp/structure_popup_main.png differ diff --git a/WebContent/WEB-INF/classes/com/pms/mapper/productionplanning.xml b/WebContent/WEB-INF/classes/com/pms/mapper/productionplanning.xml index 2926e4e..2edcdb4 100644 --- a/WebContent/WEB-INF/classes/com/pms/mapper/productionplanning.xml +++ b/WebContent/WEB-INF/classes/com/pms/mapper/productionplanning.xml @@ -2985,14 +2985,6 @@ WHERE OBJID = #{projectMgmtObjid} - - - UPDATE PROJECT_MGMT - SET - PART_OBJID = NULL - WHERE OBJID = #{projectMgmtObjid} - - SELECT @@ -3011,326 +3003,4 @@ LEFT JOIN USER_INFO UI ON UI.USER_ID = T.WRITER WHERE T.OBJID::VARCHAR = #{objid} - - - - SELECT - PM.OBJID, - PM.CONTRACT_OBJID, - PM.PROJECT_NO, - PM.PART_NO, - PM.PART_NAME, - PM.PART_OBJID, - PM.QUANTITY, - PM.REQ_DEL_DATE, - PM.BOM_REPORT_OBJID, - CM.CUSTOMER_OBJID, - (SELECT SUPPLY_NAME FROM SUPPLY_MNG WHERE OBJID::VARCHAR = CM.CUSTOMER_OBJID LIMIT 1) AS CUSTOMER_NAME, - CM.PRODUCT, - (SELECT CODE_NAME FROM COMM_CODE WHERE CODE_ID = CM.PRODUCT LIMIT 1) AS PRODUCT_NAME - FROM - PROJECT_MGMT PM - LEFT JOIN CONTRACT_MGMT CM ON CM.OBJID = PM.CONTRACT_OBJID - WHERE PM.OBJID = #{objid} - - - - - WITH RECURSIVE BOM_TREE AS ( - -- 최상위 레벨 (LEVEL = 0) - SELECT - BPQ.OBJID, - BPQ.BOM_REPORT_OBJID, - BPQ.PARENT_OBJID, - BPQ.CHILD_OBJID, - BPQ.PARENT_PART_NO, - BPQ.PART_NO, - BPQ.QTY, - 0 AS LEVEL, - CAST(BPQ.SEQ AS VARCHAR) AS SORT_PATH, - PM.PART_NO AS PART_NO_TITLE, - PM.PART_NAME, - PM.MATERIAL, - PM.SPEC, - PM.UNIT, - PM.WEIGHT, - PM.REVISION, - '' AS HEAT_TREAT_HARDNESS, - '' AS HEAT_TREAT_METHOD, - '' AS SURFACE_TREATMENT, - '' AS SUPPLIER_NAME, - '' AS CATEGORY_NAME, - '' AS RAW_MATERIAL, - '' AS SIZE, - 0 AS ORDER_QTY, - BPQ.QTY AS QUANTITY, - 0 AS PRODUCTION_QTY, - '' AS PROCESSOR_NAME, - '' AS PROCESS_DUE_DATE, - '' AS GRINDING_DUE_DATE, - -- 파일 첨부 여부 (3D, 2D, PDF) - CASE WHEN EXISTS( - SELECT 1 FROM attach_file_info AF - WHERE AF.DOC_TYPE = 'PART_3D' - AND AF.OBJID = PM.OBJID::NUMERIC - AND AF.STATUS = 'ACTIVE' - ) THEN 'Y' ELSE 'N' END AS HAS_3D, - CASE WHEN EXISTS( - SELECT 1 FROM attach_file_info AF - WHERE AF.DOC_TYPE = 'PART_2D' - AND AF.OBJID = PM.OBJID::NUMERIC - AND AF.STATUS = 'ACTIVE' - ) THEN 'Y' ELSE 'N' END AS HAS_2D, - CASE WHEN EXISTS( - SELECT 1 FROM attach_file_info AF - WHERE AF.DOC_TYPE = 'PART_PDF' - AND AF.OBJID = PM.OBJID::NUMERIC - AND AF.STATUS = 'ACTIVE' - ) THEN 'Y' ELSE 'N' END AS HAS_PDF - FROM - BOM_PART_QTY BPQ - LEFT JOIN PART_MNG PM ON PM.OBJID::VARCHAR = BPQ.PART_NO - WHERE - BPQ.BOM_REPORT_OBJID::VARCHAR = #{bomReportObjid} - AND (BPQ.PARENT_OBJID IS NULL OR BPQ.PARENT_OBJID = '') - - UNION ALL - - -- 하위 레벨 (재귀) - SELECT - BPQ.OBJID, - BPQ.BOM_REPORT_OBJID, - BPQ.PARENT_OBJID, - BPQ.CHILD_OBJID, - BPQ.PARENT_PART_NO, - BPQ.PART_NO, - BPQ.QTY, - BT.LEVEL + 1, - BT.SORT_PATH || '-' || CAST(BPQ.SEQ AS VARCHAR), - PM.PART_NO AS PART_NO_TITLE, - PM.PART_NAME, - PM.MATERIAL, - PM.SPEC, - PM.UNIT, - PM.WEIGHT, - PM.REVISION, - '' AS HEAT_TREAT_HARDNESS, - '' AS HEAT_TREAT_METHOD, - '' AS SURFACE_TREATMENT, - '' AS SUPPLIER_NAME, - '' AS CATEGORY_NAME, - '' AS RAW_MATERIAL, - '' AS SIZE, - 0 AS ORDER_QTY, - BPQ.QTY AS QUANTITY, - 0 AS PRODUCTION_QTY, - '' AS PROCESSOR_NAME, - '' AS PROCESS_DUE_DATE, - '' AS GRINDING_DUE_DATE, - CASE WHEN EXISTS( - SELECT 1 FROM attach_file_info AF - WHERE AF.DOC_TYPE = 'PART_3D' - AND AF.OBJID = PM.OBJID::NUMERIC - AND AF.STATUS = 'ACTIVE' - ) THEN 'Y' ELSE 'N' END AS HAS_3D, - CASE WHEN EXISTS( - SELECT 1 FROM attach_file_info AF - WHERE AF.DOC_TYPE = 'PART_2D' - AND AF.OBJID = PM.OBJID::NUMERIC - AND AF.STATUS = 'ACTIVE' - ) THEN 'Y' ELSE 'N' END AS HAS_2D, - CASE WHEN EXISTS( - SELECT 1 FROM attach_file_info AF - WHERE AF.DOC_TYPE = 'PART_PDF' - AND AF.OBJID = PM.OBJID::NUMERIC - AND AF.STATUS = 'ACTIVE' - ) THEN 'Y' ELSE 'N' END AS HAS_PDF - FROM - BOM_PART_QTY BPQ - INNER JOIN BOM_TREE BT ON BT.CHILD_OBJID = BPQ.PARENT_OBJID - LEFT JOIN PART_MNG PM ON PM.OBJID::VARCHAR = BPQ.PART_NO - ) - SELECT - OBJID, - BOM_REPORT_OBJID, - PARENT_OBJID, - CHILD_OBJID, - PARENT_PART_NO, - PART_NO, - QTY, - LEVEL, - PART_NO_TITLE, - PART_NAME, - QTY AS AGGREGATE_QTY, - HAS_3D, - HAS_2D, - HAS_PDF, - MATERIAL, - HEAT_TREAT_HARDNESS, - HEAT_TREAT_METHOD, - SURFACE_TREATMENT, - SUPPLIER_NAME, - CATEGORY_NAME, - RAW_MATERIAL, - SIZE, - ORDER_QTY, - QUANTITY, - PRODUCTION_QTY, - PROCESSOR_NAME, - PROCESS_DUE_DATE, - GRINDING_DUE_DATE - FROM - BOM_TREE - ORDER BY - SORT_PATH - - - - - INSERT INTO m_bom_data ( - objid, - project_mgmt_objid, - bom_report_objid, - parent_objid, - child_objid, - parent_part_no, - part_no, - part_name, - qty, - aggregate_qty, - level, - material, - heat_treat_hardness, - heat_treat_method, - surface_treatment, - supplier_name, - category_name, - raw_material, - size, - order_qty, - quantity, - production_qty, - processor_name, - process_due_date, - grinding_due_date, - writer, - regdate - ) VALUES ( - #{OBJID}::NUMERIC, - #{projectMgmtObjid}::NUMERIC, - #{BOM_REPORT_OBJID}::NUMERIC, - - - #{PARENT_OBJID}::NUMERIC, - - - NULL, - - - - - #{CHILD_OBJID}::NUMERIC, - - - NULL, - - - #{PARENT_PART_NO}, - #{PART_NO}, - #{PART_NAME}, - #{QTY}::NUMERIC, - #{AGGREGATE_QTY}::NUMERIC, - #{LEVEL}::INTEGER, - #{MATERIAL}, - #{HEAT_TREAT_HARDNESS}, - #{HEAT_TREAT_METHOD}, - #{SURFACE_TREATMENT}, - #{SUPPLIER_NAME}, - #{CATEGORY_NAME}, - #{RAW_MATERIAL}, - #{SIZE}, - #{ORDER_QTY}::NUMERIC, - #{QUANTITY}::NUMERIC, - #{PRODUCTION_QTY}::NUMERIC, - #{PROCESSOR_NAME}, - #{PROCESS_DUE_DATE}, - #{GRINDING_DUE_DATE}, - #{writer}, - NOW() - ) - - - - - UPDATE project_mgmt - SET - mbom_version = COALESCE(mbom_version, 0) + 1, - mbom_regdate = NOW(), - mbom_writer = #{writer}, - mbom_status = 'Y' - WHERE objid = #{projectMgmtObjid} - - - - - UPDATE m_bom_data - SET - STATUS = 'DELETED', - EDIT_DATE = NOW() - WHERE PROJECT_MGMT_OBJID = #{projectMgmtObjid}::NUMERIC - AND STATUS = 'ACTIVE' - - - - - UPDATE project_mgmt - SET - mbom_status = NULL, - mbom_version = NULL, - mbom_regdate = NULL, - mbom_writer = NULL - WHERE objid = #{projectMgmtObjid} - - - - - SELECT - OBJID, - PROJECT_MGMT_OBJID, - BOM_REPORT_OBJID, - PARENT_OBJID, - CHILD_OBJID, - PARENT_PART_NO, - PART_NO, - PART_NAME AS PART_NO_TITLE, - PART_NAME, - QTY, - AGGREGATE_QTY, - LEVEL, - MATERIAL, - HEAT_TREAT_HARDNESS, - HEAT_TREAT_METHOD, - SURFACE_TREATMENT, - SUPPLIER_NAME, - CATEGORY_NAME, - RAW_MATERIAL, - SIZE, - ORDER_QTY, - QUANTITY, - PRODUCTION_QTY, - PROCESSOR_NAME, - PROCESS_DUE_DATE, - GRINDING_DUE_DATE, - -- 파일 첨부 여부 확인 (저장된 경우) - 'N' AS HAS_3D, - 'N' AS HAS_2D, - 'N' AS HAS_PDF - FROM - m_bom_data - WHERE - PROJECT_MGMT_OBJID = #{projectMgmtObjid}::NUMERIC - AND STATUS = 'ACTIVE' - ORDER BY - LEVEL, OBJID - diff --git a/WebContent/WEB-INF/dispatcher-servlet.xml b/WebContent/WEB-INF/dispatcher-servlet.xml index 0769b0a..8a5b95d 100644 --- a/WebContent/WEB-INF/dispatcher-servlet.xml +++ b/WebContent/WEB-INF/dispatcher-servlet.xml @@ -42,17 +42,6 @@ - - - - - - - - - - - diff --git a/WebContent/WEB-INF/view/contractMgmt/estimateList_new.jsp b/WebContent/WEB-INF/view/contractMgmt/estimateList_new.jsp index bf768b7..1948ab4 100644 --- a/WebContent/WEB-INF/view/contractMgmt/estimateList_new.jsp +++ b/WebContent/WEB-INF/view/contractMgmt/estimateList_new.jsp @@ -668,169 +668,21 @@ function fn_sendEstimateMail(contractObjId){ return; } - // 1단계: 견적서 템플릿 정보 조회 - Swal.fire({ - title: '견적서 조회 중...', - text: '잠시만 기다려주세요.', - allowOutsideClick: false, - onOpen: () => { - Swal.showLoading(); - } - }); - - $.ajax({ - url: "/contractMgmt/getEstimateTemplateList.do", - type: "POST", - data: { objId: contractObjId }, - dataType: "json", - success: function(data){ - if(data.result === "success" && data.list && data.list.length > 0){ - // 최종 차수 견적서 찾기 - var latestEstimate = data.list[0]; // 이미 차수 내림차순으로 정렬되어 있음 - var templateObjId = latestEstimate.OBJID || latestEstimate.objid; - var templateType = latestEstimate.TEMPLATE_TYPE || latestEstimate.template_type || latestEstimate.templateType; - - // 2단계: 견적서 페이지를 새 창으로 열고 PDF 생성 - fn_generatePdfAndSendMail(contractObjId, templateObjId, templateType); - } else { - Swal.close(); - Swal.fire({ - title: '오류', - text: '견적서를 찾을 수 없습니다.', - icon: 'error' - }); - } - }, - error: function(xhr, status, error){ - Swal.close(); - console.error("견적서 조회 오류:", xhr, status, error); - Swal.fire({ - title: '오류', - text: '견적서 조회 중 오류가 발생했습니다.', - icon: 'error' - }); - } - }); -} - -// PDF 생성 및 메일 발송 -function fn_generatePdfAndSendMail(contractObjId, templateObjId, templateType){ - Swal.fire({ - title: 'PDF 생성 중...', - text: '견적서를 PDF로 변환하고 있습니다.', - allowOutsideClick: false, - onOpen: () => { - Swal.showLoading(); - } - }); - - // 견적서 페이지 URL 생성 - var url = ""; - if(templateType === "1"){ - url = "/contractMgmt/estimateTemplate1.do?templateObjId=" + templateObjId; - } else if(templateType === "2"){ - url = "/contractMgmt/estimateTemplate2.do?templateObjId=" + templateObjId; - } - - // 숨겨진 iframe으로 페이지 로드 - var iframe = $('', { - id: 'pdfGeneratorFrame', - src: url, - style: 'position:absolute;width:0;height:0;border:none;' - }).appendTo('body'); - - // iframe 로드 완료 대기 - iframe.on('load', function(){ - try { - var iframeWindow = this.contentWindow; - - // iframe 내의 PDF 생성 함수 호출 - if(typeof iframeWindow.fn_generateAndUploadPdf === 'function'){ - iframeWindow.fn_generateAndUploadPdf(function(pdfBlob){ - // iframe 제거 - $('#pdfGeneratorFrame').remove(); - - if(pdfBlob){ - // PDF Blob과 함께 메일 발송 요청 - fn_sendMailWithPdf(contractObjId, pdfBlob); - } else { - Swal.close(); - Swal.fire({ - title: '오류', - text: 'PDF 생성에 실패했습니다.', - icon: 'error' - }); - } - }); - } else { - // iframe 제거 - $('#pdfGeneratorFrame').remove(); - - Swal.close(); - Swal.fire({ - title: '오류', - text: '견적서 페이지 로드에 실패했습니다.', - icon: 'error' - }); - } - } 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 Blob과 함께 메일 발송 (FormData 사용) -function fn_sendMailWithPdf(contractObjId, pdfBlob){ - console.log('===== 메일 발송 시작 ====='); - console.log('contractObjId:', contractObjId); - console.log('PDF Blob 크기:', pdfBlob ? pdfBlob.size : 0, 'bytes'); - console.log('========================'); - Swal.fire({ title: '메일 발송 중...', text: '잠시만 기다려주세요.', allowOutsideClick: false, - onOpen: () => { + didOpen: () => { Swal.showLoading(); } }); - // FormData 생성 - var formData = new FormData(); - formData.append('objId', contractObjId); - formData.append('pdfFile', pdfBlob, 'estimate.pdf'); - $.ajax({ url: "/contractMgmt/sendEstimateMail.do", type: "POST", - data: formData, - processData: false, // FormData 사용 시 필수 - contentType: false, // FormData 사용 시 필수 + data: { objId: contractObjId }, dataType: "json", - timeout: 60000, // 60초 타임아웃 success: function(data){ - console.log('메일 발송 응답:', data); Swal.close(); if(data.result === "success"){ Swal.fire({ @@ -854,10 +706,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: '확인' }); diff --git a/WebContent/WEB-INF/view/contractMgmt/estimateTemplate1.jsp b/WebContent/WEB-INF/view/contractMgmt/estimateTemplate1.jsp index 66c3bd7..361887f 100644 --- a/WebContent/WEB-INF/view/contractMgmt/estimateTemplate1.jsp +++ b/WebContent/WEB-INF/view/contractMgmt/estimateTemplate1.jsp @@ -165,10 +165,9 @@ body { .items-table th, .items-table td { border: 1px solid #000; - padding: 3px 5px; + padding: 6px 8px; text-align: center; font-size: 9pt; - line-height: 1.3; } .items-table th { @@ -266,12 +265,11 @@ textarea { textarea { resize: vertical; - min-height: 25px; - padding: 2px; + min-height: 30px; } .editable { - background-color: transparent; + background-color: #fffef0; } @media print { @@ -464,8 +462,8 @@ function fn_calculateAmount(row) { function fn_calculateTotal() { var total = 0; - // 품목 행만 순회 (계 행, 원화환산 행, 비고 행, 참조사항 행, 회사명 행 제외) - $("#itemsTableBody tr").not(".total-row, .total-krw-row, .remarks-row, .notes-row, .footer-row").each(function(){ + // 품목 행만 순회 (계 행, 원화환산 행, 비고 행 제외) + $("#itemsTableBody tr").not(".total-row, .total-krw-row, .remarks-row").each(function(){ var amount = $(this).find(".item-amount").val() || "0"; // 콤마와 통화 기호 제거 후 숫자로 변환 amount = amount.replace(/,/g, "").replace(/₩/g, "").replace(/\$/g, "").replace(/€/g, "").replace(/¥/g, ""); @@ -582,8 +580,8 @@ function fn_initItemDescSelect(itemId) { // 행 추가 함수 function fn_addItemRow() { - // 계 행, 원화환산 행, 비고 행, 참조사항 행, 회사명 행 제외하고 품목 행 개수 계산 - var itemRows = $("#itemsTableBody tr").not(".total-row, .total-krw-row, .remarks-row, .notes-row, .footer-row"); + // 계 행, 원화환산 행, 비고 행 제외하고 품목 행 개수 계산 + var itemRows = $("#itemsTableBody tr").not(".total-row, .total-krw-row, .remarks-row"); var nextNo = itemRows.length + 1; // 새 행 생성 @@ -858,6 +856,12 @@ 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 || ""; @@ -926,24 +930,6 @@ function fn_loadTemplateData(templateObjId){ itemsHtml += ''; itemsHtml += ''; - // 참조사항 행 추가 - itemsHtml += ''; - itemsHtml += ''; - itemsHtml += '<참조사항>'; - itemsHtml += ''; - itemsHtml += ''; - itemsHtml += ''; - itemsHtml += ''; - itemsHtml += ''; - itemsHtml += ''; - - // 하단 회사명 행 추가 - itemsHtml += ''; - itemsHtml += ''; - itemsHtml += '㈜알피에스'; - itemsHtml += ''; - itemsHtml += ''; - // HTML 삽입 $("#itemsTableBody").html(itemsHtml); @@ -990,17 +976,11 @@ function fn_loadTemplateData(templateObjId){ fn_initItemDescSelect(itemId); } - // 테이블 내 비고 값 설정 (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(); + // 테이블 내 비고 값 설정 (textarea 생성 직후) + $("#note_remarks").val(noteRemarks); + + // 합계 계산 + fn_calculateTotal(); // 결재상태에 따라 버튼 제어 fn_controlButtons(); @@ -1045,8 +1025,8 @@ function fn_loadCustomerContact(customerObjId) { function fn_save() { var items = []; - // 계 행, 원화환산 행, 비고 행, 참조사항 행, 회사명 행 제외하고 품목 행만 저장 - $("#itemsTableBody tr").not(".total-row, .total-krw-row, .remarks-row, .notes-row, .footer-row").each(function(idx) { + // 계 행, 원화환산 행, 비고 행 제외하고 품목 행만 저장 + $("#itemsTableBody tr").not(".total-row, .total-krw-row, .remarks-row").each(function(idx) { var row = $(this); var quantity = row.find(".item-qty").val() || ""; var unitPrice = row.find(".item-price").val() || ""; @@ -1148,7 +1128,6 @@ function fn_save() { - @@ -1156,7 +1135,6 @@ function fn_save() { - ㈊알피에스RPS CO., LTD대표이사이동준 - + + 담당자 : + 연락처 : + @@ -1192,19 +1173,15 @@ function fn_save() { - - - + 견적을 요청해 주셔서 대단히 감사합니다. 하기와 같이 견적서를 제출합니다. - - - 담당자 : - 연락처 : - 부가세 별도 + + + + 부가세 별도 - @@ -1268,224 +1245,31 @@ function fn_save() { - - - - <참조사항> - - - - - - - - - - ㈜알피에스 - - + + + + <참조사항> + + + + + + + + 행 추가 인쇄 - PDF 다운로드 저장 닫기 - - - - -