Files
wace_plm/WebContent/WEB-INF/view/contractMgmt/estimateTemplate2.jsp

1691 lines
54 KiB
Plaintext

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page import="com.pms.common.utils.*"%>
<%@ page import="java.util.*" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<%@include file= "/init.jsp" %>
<%
PersonBean person = (PersonBean)session.getAttribute(Constants.PERSON_BEAN);
String userId = CommonUtils.checkNull(person.getUserId());
String objId = CommonUtils.checkNull(request.getParameter("objId"));
String templateObjId = CommonUtils.checkNull(request.getParameter("templateObjId"));
String apprStatus = CommonUtils.checkNull((String)request.getAttribute("apprStatus"));
boolean isApproved = "결재완료".equals(apprStatus);
%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title><%=Constants.SYSTEM_NAME%> - 장비 견적서</title>
<style type="text/css">
@media print {
@page {
size: A4;
margin: 10mm;
}
body {
margin: 0;
padding: 0;
}
.no-print {
display: none !important;
}
}
body {
font-family: "Malgun Gothic", "맑은 고딕", Arial, sans-serif;
font-size: 10pt;
margin: 0;
padding: 20px;
background-color: #f5f5f5;
}
.estimate-container {
width: 210mm;
min-height: 297mm;
background: white;
margin: 0 auto;
padding: 15mm;
box-shadow: 0 0 10px rgba(0,0,0,0.1);
box-sizing: border-box;
}
.header-section {
display: flex;
justify-content: space-between;
align-items: flex-start;
/* margin-bottom: 20px; */
}
.logo-section {
flex: 0 0 150px;
}
.logo-img {
width: 120px;
height: auto;
}
.title-section {
flex: 1;
text-align: center;
}
.title {
font-size: 24pt;
font-weight: bold;
letter-spacing: 15px;
margin-bottom: 20px;
}
.company-info {
flex: 0 0 200px;
text-align: right;
font-size: 9pt;
line-height: 1.5;
}
.company-name {
font-weight: bold;
font-size: 10pt;
margin-bottom: 5px;
}
.basic-info {
display: flex;
gap: 10px;
margin-bottom: 20px;
font-size: 10pt;
}
.basic-info-left {
flex: 0 0 40%;
}
.basic-info-right {
flex: 1;
}
.info-row {
display: flex;
margin-bottom: 5px;
}
.info-label {
width: 80px;
font-weight: bold;
}
.info-value {
flex: 1;
}
.model-header {
background-color: #90EE90;
padding: 8px;
text-align: center;
font-weight: bold;
border: 1px solid #000;
margin-bottom: 0;
}
.items-table {
width: 100%;
border-collapse: collapse;
margin-bottom: 0;
table-layout: fixed; /* 고정 레이아웃으로 컬럼 너비 강제 */
}
.items-table th,
.items-table td {
border: 1px solid #000;
padding: 4px 2px; /* 패딩 축소 */
font-size: 9pt;
overflow: hidden; /* 내용이 넘칠 경우 숨김 */
word-wrap: break-word; /* 긴 단어 줄바꿈 */
}
.items-table th {
background-color: #E8E8E8;
font-weight: bold;
text-align: center;
font-size: 8pt; /* 헤더 글자 크기 축소 */
padding: 3px 2px; /* 헤더 패딩 더 축소 */
line-height: 1.2; /* 줄 간격 축소 */
}
/* colgroup으로 너비 제어하므로 클래스 기반 너비는 제거하거나 주석 처리 */
.items-table .col-no { text-align: center; }
.items-table .col-desc { }
.items-table .col-spec { }
.items-table .col-qty { text-align: center; }
.items-table .col-price { text-align: right; }
.items-table .col-amount { text-align: right; }
.items-table .col-remark { text-align: center; }
.category-row {
background-color: #f0f0f0;
font-weight: bold;
}
.subtotal-row {
background-color: #FFFF00;
font-weight: bold;
}
.subtotal-row td {
text-align: center;
}
.subtotal-row input {
background-color: transparent;
border: none;
text-align: center;
width: 100%;
font-weight: bold;
}
.detail-row td {
vertical-align: top;
padding: 5px 8px;
}
.notes-section {
/* margin-top: 20px; */
/* padding: 15px; */
border: 1px solid #ddd;
font-size: 9pt;
line-height: 1.8;
background-color: white;
}
.notes-section ul {
margin: 5px 0;
padding-left: 20px;
}
.notes-section li {
margin-bottom: 5px;
}
.footer-section {
display: flex;
justify-content: space-between;
margin-top: 20px;
font-size: 8pt;
color: #666;
}
.company-footer {
text-align: right;
font-weight: bold;
font-size: 11pt;
}
input[type="text"],
textarea {
border: none;
outline: none;
background: transparent;
width: 100%;
font-family: inherit;
font-size: inherit;
white-space: pre-wrap;
}
textarea.item-spec {
resize: none;
overflow: hidden;
min-height: 40px;
line-height: 1.4;
white-space: pre-wrap;
}
.highlight {
background-color: #FFFF00 !important;
}
.vat-badge {
background-color: #FFFF00;
padding: 3px 8px;
font-weight: bold;
display: inline-block;
}
.estimate-btn-area {
position: fixed;
bottom: 0;
left: 0;
right: 0;
text-align: right;
padding: 15px 30px;
background-color: #ffffff;
border-top: 3px solid #007bff;
box-shadow: 0 -2px 10px rgba(0,0,0,0.1);
z-index: 1000;
}
.estimate-btn {
display: inline-block;
padding: 5px 15px;
margin: 0 2px;
font-size: 12px;
font-weight: normal;
cursor: pointer;
border: 1px solid #60a5fa;
background-color: #60a5fa;
color: white;
border-radius: 3px;
transition: all 0.3s;
line-height: 1;
vertical-align: middle;
white-space: nowrap;
}
.estimate-btn:hover {
background-color: #1e3a8a;
border-color: #1e3a8a;
box-shadow: 0 2px 6px rgba(0,123,255,0.4);
}
.estimate-btn:active {
background-color: #1e3a8a;
border-color: #1e3a8a;
box-shadow: 0 1px 3px rgba(96,165,250,0.3);
}
@media print {
.editable {
background-color: transparent;
}
textarea {
border: none !important;
overflow: hidden;
resize: none;
background: white !important;
}
input[type="text"] {
border: none !important;
background: white !important;
}
.vat-badge {
background-color: #FFFF00 !important;
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
.highlight {
background-color: #FFFF00 !important;
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
.subtotal-row {
background-color: #FFFF00 !important;
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
.category-row {
background-color: #f0f0f0 !important;
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
.items-table th {
background-color: #E8E8E8 !important;
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
.model-header {
background-color: #90EE90 !important;
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
}
</style>
<script type="text/javascript">
// 데이터 로딩 완료 플래그 (메일 발송 시 PDF 생성용)
window.dataLoaded = false;
window.dataLoadComplete = false;
$(function(){
var isApproved = <%=isApproved%>;
// 결재완료 상태일 때 편집 불가 처리
if(isApproved) {
// 모든 입력 필드 읽기 전용으로 변경
$("input[type='text'], textarea, select").prop("readonly", true).prop("disabled", true);
$("[contenteditable='true']").prop("contenteditable", false);
// 저장 버튼과 카테고리 추가 버튼 숨기기
$("#btnSave").hide();
$("input[value='+ 카테고리 추가']").hide();
// 삭제 버튼 숨기기
$(".btn-delete-category").hide();
}
// 환종 표시 (페이지 로드 시)
var currencyName = "${estimate.CONTRACT_CURRENCY_NAME}";
if(currencyName && currencyName !== "") {
// 괄호 안의 기호만 추출 (예: "달러 ($)" -> "$")
var match = currencyName.match(/\(([^)]+)\)/);
var displayText = match ? match[1] : currencyName;
$("#currency_display").text(displayText);
}
// 시행일자 datepicker 초기화
if(!isApproved) {
$("#executor_date").datepicker({
changeMonth: true,
changeYear: true,
dateFormat: 'yy-mm-dd'
});
}
// 수신처(고객사) select2 초기화
if(!isApproved) {
var recipientValue = $("#recipient").val();
$("#recipient").select2({
width: '200px',
placeholder: '고객사 선택'
});
// 초기 선택값이 있으면 select2에 반영 (약간의 지연 후)
if(recipientValue && recipientValue !== "") {
setTimeout(function() {
$("#recipient").val(recipientValue).trigger('change.select2');
}, 100);
}
}
// 숫자만 입력 가능하도록 제한 (unit_price, subtotal)
$(document).on("keypress", ".item-price, .subtotal-amount", function(e) {
// 숫자(0-9), 백스페이스, 삭제, 탭, 엔터, 콤마만 허용
var charCode = (e.which) ? e.which : e.keyCode;
if (charCode != 8 && charCode != 9 && charCode != 13 && charCode != 44 &&
(charCode < 48 || charCode > 57)) {
e.preventDefault();
return false;
}
return true;
});
// 인쇄 버튼
$("#btnPrint").click(function(){
window.print();
});
// 저장 버튼
$("#btnSave").click(function(){
if(confirm("장비 견적서를 저장하시겠습니까?")) {
fn_save();
}
});
// 닫기 버튼
$("#btnClose").click(function(){
self.close();
});
// 금액 자동 계산
$(".item-qty, .item-price").on("change keyup", function(){
fn_calculateAmount($(this).closest("tr"));
fn_calculateSubtotal($(this).closest("tbody"));
// 초음파 CNC Machine의 금액이 변경되면 최종 견적가도 업데이트
if($(this).closest(".category-section").data("category") === "cnc_machine") {
fn_calculateTotal();
}
});
// 콤마 자동 추가
$(".item-price, .item-amount, .subtotal-amount").on("blur", function(){
var val = $(this).val().replace(/,/g, "");
if(!isNaN(val) && val !== "") {
$(this).val(addComma(val));
}
});
// Subtotal 입력 시 최종 견적가 자동 계산
$(".subtotal-amount").on("change keyup", function(){
fn_calculateTotal();
});
// textarea 자동 높이 조절
$("textarea.item-spec, textarea.item-desc, textarea.item-qty, textarea.item-remark").on("input", function(){
this.style.height = "auto";
this.style.height = (this.scrollHeight) + "px";
});
// 데이터 로드 (저장된 견적서를 열 때만)
if("<%=templateObjId%>" !== "" && "<%=templateObjId%>" !== "-1") {
// 로딩 표시
Swal.fire({
title: '데이터 로딩 중...',
text: '견적서 정보를 불러오고 있습니다.',
allowOutsideClick: false,
didOpen: () => {
Swal.showLoading();
}
});
fn_loadData();
} else {
// 신규 작성 시에는 바로 데이터 로딩 완료
window.dataLoadComplete = true;
// 라이브러리 로드 체크
fn_checkLibrariesAndSetDataLoaded();
}
});
// 라이브러리 로드 확인 및 dataLoaded 설정
function fn_checkLibrariesAndSetDataLoaded() {
var checkCount = 0;
var checkInterval = setInterval(function() {
checkCount++;
if(typeof html2canvas !== 'undefined' && typeof jsPDF !== 'undefined') {
console.log('라이브러리 로드 완료, dataLoaded = true 설정');
window.dataLoaded = true;
clearInterval(checkInterval);
} else if(checkCount > 300) {
console.error('라이브러리 로드 타임아웃');
window.dataLoaded = true;
clearInterval(checkInterval);
}
}, 100);
}
// 금액 계산
function fn_calculateAmount(row) {
if(row.hasClass("category-row") || row.hasClass("subtotal-row")) {
return;
}
var qty = row.find(".item-qty").val() || "1";
var price = row.find(".item-price").val().replace(/,/g, "") || "0";
var amount = parseInt(qty) * parseInt(price);
if(!isNaN(amount)) {
row.find(".item-amount").val(addComma(amount));
}
}
// 소계 계산
function fn_calculateSubtotal(tbody) {
var total = 0;
tbody.find("tr:not(.category-row):not(.subtotal-row)").each(function() {
var amount = $(this).find(".item-amount").val();
if(amount) {
var numAmount = parseInt(amount.replace(/,/g, "").replace(/-/g, ""));
if(!isNaN(numAmount)) {
total += numAmount;
}
}
});
tbody.find(".subtotal-amount").val(addComma(total));
}
// 콤마 추가
function addComma(num) {
var regexp = /\B(?=(\d{3})+(?!\d))/g;
return num.toString().replace(regexp, ',');
}
// 카테고리 추가
function fn_addCategory() {
// 다음 카테고리 번호 계산 (현재 전체 카테고리 개수 + 1)
var totalCategoryCount = $(".category-section:not([data-category='cnc_machine'])").length;
var newCategoryNo = totalCategoryCount + 1;
var newCategoryName = "카테고리 " + newCategoryNo;
// 새 카테고리 HTML 생성 (6컬럼 구조)
var newCategoryHtml =
'<table class="items-table category-section" data-category="category_' + newCategoryNo + '">' +
'<colgroup>' +
'<col style="width: 5%;">' +
'<col style="width: 26%;">' +
'<col style="width: 28.5%;">' +
'<col style="width: 6.5%;">' +
'<col style="width: 24%;">' +
'<col style="width: 10%;">' +
'</colgroup>' +
'<tbody>' +
'<tr class="category-row">' +
'<td rowspan="2" class="category-no">' + newCategoryNo + '</td>' +
'<td colspan="4" contenteditable="true">' + newCategoryName + '</td>' +
'<td style="text-align: center;">' +
'<button type="button" class="btn-delete-category" onclick="fn_deleteCategory(this)" style="padding: 2px 8px; font-size: 10px; cursor: pointer; background-color: #dc3545; color: white; border: none; border-radius: 3px;">삭제</button>' +
'</td>' +
'</tr>' +
'<tr class="detail-row">' +
'<td>' +
'<textarea class="item-desc" rows="5" style="width: 100%; resize: vertical; font-size: 9pt; line-height: 1.6;"></textarea>' +
'</td>' +
'<td>' +
'<textarea class="item-spec" rows="5" style="width: 100%; resize: vertical; font-size: 9pt; line-height: 1.6;"></textarea>' +
'</td>' +
'<td>' +
'<textarea class="item-qty" rows="5" style="width: 100%; resize: vertical; font-size: 9pt; line-height: 1.6; text-align: right;"></textarea>' +
'</td>' +
'<td>' +
'<textarea class="item-qty2" rows="5" style="width: 100%; resize: vertical; font-size: 9pt; line-height: 1.6;"></textarea>' +
'</td>' +
'<td>' +
'<textarea class="item-remark" rows="5" style="width: 100%; resize: vertical; font-size: 9pt; line-height: 1.6;"></textarea>' +
'</td>' +
'</tr>' +
'<tr class="subtotal-row">' +
'<td colspan="4">Subtotal</td>' +
'<td><input type="text" class="subtotal-amount" value=""></td>' +
'<td></td>' +
'</tr>' +
'</tbody>' +
'</table>';
// 비고 섹션 앞에 삽입
$(".notes-section").before(newCategoryHtml);
// textarea 자동 높이 조절 이벤트 바인딩
$("textarea.item-spec, textarea.item-desc, textarea.item-qty, textarea.item-qty2, textarea.item-remark").off("input").on("input", function(){
this.style.height = "auto";
this.style.height = (this.scrollHeight) + "px";
});
// Subtotal 입력 이벤트 바인딩
$(".subtotal-amount").off("change keyup").on("change keyup", function(){
fn_calculateTotal();
});
}
// 저장된 데이터로부터 카테고리 생성 (로드 시 사용) - 6컬럼 구조
function fn_createCategoryFromData(category) {
var categoryNo = category.category_no || 1;
var categoryName = category.category_name || "카테고리 " + categoryNo;
// 새 카테고리 HTML 생성
var newCategoryHtml =
'<table class="items-table category-section" data-category="' + category.category + '">' +
'<colgroup>' +
'<col style="width: 5%;">' +
'<col style="width: 26%;">' +
'<col style="width: 28.5%;">' +
'<col style="width: 6.5%;">' +
'<col style="width: 24%;">' +
'<col style="width: 10%;">' +
'</colgroup>' +
'<tbody>' +
'<tr class="category-row">' +
'<td rowspan="2" class="category-no">' + categoryNo + '</td>' +
'<td colspan="4" contenteditable="true">' + categoryName + '</td>' +
'<td style="text-align: center;">' +
'<button type="button" class="btn-delete-category" onclick="fn_deleteCategory(this)" style="padding: 2px 8px; font-size: 10px; cursor: pointer; background-color: #dc3545; color: white; border: none; border-radius: 3px;">삭제</button>' +
'</td>' +
'</tr>' +
'<tr class="detail-row">' +
'<td>' +
'<textarea class="item-desc" rows="5" style="width: 100%; resize: vertical; font-size: 9pt; line-height: 1.6; white-space: pre-wrap;">' + (category.description || '') + '</textarea>' +
'</td>' +
'<td>' +
'<textarea class="item-spec" rows="5" style="width: 100%; resize: vertical; font-size: 9pt; line-height: 1.6; white-space: pre-wrap;">' + (category.specification || '') + '</textarea>' +
'</td>' +
'<td>' +
'<textarea class="item-qty" rows="5" style="width: 100%; resize: vertical; font-size: 9pt; line-height: 1.6; white-space: pre-wrap; text-align: right;">' + (category.quantity || '') + '</textarea>' +
'</td>' +
'<td>' +
'<textarea class="item-qty2" rows="5" style="width: 100%; resize: vertical; font-size: 9pt; line-height: 1.6; white-space: pre-wrap;">' + (category.quantity2 || '') + '</textarea>' +
'</td>' +
'<td>' +
'<textarea class="item-remark" rows="5" style="width: 100%; resize: vertical; font-size: 9pt; line-height: 1.6; white-space: pre-wrap;">' + (category.remark || '') + '</textarea>' +
'</td>' +
'</tr>' +
'<tr class="subtotal-row">' +
'<td colspan="4">Subtotal</td>' +
'<td><input type="text" class="subtotal-amount" value="' + (category.subtotal ? addComma(category.subtotal) : '') + '"></td>' +
'<td></td>' +
'</tr>' +
'</tbody>' +
'</table>';
// 비고 섹션 앞에 삽입
$(".notes-section").before(newCategoryHtml);
// textarea 자동 높이 조절 이벤트 바인딩
$("textarea.item-spec, textarea.item-desc, textarea.item-qty, textarea.item-qty2, textarea.item-remark").off("input").on("input", function(){
this.style.height = "auto";
this.style.height = (this.scrollHeight) + "px";
});
// Subtotal 입력 이벤트 바인딩
$(".subtotal-amount").off("change keyup").on("change keyup", function(){
fn_calculateTotal();
});
}
// 카테고리 삭제
function fn_deleteCategory(btn) {
if(confirm("이 카테고리를 삭제하시겠습니까?")) {
var categoryTable = $(btn).closest(".category-section");
var groupName = categoryTable.data("group");
// 카테고리 테이블 삭제
categoryTable.remove();
// 그룹에 속한 경우, 해당 그룹의 남은 카테고리 확인
if(groupName) {
var remainingInGroup = $(".category-section[data-group='" + groupName + "']").length;
// 그룹의 마지막 카테고리가 삭제되면 Subtotal도 삭제
if(remainingInGroup === 0) {
$("#" + groupName + "_subtotal").remove();
}
}
// 카테고리 번호 재정렬
fn_reorderCategories();
// 최종 견적가 재계산
fn_calculateTotal();
}
}
// 카테고리 번호 재정렬 (모든 카테고리)
function fn_reorderCategories() {
var categoryNo = 1;
$(".category-section:not([data-category='cnc_machine'])").each(function() {
$(this).find(".category-no").text(categoryNo);
categoryNo++;
});
}
// 최종 견적가 계산 (4개 Subtotal 합계)
function fn_calculateTotal() {
var total = 0;
// 초음파 CNC Machine의 AMOUNT 값 추가
var cncAmount = $(".category-section[data-category='cnc_machine'] .item-amount").val();
if(cncAmount && cncAmount !== "") {
var numVal = parseInt(cncAmount.replace(/,/g, ""));
if(!isNaN(numVal)) {
total += numVal;
}
}
// 모든 Subtotal 값을 더함
$(".subtotal-amount").each(function() {
var val = $(this).val();
if(val && val !== "" && val !== "-") {
var numVal = parseInt(val.replace(/,/g, ""));
if(!isNaN(numVal)) {
total += numVal;
}
}
});
// 초음파 CNC Machine의 최종 견적가에 설정
var finalAmount = $(".category-section[data-category='cnc_machine'] .final-amount");
if(total > 0) {
finalAmount.val(addComma(total));
} else {
finalAmount.val("");
}
}
// 데이터 로드
function fn_loadData() {
$.ajax({
url: "/contractMgmt/getEstimateDetail.do",
type: "POST",
data: {
objId: "<%=objId%>",
templateObjId: "<%=templateObjId%>",
template_type: "2"
},
dataType: "json",
timeout: 120000, // 타임아웃 120초 (2분)
success: function(data) {
Swal.close(); // 로딩 닫기
console.log("=== fn_loadData 응답 ===");
console.log("data:", data);
console.log("data.estimate:", data.estimate);
if(data.estimate) {
console.log("estimate의 모든 키:", Object.keys(data.estimate));
console.log("EXECUTOR_DATE:", data.estimate.EXECUTOR_DATE);
console.log("executor_date:", data.estimate.executor_date);
console.log("PART_NAME:", data.estimate.PART_NAME);
console.log("part_name:", data.estimate.part_name);
console.log("GROUP1_SUBTOTAL:", data.estimate.GROUP1_SUBTOTAL);
console.log("group1_subtotal:", data.estimate.group1_subtotal);
}
if(data && data.estimate) {
// 기본 정보 바인딩
$("#executor_date").val(data.estimate.EXECUTOR_DATE || "");
$("#recipient").val(data.estimate.RECIPIENT || "").trigger('change');
$("#part_name").val(data.estimate.PART_NAME || "");
$("#part_objid").val(data.estimate.PART_OBJID || "");
// 환종 표시
if(data.estimate.CONTRACT_CURRENCY_NAME) {
// 괄호 안의 기호만 추출 (예: "달러 ($)" -> "$")
var match = data.estimate.CONTRACT_CURRENCY_NAME.match(/\(([^)]+)\)/);
var displayText = match ? match[1] : data.estimate.CONTRACT_CURRENCY_NAME;
$("#currency_display").text(displayText);
}
// 비고 내용 - 기존 데이터가 있을 때만 덮어쓰기
if(data.estimate.NOTES_CONTENT && data.estimate.NOTES_CONTENT !== "") {
$("#notes_content").html(data.estimate.NOTES_CONTENT);
}
// 견적 유효기간 - 기존 데이터가 있을 때만 덮어쓰기
if(data.estimate.VALIDITY_PERIOD && data.estimate.VALIDITY_PERIOD !== "") {
$("#validity_period").val(data.estimate.VALIDITY_PERIOD);
}
// 카테고리 데이터 로드 (items가 categories로 변환됨)
var categories = data.items || [];
console.log("=== 카테고리 데이터 로드 ===");
console.log("categories:", categories);
console.log("categories.length:", categories.length);
if(categories && categories.length > 0) {
// 1. 먼저 동적으로 추가된 카테고리 모두 삭제 (기본 1-7번 제외)
$(".category-section[data-category^='category_']").remove();
// 2. 저장된 카테고리 목록 생성
var savedCategoryIds = categories.map(function(cat) { return cat.category; });
console.log("저장된 카테고리 목록:", savedCategoryIds);
// 3. 기본 카테고리(1-7) 중 저장되지 않은 것은 숨기기
$(".category-section:not([data-category='cnc_machine']):not([data-category^='category_'])").each(function() {
var categoryId = $(this).data("category");
if(savedCategoryIds.indexOf(categoryId) === -1) {
console.log("카테고리 숨김:", categoryId);
$(this).hide();
} else {
$(this).show();
}
});
// 4. 카테고리별로 데이터 바인딩 또는 생성
$.each(categories, function(catIdx, category) {
console.log("카테고리 " + catIdx + ":", category);
var categorySection = $(".category-section[data-category='" + category.category + "']");
console.log(" category.category:", category.category);
console.log(" categorySection.length:", categorySection.length);
// 기존 카테고리가 없으면 동적으로 생성 (동적 추가된 카테고리)
if(categorySection.length === 0 && category.category.startsWith("category_")) {
fn_createCategoryFromData(category);
categorySection = $(".category-section[data-category='" + category.category + "']");
}
if(categorySection.length > 0) {
// 초음파 CNC Machine (특별 영역)
if(category.category === "cnc_machine" && category.items && category.items.length > 0) {
var item = category.items[0];
categorySection.find(".item-desc").val(item.description || "");
categorySection.find(".item-spec").val(item.specification || "");
categorySection.find(".item-qty").val(item.quantity || "");
categorySection.find(".item-price").val(item.unit_price ? addComma(item.unit_price) : "");
categorySection.find(".item-amount").val(item.amount ? addComma(item.amount) : "");
categorySection.find(".item-remark").val(item.remark || "");
}
// 기구~포장/물류 (일반 영역)
else {
// 카테고리 이름 바인딩
if(category.category_name) {
categorySection.find(".category-row td[contenteditable]").text(category.category_name);
}
categorySection.find(".item-desc").val(category.description || "");
categorySection.find(".item-spec").val(category.specification || "");
categorySection.find(".item-qty").val(category.quantity || "");
categorySection.find(".item-qty2").val(category.quantity2 || "");
categorySection.find(".item-remark").val(category.remark || "");
// 개별 Subtotal 바인딩 (그룹에 속하지 않은 카테고리만)
if(!category.group && category.subtotal && category.subtotal !== "") {
categorySection.find(".subtotal-amount").val(addComma(category.subtotal));
}
}
}
});
// textarea 높이 자동 조절
$("textarea.item-spec, textarea.item-desc, textarea.item-qty, textarea.item-remark").each(function() {
this.style.height = "auto";
this.style.height = (this.scrollHeight) + "px";
});
// 카테고리 번호 재정렬
fn_reorderCategories();
}
// 그룹 Subtotal 로드 (group1)
if(data.estimate.GROUP1_SUBTOTAL) {
$("#group1_subtotal .subtotal-amount").val(addComma(data.estimate.GROUP1_SUBTOTAL));
}
// 최종 견적가 계산 (데이터 로드 후)
fn_calculateTotal();
// 데이터 로딩 완료 플래그 설정
window.dataLoadComplete = true;
// 라이브러리 로드 확인 후 dataLoaded 설정
fn_checkLibrariesAndSetDataLoaded();
}
},
error: function(xhr, status, error) {
Swal.close(); // 로딩 닫기
window.dataLoadComplete = false;
window.dataLoaded = false; // 에러 시 false로 설정
console.log("=== fn_loadData 에러 ===");
console.log("status:", status);
console.log("error:", error);
console.log("xhr:", xhr);
if(status === "timeout") {
Swal.fire({
title: "타임아웃",
text: "견적서 데이터 로딩 시간이 초과되었습니다. 서버 로그를 확인해주세요.",
icon: "error"
});
} else {
Swal.fire({
title: "데이터 로딩 실패",
text: "데이터를 불러오는데 실패했습니다. (" + status + ")",
icon: "error"
});
}
}
});
}
// 저장
function fn_save() {
// 로딩 표시
Swal.fire({
title: '저장 중...',
text: '데이터를 저장하고 있습니다.',
allowOutsideClick: false,
didOpen: () => {
Swal.showLoading();
}
});
var categories = [];
// 초음파 CNC Machine (특별 영역)
var cncSection = $(".category-section[data-category='cnc_machine']");
if(cncSection.length > 0) {
categories.push({
category: "cnc_machine",
category_name: "초음파 CNC Machine",
category_no: 0,
content: "",
items: [{
description: cncSection.find(".item-desc").val() || "",
specification: cncSection.find(".item-spec").val() || "",
quantity: cncSection.find(".item-qty").val() || "",
unit_price: (cncSection.find(".item-price").val() || "").replace(/,/g, ""),
amount: (cncSection.find(".item-amount").val() || "").replace(/,/g, ""),
remark: cncSection.find(".item-remark").val() || ""
}],
subtotal: ""
});
}
// 기구~포장/물류 (일반 영역) - 보이는 카테고리만 수집
$(".category-section:not([data-category='cnc_machine']):visible").each(function(idx) {
var section = $(this);
var categoryName = section.data("category");
var groupName = section.data("group");
// contenteditable 영역에서 카테고리 이름 가져오기
var categoryTitle = section.find(".category-row td[contenteditable]").text().trim();
var desc = section.find(".item-desc").val() || "";
var spec = section.find(".item-spec").val() || "";
var qty = section.find(".item-qty").val() || "";
var qty2 = section.find(".item-qty2").val() || "";
var remark = section.find(".item-remark").val() || "";
// 개별 Subtotal (그룹에 속하지 않은 카테고리만)
var subtotal = "";
if(!groupName) {
var subtotalVal = section.find(".subtotal-amount").val() || "-";
subtotal = subtotalVal === "-" ? "" : subtotalVal.replace(/,/g, "");
}
var categoryData = {
category: categoryName,
category_name: categoryTitle,
category_no: idx + 1,
group: groupName || "",
description: desc,
specification: spec,
quantity: qty,
quantity2: qty2,
remark: remark,
subtotal: subtotal
};
console.log("카테고리 " + (idx + 1) + " 수집:", categoryData);
categories.push(categoryData);
});
console.log("=== 저장할 전체 카테고리 ===");
console.log("categories 개수:", categories.length);
console.log("categories:", categories);
// 그룹 Subtotal 저장 (group1)
var group1Subtotal = "";
if($("#group1_subtotal").length > 0) {
var group1SubtotalVal = $("#group1_subtotal .subtotal-amount").val() || "";
group1Subtotal = group1SubtotalVal.replace(/,/g, "");
}
// 최종 견적가 (TOTAL_AMOUNT) - 콤마 제거
var finalAmountVal = $(".category-section[data-category='cnc_machine'] .final-amount").val() || "";
var totalAmount = finalAmountVal.replace(/,/g, "");
// 환율 정보 가져오기 (JSP에서 전달된 값)
var exchangeRate = parseFloat("${estimate.EXCHANGE_RATE}" || "1");
// 원화 환산 (TOTAL_AMOUNT_KRW)
var totalAmountKrw = "";
if(totalAmount && totalAmount !== "" && !isNaN(totalAmount)) {
totalAmountKrw = Math.round(parseFloat(totalAmount) * exchangeRate).toString();
}
var formData = {
objId: "<%=objId%>",
templateObjId: "<%=templateObjId%>",
template_type: "2",
executor_date: $("#executor_date").val(),
recipient: $("#recipient").val(),
part_name: $("#part_name").val(),
part_objid: $("#part_objid").val(),
notes_content: $("#notes_content").html(),
validity_period: $("#validity_period").val(),
categories: JSON.stringify(categories),
group1_subtotal: group1Subtotal,
total_amount: totalAmount,
total_amount_krw: totalAmountKrw
};
$.ajax({
url: "/contractMgmt/saveEstimate2.do",
type: "POST",
data: formData,
dataType: "json",
timeout: 120000, // 타임아웃 120초 (2분)
success: function(data) {
Swal.close(); // 로딩 닫기
if(data.result === "success") {
Swal.fire({
title: "저장되었습니다.",
icon: "success"
}).then(function() {
if(opener && opener.fn_search) {
opener.fn_search();
}
self.close();
});
} else {
Swal.fire("저장에 실패했습니다.");
}
},
error: function(xhr, status, error) {
Swal.close(); // 로딩 닫기
if(status === "timeout") {
Swal.fire({
title: "타임아웃",
text: "저장 시간이 초과되었습니다. 데이터가 많은 경우 시간이 오래 걸릴 수 있습니다.",
icon: "error"
});
} else {
Swal.fire("저장 중 오류가 발생했습니다.");
}
}
});
}
// PDF 생성 및 업로드 (메일 발송용)
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;
}
// 버튼 영역 임시 숨김
$('.estimate-btn-area').hide();
// textarea를 div로 변환 (줄바꿈 보존)
var textareaBackup = [];
$('textarea').each(function(index) {
var $textarea = $(this);
var content = $textarea.val();
var $div = $('<div></div>').css({
'white-space': 'pre-wrap',
'word-wrap': 'break-word',
'font-family': $textarea.css('font-family'),
'font-size': $textarea.css('font-size'),
'line-height': $textarea.css('line-height'),
'padding': $textarea.css('padding'),
'text-align': $textarea.css('text-align'),
'min-height': $textarea.css('min-height')
}).text(content);
textareaBackup.push({
element: $textarea,
parent: $textarea.parent()
});
$textarea.replaceWith($div);
});
// 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 캡처 완료');
// textarea 복원
$('div').each(function(index) {
if(index < textareaBackup.length) {
var backup = textareaBackup[index];
$(this).replaceWith(backup.element);
}
});
// 스타일 복원
container.css({
'background': originalBg,
'box-shadow': originalShadow,
'padding': originalPadding
});
$('body').css('background-color', originalBodyBg);
// 버튼 영역 다시 표시
$('.estimate-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) {
// textarea 복원
textareaBackup.forEach(function(backup) {
backup.parent.append(backup.element);
});
$('.estimate-btn-area').show();
console.error('Canvas 캡처 오류:', error);
if(callback && typeof callback === 'function') {
callback(null);
}
});
}
</script>
</head>
<body>
<div class="estimate-container">
<!-- 헤더 섹션 -->
<div class="header-section">
<div class="logo-section">
<img src="<%=request.getContextPath()%>/images/logo.png" alt="RPS Logo" style="max-width: 120px; height: auto;">
</div>
<div class="title-section">
<div class="title">견 적 서</div>
</div>
<div style="flex: 0 0 200px; text-align: right; font-size: 9pt; line-height: 1.5;">
<!-- 빈 공간 (기존 회사정보 위치) -->
</div>
</div>
<!-- 기본 정보 (좌우 배치) -->
<div style="display: flex; justify-content: space-between; padding: 0 10px;">
<div style="text-align: left; font-size: 10pt; line-height: 2;">
<div><strong>시행일자 :</strong> <input type="text" id="executor_date" class="date_icon" value="" style="width: 200px; border: none; border-bottom: 1px solid #999; padding: 2px 5px;"></div>
<div><strong>수 신 처 :</strong>
<select id="recipient" style="width: 200px; border: none; border-bottom: 1px solid #999; background: #fffef0; padding: 2px 5px; font-size: 10pt;">
<option value="">고객사 선택</option>
${code_map.customer_cd}
</select>
</div>
<div><strong>품  명 :</strong>
<input type="text" id="part_name" value="${not empty items[0] ? items[0].PART_NAME : ''}" style="width: 200px; border: none; border-bottom: 1px solid #999; padding: 2px 5px;">
<input type="hidden" id="part_objid" value="${not empty items[0] ? items[0].PART_OBJID : ''}">
</div>
</div>
<div style="text-align: right;">
<!-- 회사정보 -->
<div style="display: inline-block; min-width: 300px;">
<div style="font-weight: bold; font-size: 11pt; margin-bottom: 5px;">RPS CO., LTD</div>
<div style="font-size: 9pt; line-height: 1.5;">대전광역시 유성구 국제과학로 10로 8</div>
<div style="font-size: 9pt; line-height: 1.5;">TEL: (042)602-3300, FAX: (042)672-3399</div>
</div>
</div>
</div>
<!-- 설비 Model 헤더 -->
<div class="model-header">
설비 Model : <span class="editable" style="display: inline-block; min-width: 200px;">
<input type="text" id="model_code" value="RUV-RA500S" style="text-align: center; font-weight: bold;">
</span>
</div>
<!-- 초음파 CNC Machine (특별 영역) -->
<table class="items-table category-section" data-category="cnc_machine">
<colgroup>
<col style="width: 5%;">
<col style="width: 26%;">
<col style="width: 28.5%;">
<col style="width: 6.5%;">
<col style="width: 12%;">
<col style="width: 12%;">
<col style="width: 10%;">
</colgroup>
<thead>
<tr>
<th>NO</th>
<th>DESCRIPTION</th>
<th>SPECIFICATION</th>
<th>Q'TY</th>
<th>UNIT PRICE</th>
<th>AMOUNT</th>
<th>REMARK</th>
</tr>
</thead>
<tbody>
<tr class="detail-row">
<td rowspan="2">1</td>
<td rowspan="2"><input type="text" class="item-desc" value="초음파 CNC Machine" style="width: 100%; border: none; background: transparent;"></td>
<td><input type="text" class="item-spec" value="Hole 가공" style="width: 100%; border: none; background: transparent; text-align: center;"></td>
<td><input type="text" class="item-qty" value="${not empty items[0] ? items[0].QUANTITY : '1'}" style="width: 100%; border: none; background: transparent; text-align: center;"></td>
<td><input type="text" class="item-price" value="" style="width: 100%; border: none; background: transparent; text-align: right;"></td>
<td><input type="text" class="item-amount" value="" readonly style="width: 100%; border: none; background: transparent; text-align: right;"></td>
<td><input type="text" class="item-remark" value="" style="width: 100%; border: none; background: transparent;"></td>
</tr>
<tr class="subtotal-row">
<td colspan="2" style="text-align: center; font-weight: bold; font-size: 8pt;">최종 견적가</td>
<td colspan="2">
<input type="text" class="final-amount" value="" readonly style="width: 70%; border: none; background: transparent; text-align: right; font-weight: bold;">
<span id="currency_display" style="font-weight: bold; margin-left: 5px;"></span>
</td>
<td style="text-align: center; font-size: 8pt;">VAT 별도</td>
</tr>
</tbody>
</table>
<!-- 1. 기구 -->
<table class="items-table category-section" data-category="structure" data-group="group1">
<colgroup>
<col style="width: 5%;">
<col style="width: 26%;">
<col style="width: 28.5%;">
<col style="width: 6.5%;">
<col style="width: 24%;">
<col style="width: 10%;">
</colgroup>
<tbody>
<tr class="category-row">
<td rowspan="2" class="category-no">1</td>
<td colspan="4" contenteditable="true">기구</td>
<td style="text-align: center;">
<button type="button" class="btn-delete-category" onclick="fn_deleteCategory(this)" style="padding: 2px 8px; font-size: 10px; cursor: pointer; background-color: #dc3545; color: white; border: none; border-radius: 3px;">삭제</button>
</td>
</tr>
<tr class="detail-row">
<td>
<textarea class="item-desc" rows="10" style="width: 100%; resize: vertical; font-size: 9pt; line-height: 1.6;">X,Y,Z LINEAR
LINEAR SCALE ENCODER
주물 BODY
X,Z AXIS COLUMN
COVER
LM GUIDE
LM GUIDE
TABLE
SUS 자바라 X, Y</textarea>
</td>
<td>
<textarea class="item-spec" rows="10" style="width: 100%; resize: vertical; font-size: 9pt; line-height: 1.6;">
MINERAL CASTING
MINERAL CASTING
SPCC 도장
X,Y: SHS25 P급, C1 중예압
Z: SRG25 P급, C0
600 * 500
벨로우즈 멀티 커버</textarea>
</td>
<td>
<textarea class="item-qty" rows="10" style="width: 100%; resize: vertical; font-size: 9pt; line-height: 1.6; text-align: right;">
3
1
1
1
4
2
1
4</textarea>
</td>
<td>
<textarea class="item-qty2" rows="10" style="width: 100%; resize: vertical; font-size: 9pt; line-height: 1.6;"></textarea>
</td>
<td>
<textarea class="item-remark" rows="10" style="width: 100%; resize: vertical; font-size: 9pt; line-height: 1.6;"></textarea>
</td>
</tr>
</tbody>
</table>
<!-- 2. 초음파 스핀들 모듈 -->
<table class="items-table category-section" data-category="spindle_module" data-group="group1">
<colgroup>
<col style="width: 5%;">
<col style="width: 26%;">
<col style="width: 28.5%;">
<col style="width: 6.5%;">
<col style="width: 24%;">
<col style="width: 10%;">
</colgroup>
<tbody>
<tr class="category-row">
<td rowspan="2" class="category-no">2</td>
<td colspan="4" contenteditable="true">초음파 스핀들 모듈</td>
<td style="text-align: center;">
<button type="button" class="btn-delete-category" onclick="fn_deleteCategory(this)" style="padding: 2px 8px; font-size: 10px; cursor: pointer; background-color: #dc3545; color: white; border: none; border-radius: 3px;">삭제</button>
</td>
</tr>
<tr class="detail-row">
<td>
<textarea class="item-desc" rows="5" style="width: 100%; resize: vertical; font-size: 9pt; line-height: 1.6;">초음파 스핀들 모듈
제너레이터</textarea>
</td>
<td>
<textarea class="item-spec" rows="5" style="width: 100%; resize: vertical; font-size: 9pt; line-height: 1.6;">AS030-080H2A2.0-U
15~50kHz, 50W</textarea>
</td>
<td>
<textarea class="item-qty" rows="5" style="width: 100%; resize: vertical; font-size: 9pt; line-height: 1.6; text-align: right;">1
1</textarea>
</td>
<td>
<textarea class="item-qty2" rows="5" style="width: 100%; resize: vertical; font-size: 9pt; line-height: 1.6;"></textarea>
</td>
<td>
<textarea class="item-remark" rows="5" style="width: 100%; resize: vertical; font-size: 9pt; line-height: 1.6;"></textarea>
</td>
</tr>
</tbody>
</table>
<!-- 3. 전장 -->
<table class="items-table category-section" data-category="electric" data-group="group1">
<colgroup>
<col style="width: 5%;">
<col style="width: 26%;">
<col style="width: 28.5%;">
<col style="width: 6.5%;">
<col style="width: 24%;">
<col style="width: 10%;">
</colgroup>
<tbody>
<tr class="category-row">
<td rowspan="2" class="category-no">3</td>
<td colspan="4" contenteditable="true">전장</td>
<td style="text-align: center;">
<button type="button" class="btn-delete-category" onclick="fn_deleteCategory(this)" style="padding: 2px 8px; font-size: 10px; cursor: pointer; background-color: #dc3545; color: white; border: none; border-radius: 3px;">삭제</button>
</td>
</tr>
<tr class="detail-row">
<td>
<textarea class="item-desc" rows="5" style="width: 100%; resize: vertical; font-size: 9pt; line-height: 1.6;">C-BOX
INVERTER
NC 제어기</textarea>
</td>
<td>
<textarea class="item-spec" rows="5" style="width: 100%; resize: vertical; font-size: 9pt; line-height: 1.6;">Panel & Box, 공용 전기 자재, 냉각장치
DELTA MS300
SIEMENS CONTROL PANEL 828D</textarea>
</td>
<td>
<textarea class="item-qty" rows="5" style="width: 100%; resize: vertical; font-size: 9pt; line-height: 1.6; text-align: right;">1
1
1</textarea>
</td>
<td>
<textarea class="item-qty2" rows="5" style="width: 100%; resize: vertical; font-size: 9pt; line-height: 1.6;"></textarea>
</td>
<td>
<textarea class="item-remark" rows="5" style="width: 100%; resize: vertical; font-size: 9pt; line-height: 1.6;"></textarea>
</td>
</tr>
</tbody>
</table>
<!-- 4. UTILITY -->
<table class="items-table category-section" data-category="utility" data-group="group1">
<colgroup>
<col style="width: 5%;">
<col style="width: 26%;">
<col style="width: 28.5%;">
<col style="width: 6.5%;">
<col style="width: 24%;">
<col style="width: 10%;">
</colgroup>
<tbody>
<tr class="category-row">
<td rowspan="2" class="category-no">4</td>
<td colspan="4" contenteditable="true">UTILITY</td>
<td style="text-align: center;">
<button type="button" class="btn-delete-category" onclick="fn_deleteCategory(this)" style="padding: 2px 8px; font-size: 10px; cursor: pointer; background-color: #dc3545; color: white; border: none; border-radius: 3px;">삭제</button>
</td>
</tr>
<tr class="detail-row">
<td>
<textarea class="item-desc" rows="5" style="width: 100%; resize: vertical; font-size: 9pt; line-height: 1.6;">공압 PANEL & HOSE
CHILLER
절삭유 공급장치
OIL 자동급유장치
절삭유 공급 필터</textarea>
</td>
<td>
<textarea class="item-spec" rows="5" style="width: 100%; resize: vertical; font-size: 9pt; line-height: 1.6;">
DSD-010S
순환장치
LUBRICATION
1um 1ea, 10um 1ea</textarea>
</td>
<td>
<textarea class="item-qty" rows="5" style="width: 100%; resize: vertical; font-size: 9pt; line-height: 1.6; text-align: right;">1
1
1
1
1</textarea>
</td>
<td>
<textarea class="item-qty2" rows="5" style="width: 100%; resize: vertical; font-size: 9pt; line-height: 1.6;"></textarea>
</td>
<td>
<textarea class="item-remark" rows="5" style="width: 100%; resize: vertical; font-size: 9pt; line-height: 1.6;"></textarea>
</td>
</tr>
</tbody>
</table>
<!-- 1~4번 그룹 공유 Subtotal -->
<table class="items-table" id="group1_subtotal">
<colgroup>
<col style="width: 5%;">
<col style="width: 26%;">
<col style="width: 28.5%;">
<col style="width: 6.5%;">
<col style="width: 24%;">
<col style="width: 10%;">
</colgroup>
<tbody>
<tr class="subtotal-row">
<td colspan="4">Subtotal</td>
<td class="editable"><input type="text" class="subtotal-amount" data-group="group1" value=""></td>
<td></td>
</tr>
</tbody>
</table>
<!-- 5. Option -->
<table class="items-table category-section" data-category="option">
<colgroup>
<col style="width: 5%;">
<col style="width: 26%;">
<col style="width: 28.5%;">
<col style="width: 6.5%;">
<col style="width: 24%;">
<col style="width: 10%;">
</colgroup>
<tbody>
<tr class="category-row">
<td rowspan="2" class="category-no">5</td>
<td colspan="4" contenteditable="true">Option</td>
<td style="text-align: center;">
<button type="button" class="btn-delete-category" onclick="fn_deleteCategory(this)" style="padding: 2px 8px; font-size: 10px; cursor: pointer; background-color: #dc3545; color: white; border: none; border-radius: 3px;">삭제</button>
</td>
</tr>
<tr class="detail-row">
<td>
<textarea class="item-desc" rows="5" style="width: 100%; resize: vertical; font-size: 9pt; line-height: 1.6;">OMP400
NC4
ATC</textarea>
</td>
<td>
<textarea class="item-spec" rows="5" style="width: 100%; resize: vertical; font-size: 9pt; line-height: 1.6;">OMP400(Renishaw)
NV4Blue(Renishaw)
-</textarea>
</td>
<td>
<textarea class="item-qty" rows="5" style="width: 100%; resize: vertical; font-size: 9pt; line-height: 1.6; text-align: right;">1
1
1</textarea>
</td>
<td>
<textarea class="item-qty2" rows="5" style="width: 100%; resize: vertical; font-size: 9pt; line-height: 1.6;"></textarea>
</td>
<td>
<textarea class="item-remark" rows="5" style="width: 100%; resize: vertical; font-size: 9pt; line-height: 1.6;"></textarea>
</td>
</tr>
<tr class="subtotal-row">
<td colspan="4">Subtotal</td>
<td class="editable"><input type="text" class="subtotal-amount" value=""></td>
<td></td>
</tr>
</tbody>
</table>
<!-- 6. Set up -->
<table class="items-table category-section" data-category="setup">
<colgroup>
<col style="width: 5%;">
<col style="width: 26%;">
<col style="width: 28.5%;">
<col style="width: 6.5%;">
<col style="width: 24%;">
<col style="width: 10%;">
</colgroup>
<tbody>
<tr class="category-row">
<td rowspan="2" class="category-no">6</td>
<td colspan="4" contenteditable="true">Set up</td>
<td style="text-align: center;">
<button type="button" class="btn-delete-category" onclick="fn_deleteCategory(this)" style="padding: 2px 8px; font-size: 10px; cursor: pointer; background-color: #dc3545; color: white; border: none; border-radius: 3px;">삭제</button>
</td>
</tr>
<tr class="detail-row">
<td>
<textarea class="item-desc" rows="5" style="width: 100%; resize: vertical; font-size: 9pt; line-height: 1.6;">인건비 (기구+제어)
인건비 (Training)
기타(항공/숙박/교통/식비)</textarea>
</td>
<td>
<textarea class="item-spec" rows="5" style="width: 100%; resize: vertical; font-size: 9pt; line-height: 1.6;"></textarea>
</td>
<td>
<textarea class="item-qty" rows="5" style="width: 100%; resize: vertical; font-size: 9pt; line-height: 1.6; text-align: right;"></textarea>
</td>
<td>
<textarea class="item-qty2" rows="5" style="width: 100%; resize: vertical; font-size: 9pt; line-height: 1.6;"></textarea>
</td>
<td>
<textarea class="item-remark" rows="5" style="width: 100%; resize: vertical; font-size: 9pt; line-height: 1.6;"></textarea>
</td>
</tr>
<tr class="subtotal-row">
<td colspan="4">Subtotal</td>
<td class="editable"><input type="text" class="subtotal-amount" value=""></td>
<td></td>
</tr>
</tbody>
</table>
<!-- 7. 포장/물류 -->
<table class="items-table category-section" data-category="packing">
<colgroup>
<col style="width: 5%;">
<col style="width: 26%;">
<col style="width: 28.5%;">
<col style="width: 6.5%;">
<col style="width: 24%;">
<col style="width: 10%;">
</colgroup>
<tbody>
<tr class="category-row">
<td rowspan="2" class="category-no">7</td>
<td colspan="4" contenteditable="true">포장/물류</td>
<td style="text-align: center;">
<button type="button" class="btn-delete-category" onclick="fn_deleteCategory(this)" style="padding: 2px 8px; font-size: 10px; cursor: pointer; background-color: #dc3545; color: white; border: none; border-radius: 3px;">삭제</button>
</td>
</tr>
<tr class="detail-row">
<td>
<textarea class="item-desc" rows="5" style="width: 100%; resize: vertical; font-size: 9pt; line-height: 1.6;">포장비
물류비</textarea>
</td>
<td>
<textarea class="item-spec" rows="5" style="width: 100%; resize: vertical; font-size: 9pt; line-height: 1.6;"></textarea>
</td>
<td>
<textarea class="item-qty" rows="5" style="width: 100%; resize: vertical; font-size: 9pt; line-height: 1.6; text-align: right;"></textarea>
</td>
<td>
<textarea class="item-qty2" rows="5" style="width: 100%; resize: vertical; font-size: 9pt; line-height: 1.6;"></textarea>
</td>
<td>
<textarea class="item-remark" rows="5" style="width: 100%; resize: vertical; font-size: 9pt; line-height: 1.6;"></textarea>
</td>
</tr>
<tr class="subtotal-row">
<td colspan="4">Subtotal</td>
<td class="editable"><input type="text" class="subtotal-amount" value=""></td>
<td></td>
</tr>
</tbody>
</table>
<!-- 비고 섹션 -->
<div class="notes-section">
<div id="notes_content" contenteditable="true" style="width: 97%; min-height: 180px; border: 1px solid #ddd; padding: 10px; font-family: inherit; font-size: 9pt; line-height: 1.8; background-color: white; overflow-y: auto;"><strong>■ 최종 견적가는 부가세 별도입니다.</strong><br>■ 장비 납기 : 발주 시 협의<br>■ 운송 조건 : 국내 RPS 에서 진행<br>국외 FOB 기준으로 운송비 / 포장비는 선 당사 부담후 실비 정산<br>■ 결제 조건 : 계약금 : 30% / 중도금 : 60% / 잔금 : 10%<br>-. 계약금 : 발주 후 7일 이내<br>-. 중도금 : 출하 전<br>-. 잔 금 : 설치 완료 후<br>■ 장비사양 : 본견적은 당사 표준 사항<br>사양 협의시 금액 변동성이 있음.<br>■ Warrenty Period: 1년(소모성 parts 제외)<br>■ 주의 : RPS 동의없이 초음파 스핀들의 임의 탈거 또는 해체시 보증 할수 없음.<br><br><div style="display: flex; justify-content: space-between; align-items: center;"><span>* 견적유효기간: 4주</span><span>㈜ 알 피 에 스</span></div></div>
</div>
<!-- 푸터 -->
<!-- <div class="footer-section">
<div><input type="text" id="validity_period" value="* 견적유효기간: 0주 " style="width: 250px; text-align: left;"></div>
<div class="company-footer">㈜ 알 피 에 스</div>
</div> -->
<div style="text-align: right; font-size: 7pt; color: #999; margin-top: 5px;">
* RPS 대외비 - 본자료는 RPS의 사전허가 없이 제3자에게 제공할 수 없음을 알려드립니다.
</div>
</div>
<!-- 버튼 영역 -->
<div class="estimate-btn-area no-print">
<input type="button" value="+ 카테고리 추가" onclick="fn_addCategory()" class="estimate-btn"><input type="button" value="인쇄" id="btnPrint" class="estimate-btn"><input type="button" value="저장" id="btnSave" class="estimate-btn"><input type="button" value="닫기" id="btnClose" class="estimate-btn">
</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>
// 라이브러리 로드 완료 대기 및 dataLoaded 플래그 설정
$(document).ready(function() {
// 라이브러리 로드 체크
var checkLibraries = setInterval(function() {
if(typeof html2canvas !== 'undefined' && typeof jsPDF !== 'undefined') {
console.log('PDF 라이브러리 로드 완료');
// 데이터 로딩이 완료되었는지 확인
if(window.dataLoadComplete === true) {
window.dataLoaded = true;
console.log('dataLoaded = true 설정 완료');
}
clearInterval(checkLibraries);
}
}, 100);
});
</script>
</body>
</html>