- URL 하드코딩 → .env APP_URL 기반 동적 URL로 변경 - DB 연결 하드코딩 → .env 기반으로 변경 - MySQL strict mode DATE 오류 수정
934 lines
33 KiB
PHP
934 lines
33 KiB
PHP
<script>
|
||
// 견적서의 lastJS를 변형해서 수주내역에서 인정제품의 거래명세표를 저장하기 위함이다.
|
||
// 수주리스트의 인정제품 거래명세표로 활용되는 코드
|
||
var ajaxRequest_write = null;
|
||
$(document).ready(function() {
|
||
$('#loadingOverlay').hide(); // 로딩 오버레이 숨기기
|
||
|
||
var dataList = <?php echo json_encode($detailJson ?? []); ?>;
|
||
|
||
// JSON 데이터를 처리하기 전에 유효성 검사
|
||
if (dataList && typeof dataList === 'string') {
|
||
try {
|
||
dataList = JSON.parse(dataList); // JSON 문자열을 객체로 변환
|
||
} catch (e) {
|
||
console.error('JSON parsing error: ', e);
|
||
dataList = []; // 오류 발생 시 빈 배열로 초기화
|
||
}
|
||
}
|
||
|
||
// 배열인지 확인
|
||
// console.log('dataList after JSON.parse check: ', dataList);
|
||
if (!Array.isArray(dataList)) {
|
||
dataList = [];
|
||
}
|
||
else
|
||
{
|
||
$("#updateText").text('견적수정됨');
|
||
}
|
||
|
||
// 테이블에 데이터 로드
|
||
loadTableData('#detailTable', dataList);
|
||
|
||
// 셔터박스 오류메시지 화면에 표시
|
||
var shutterboxMsg = <?= json_encode($shutterboxMsg) ?>;
|
||
|
||
// JavaScript로 처리
|
||
if (shutterboxMsg) {
|
||
var shutterboxDiv = document.getElementById("shutterboxMsg");
|
||
shutterboxDiv.style.display = "block"; // 보이게 설정
|
||
shutterboxDiv.innerHTML = shutterboxMsg; // 내용 설정
|
||
}
|
||
});
|
||
|
||
function loadTableData(tableBodySelector, dataList) {
|
||
console.log('loadTableData data: ', dataList); // 여기서 데이터 확인
|
||
|
||
var tableBody = $(tableBodySelector); // 테이블 본문 선택
|
||
|
||
// 데이터를 반복하면서 테이블에 행을 업데이트
|
||
var count = 1;
|
||
dataList.forEach(function(rowData, index) {
|
||
var row = tableBody.find('tr').eq(index + count); // index에 맞는 tr을 가져옴
|
||
if (row.length) {
|
||
updateRowData(row, rowData, index);
|
||
}
|
||
// console.log('index data: ', index); // 여기서 데이터 확인
|
||
// console.log('rowData data: ', rowData); // 여기서 데이터 확인
|
||
// count++;
|
||
});
|
||
}
|
||
|
||
// 기존 행을 수정하는 함수
|
||
function updateRowData(row, rowData, rowIndex) {
|
||
// 수정해야 할 td 요소들을 선택하여 해당 값을 업데이트
|
||
row.find('.su-input').val(rowData[0]); // 수량
|
||
row.find('.area-length-input').val(rowData[2]); // 길이
|
||
row.find('.area-price-input').val(rowData[3]); // 면적단가
|
||
row.find('.unit-price-input').val(rowData[4]); // 단가
|
||
|
||
// 수정된 행에 동적 계산 함수 호출
|
||
calculateRowTotal(row); // 필요 시 계산 함수 호출
|
||
}
|
||
|
||
|
||
function inputNumber(input) {
|
||
const value = input.value.replace(/,/g, ''); // 콤마 제거
|
||
input.value = parseInt(value, 10).toLocaleString(); // 다시 콤마 추가
|
||
calculateRowTotal($(input).closest('tr')); // 행의 합계 다시 계산
|
||
}
|
||
|
||
// 이벤트 리스너 설정 및 계산 함수 호출
|
||
function setupEventListeners() {
|
||
document.querySelectorAll('input').forEach(input => {
|
||
input.addEventListener('input', function () {
|
||
const row = input.closest('tr');
|
||
calculateRowTotal(row);
|
||
calculateAllSubtotals();
|
||
calculateGrandTotal();
|
||
calculateRowTotalFirstTable();
|
||
});
|
||
});
|
||
}
|
||
|
||
// 페이지 로드 시 초기 계산 및 이벤트 리스너 설정
|
||
window.onload = function () {
|
||
calculateAllSubtotals();
|
||
calculateGrandTotal();
|
||
setupEventListeners();
|
||
updateDiscountFooterRow();
|
||
calculateEstimateAmounts();
|
||
calculateRowTotalFirstTable();
|
||
};
|
||
|
||
|
||
$(document).ready(function () {
|
||
var rowArray = <?= isset($row_array_json) ? $row_array_json : '[]' ?>;
|
||
|
||
if (Array.isArray(rowArray) && rowArray.length > 0) {
|
||
rowArray.forEach(function (rowspanValue, index) {
|
||
var cell = document.getElementById('dynamicRowspan-' + index);
|
||
console.log('index', index);
|
||
if (cell && rowspanValue > 0) {
|
||
cell.setAttribute('rowspan', rowspanValue);
|
||
}
|
||
});
|
||
}
|
||
});
|
||
|
||
|
||
// 숫자 포맷팅 함수 (콤마 추가 및 소수점 둘째자리에서 반올림)
|
||
function formatNumber(value) {
|
||
// 소수점 둘째 자리에서 반올림
|
||
const roundedValue = value;
|
||
// 콤마 추가 포맷팅
|
||
return new Intl.NumberFormat().format(roundedValue);
|
||
}
|
||
|
||
// 숫자에서 콤마를 제거하는 함수
|
||
function cleanNumber(value) {
|
||
// value가 null 또는 undefined인 경우 0을 반환하도록 처리
|
||
if (!value) return 0;
|
||
return parseFloat(value.replace(/,/g, '')) || 0;
|
||
}
|
||
|
||
// 입력 필드에서 숫자를 포맷팅하는 함수
|
||
function inputNumber(input) {
|
||
const cursorPosition = input.selectionStart; // 현재 커서 위치를 저장
|
||
const value = input.value.replace(/,/g, ''); // 입력값에서 숫자만 남기고 제거
|
||
const formattedValue = Number(value).toLocaleString(); // 천 단위 콤마 추가
|
||
input.value = formattedValue; // 포맷팅된 값으로 설정
|
||
input.setSelectionRange(cursorPosition, cursorPosition); // 커서 위치 유지
|
||
}
|
||
|
||
// 세부내역 행별 합계 계산
|
||
function calculateRowTotal(row) {
|
||
// row를 jQuery 객체로 변환
|
||
row = $(row);
|
||
|
||
// jQuery 객체에서 값을 가져옴
|
||
const itemNameInput = row.find('.item-name');
|
||
const suInput = row.find('.su-input');
|
||
const areaLengthInput = row.find('.area-length-input');
|
||
const areaPriceInput = row.find('.area-price-input');
|
||
const unitPriceInput = row.find('.unit-price-input');
|
||
|
||
const su = suInput.length ? cleanNumber(suInput.val()) : 1;
|
||
const areaLength = areaLengthInput.length ? cleanNumber(areaLengthInput.val()) : 1;
|
||
const areaPrice = areaPriceInput.length ? cleanNumber(areaPriceInput.val()) : 1;
|
||
let unitPrice = unitPriceInput.length ? cleanNumber(unitPriceInput.val()) : 1;
|
||
|
||
const roundedAreaPrice = parseFloat(areaPrice);
|
||
|
||
if (roundedAreaPrice > 0) {
|
||
unitPrice = Math.round(Math.round(areaLength * roundedAreaPrice )); // 소수점 첫째자리 반올림
|
||
unitPriceInput.val(formatNumber(unitPrice)); // 단가 업데이트
|
||
}
|
||
|
||
let totalPrice = 0;
|
||
if (!areaLength && !areaPrice) {
|
||
totalPrice = Math.round(Math.round((su * unitPrice) ) );
|
||
} else if (areaLength && !areaPrice) {
|
||
totalPrice = Math.round(Math.round((areaLength * unitPrice * su) ) );
|
||
} else {
|
||
totalPrice = Math.round(Math.round((su * unitPrice) ) );
|
||
}
|
||
|
||
const totalCell = row.find('.total-price');
|
||
if (totalCell.length) {
|
||
if(totalPrice>200)
|
||
totalCell.text(formatNumber(totalPrice));
|
||
}
|
||
|
||
console.log('totalPrice', totalPrice);
|
||
console.log('itemNameInput', itemNameInput.text());
|
||
console.log('suInput', suInput.val());
|
||
console.log('areaLengthInput', areaLengthInput.val());
|
||
console.log('areaPriceInput', areaPriceInput.val());
|
||
console.log('unitPriceInput', unitPriceInput.val());
|
||
|
||
// return Math.round( parseInt(totalCell.text().replace(/,/g, '')) );
|
||
return totalPrice;
|
||
}
|
||
|
||
// 계산식 첫테이블의 일련번호별 소계 계산 (jQuery 방식)
|
||
function calculateSubtotalBySerial(serialNumber) {
|
||
let subtotal = 0;
|
||
let vat = 0;
|
||
let total = 0;
|
||
const rows = $(`.calculation-row[data-serial="${serialNumber}"]`);
|
||
|
||
rows.each(function() {
|
||
// Math.round()는 소수점 이하를 반올림 (예: 1.4 -> 1, 1.5 -> 2)
|
||
// Math.ceil()은 소수점 이하를 무조건 올림 (예: 1.1 -> 2, 1.9 -> 2)
|
||
// Math.floor()는 소수점 이하를 무조건 버림 (예: 1.1 -> 1, 1.9 -> 1)
|
||
const rowTotal = calculateRowTotal($(this)); // 300 이하는 일련번호로 취급한다. 안전장치이다.
|
||
if (rowTotal > 300) {
|
||
subtotal += rowTotal;
|
||
}
|
||
console.log(' calculateSubtotalBySerial subtotal', subtotal);
|
||
});
|
||
|
||
vat = Math.round(subtotal * 0.1);
|
||
total = subtotal + vat;
|
||
|
||
const subtotalCells = $(`.subtotal-cell[data-serial="${serialNumber}"]`);
|
||
const vatCells = $(`.vat-cell[data-serial="${serialNumber}"]`);
|
||
const totalCells = $(`.subtotalamount-cell[data-serial="${serialNumber}"]`);
|
||
if (subtotalCells.length > 0) {
|
||
subtotalCells.each(function() {
|
||
$(this).text(formatNumber(subtotal));
|
||
});
|
||
} else {
|
||
console.error(`소계 셀을 찾을 수 없습니다. 일련번호: ${serialNumber}`);
|
||
}
|
||
if (vatCells.length > 0) {
|
||
vatCells.each(function() {
|
||
$(this).text(formatNumber(vat));
|
||
});
|
||
}
|
||
if (totalCells.length > 0) {
|
||
totalCells.each(function() {
|
||
$(this).text(formatNumber(total));
|
||
});
|
||
}
|
||
return subtotal;
|
||
}
|
||
|
||
// 모든 일련번호별 소계 및 총합계 계산 (jQuery 방식)
|
||
function calculateAllSubtotals() {
|
||
let grandTotal = 0;
|
||
const uniqueSerials = new Set();
|
||
$('.calculation-row').each(function() {
|
||
uniqueSerials.add($(this).data('serial'));
|
||
});
|
||
|
||
uniqueSerials.forEach(function(serialNumber) {
|
||
grandTotal += calculateSubtotalBySerial(serialNumber);
|
||
});
|
||
|
||
return grandTotal;
|
||
}
|
||
|
||
// 숫자만 남기는 헬퍼
|
||
function cleanNumber(str) {
|
||
return Number(String(str).replace(/[^0-9.-]/g, '')) || 0;
|
||
}
|
||
|
||
// 3자리마다 콤마 찍되, 먼저 소수점 이하는 반올림 처리
|
||
function formatNumber(num) {
|
||
// 1) 문자열이면 콤마 없애고 숫자로 변환
|
||
const value = typeof num === 'string'
|
||
? parseFloat(num.replace(/,/g, ''))
|
||
: num;
|
||
|
||
// 2) 소수점 이하 반올림
|
||
const rounded = Math.round(value || 0);
|
||
|
||
// 3) 천 단위 구분자 추가
|
||
return rounded.toLocaleString();
|
||
}
|
||
|
||
|
||
// 1) grand total 계산 후, 차액까지 계산
|
||
function calculateGrandTotal() {
|
||
// 소수점 이하를 반올림해서 정수 와 원 단위만 남김
|
||
const rawTotal = calculateAllSubtotals();
|
||
const rawVat = Math.round(rawTotal * 0.1);
|
||
const grandTotal = rawTotal;
|
||
const rawSumTotal = grandTotal + rawVat;
|
||
|
||
let finalTotal = 0;
|
||
if (rawVat > 0) {
|
||
finalTotal = rawSumTotal;
|
||
} else {
|
||
finalTotal = grandTotal;
|
||
}
|
||
|
||
const grandTotalCells = $('.grand-total');
|
||
if (grandTotalCells.length > 0) {
|
||
grandTotalCells.each(function() {
|
||
$(this).text(formatNumber(grandTotal));
|
||
});
|
||
$('#totalsum').text(formatNumber(finalTotal));
|
||
// 한글 금액 표기도 반올림된 정수로
|
||
$('#koreantotalsum').text(KoreanNumber(finalTotal));
|
||
|
||
const firstSum = cleanNumber($("#EstimateFirstSum").val());
|
||
if (grandTotal > 0 && rawVat> 0 && firstSum < 1) {
|
||
$("#EstimateFirstSum").val(formatNumber(finalTotal));
|
||
} else {
|
||
$("#EstimateUpdatetSum").val(formatNumber(finalTotal));
|
||
}
|
||
|
||
const updatedSum = cleanNumber($("#EstimateUpdatetSum").val());
|
||
if (updatedSum > 0) {
|
||
$("#EstimateDiffer").val(formatNumber(updatedSum - cleanNumber($("#EstimateFirstSum").val())));
|
||
} else {
|
||
$("#EstimateDiffer").val(0);
|
||
}
|
||
|
||
} else {
|
||
console.error("전체 합계 셀을 찾을 수 없습니다. '.grand-total' 클래스 확인하세요.");
|
||
}
|
||
|
||
let grandVatCells = $('.grand-vat');
|
||
if (grandVatCells.length > 0) {
|
||
grandVatCells.each(function() {
|
||
$(this).text(formatNumber(rawVat));
|
||
});
|
||
} else {
|
||
console.error("전체 합계 셀을 찾을 수 없습니다. '.grand-vat' 클래스 확인하세요.");
|
||
}
|
||
|
||
let grandSumCells = $('.grand-sum');
|
||
if (grandSumCells.length > 0) {
|
||
grandSumCells.each(function() {
|
||
$(this).text(formatNumber(rawSumTotal));
|
||
});
|
||
} else {
|
||
console.error("전체 합계 셀을 찾을 수 없습니다. '.grand-sum' 클래스 확인하세요.");
|
||
}
|
||
}
|
||
|
||
|
||
// 2) '견적확정액', '할인금액', '최종결정금액' 계산 함수
|
||
function calculateEstimateAmounts() {
|
||
// 1) 자동견적(=EstimateFirstSum) / 수동견적(=EstimateUpdatetSum) 값 가져오기
|
||
const autoSum = cleanNumber($("#EstimateFirstSum").val());
|
||
let manualSum = cleanNumber($("#EstimateUpdatetSum").val());
|
||
|
||
// ★ 수동견적과 자동견적이 같으면 수동견적을 지워서 나오지 않게
|
||
if (manualSum > 0 && manualSum === autoSum) {
|
||
manualSum = 0;
|
||
$("#EstimateUpdatetSum").val('');
|
||
}
|
||
|
||
// 2) 견적확정액 결정: 수동견적 있으면 수동, 없으면 자동
|
||
const fixAmount = manualSum > 0 ? manualSum : autoSum;
|
||
$("#EstimateFixAmount").val(formatNumber(fixAmount));
|
||
|
||
// 3) 할인금액 = 견적확정액 × 할인율 ÷ 100
|
||
const discountRate = $("#EstimateDiscountRate").val();
|
||
let discountAmt = fixAmount * discountRate / 100 ;
|
||
|
||
const subtotal = cleanNumber($('#subtotalamount').text());
|
||
|
||
let finalTotal = 0;
|
||
let finalRounded = 0;
|
||
if (discountRate > 0 ) {
|
||
finalTotal = subtotal - discountAmt;
|
||
// 1000원 미만 절사
|
||
finalRounded = Math.floor(finalTotal / 1000) * 1000;
|
||
}
|
||
else
|
||
{
|
||
finalTotal = subtotal ;
|
||
finalRounded = finalTotal ;
|
||
}
|
||
|
||
discountAmt += (finalTotal - finalRounded);
|
||
|
||
$("#EstimateDiscount").val(formatNumber(discountAmt));
|
||
|
||
// 4) 최종결정금액 = 견적확정액 – 할인금액
|
||
const finalSum = fixAmount - discountAmt;
|
||
$("#EstimateFinalSum").val(formatNumber(finalSum));
|
||
|
||
// → 할인/최종 합계 row 갱신
|
||
updateDiscountFooterRow();
|
||
|
||
}
|
||
|
||
// 첫 번째 테이블: 행별 합계 계산 (jQuery 방식)
|
||
function calculateRowTotalFirstTable() {
|
||
const rows = $('.calculation-firstrow');
|
||
|
||
rows.each(function() {
|
||
const suInput = $(this).find('.total-su-input'); // 수량 입력 필드
|
||
const subtotalCell = $(this).find('.subtotal-cell'); // 소계 셀
|
||
const unitPriceCell = $(this).find('.total-unit-price-input'); // 단가 셀
|
||
|
||
const su = suInput ? cleanNumber(suInput.text()) : 1;
|
||
const subtotal = subtotalCell ? cleanNumber(subtotalCell.text()) : 0;
|
||
|
||
let unitPrice = 0;
|
||
if (su > 0) {
|
||
unitPrice = subtotal / su; // 소계를 수량으로 나눠서 단가 계산
|
||
}
|
||
|
||
if (unitPriceCell.length) {
|
||
unitPriceCell.text(formatNumber(unitPrice)); // 단가 셀에 표시
|
||
}
|
||
});
|
||
}
|
||
|
||
function KoreanNumber(number) {
|
||
const koreanNumbers = ['', '일', '이', '삼', '사', '오', '육', '칠', '팔', '구'];
|
||
const koreanUnits = ['', '십', '백', '천'];
|
||
const bigUnits = ['', '만', '억', '조'];
|
||
|
||
let result = '';
|
||
let unitIndex = 0;
|
||
let numberStr = String(number);
|
||
|
||
// 숫자가 0인 경우 '영원'을 반환
|
||
if (number == 0) return '영원';
|
||
|
||
// 뒤에서부터 4자리씩 끊어서 처리
|
||
while (numberStr.length > 0) {
|
||
let chunk = numberStr.slice(-4); // 마지막 4자리
|
||
numberStr = numberStr.slice(0, -4); // 나머지 숫자
|
||
|
||
let chunkResult = '';
|
||
for (let i = 0; i < chunk.length; i++) {
|
||
const digit = parseInt(chunk[i]);
|
||
if (digit > 0) {
|
||
chunkResult += koreanNumbers[digit] + koreanUnits[chunk.length - i - 1];
|
||
}
|
||
}
|
||
if (chunkResult) {
|
||
result = chunkResult + bigUnits[unitIndex] + result;
|
||
}
|
||
unitIndex++;
|
||
}
|
||
// 불필요한 '일십', '일백', '일천' 등의 단위를 제거하고 '원'을 붙여 반환
|
||
result = result.replace(/일(?=십|백|천)/g, '').trim();
|
||
return result + '';
|
||
}
|
||
|
||
function removeAllButLastOccurrence(string, target) {
|
||
// 마지막 '만'의 위치를 찾습니다
|
||
const lastPos = string.lastIndexOf(target);
|
||
|
||
// 마지막 '만'이 없으면 원래 문자열을 반환합니다
|
||
if (lastPos === -1) {
|
||
return string;
|
||
}
|
||
|
||
// 마지막 '만'을 제외한 모든 '만'을 제거합니다
|
||
const beforeLastPos = string.slice(0, lastPos);
|
||
const afterLastPos = string.slice(lastPos);
|
||
|
||
// '만'을 빈 문자열로 대체합니다
|
||
const result = beforeLastPos.replace(new RegExp(target, 'g'), '') + afterLastPos;
|
||
|
||
return result;
|
||
}
|
||
|
||
$(document).ready(function() {
|
||
// 저장 버튼 클릭 시 saveData 함수 호출
|
||
$(".saveBtn").click(function() {
|
||
saveData();
|
||
});
|
||
});
|
||
|
||
function saveData() {
|
||
const myform = document.getElementById('board_form');
|
||
let allValid = true;
|
||
|
||
if (!allValid) return;
|
||
|
||
var num = $("#num").val();
|
||
$("#overlay").show();
|
||
$("button").prop("disabled", true);
|
||
|
||
// 모드 설정 (insert 또는 modify)
|
||
if ($("#mode").val() !== 'copy') {
|
||
if (Number(num) < 1) {
|
||
$("#mode").val('insert');
|
||
} else {
|
||
$("#mode").val('modify');
|
||
}
|
||
} else {
|
||
$("#mode").val('insert');
|
||
}
|
||
|
||
// 데이터 수집 (input 요소만 저장)
|
||
let formData = [];
|
||
$('#detailTable tbody tr').each(function() {
|
||
let rowData = [];
|
||
// 각 tr의 input 요소 순서대로 처리
|
||
$(this).find('input, select').each(function() {
|
||
let value = $(this).val();
|
||
rowData.push(value); // input 값을 배열에 순서대로 추가
|
||
});
|
||
formData.push(rowData); // 각 행의 input 데이터를 배열에 추가
|
||
});
|
||
|
||
// formData는 이제 각 행의 input 값들만 포함하는 배열입니다.
|
||
// console.log('formData:', formData);
|
||
|
||
// JSON 문자열로 변환하여 form input에 설정
|
||
let jsonString = JSON.stringify(formData);
|
||
$('#detailJson').val(jsonString);
|
||
|
||
// console.log('detailJson', jsonString);
|
||
|
||
|
||
|
||
$("#estimateSurang").val('<?= $estimateSurang ?>'); // 수량 저장
|
||
$("#estimateTotal").val($("#EstimateFinalSum").val()); // 최종 결정금액 저장
|
||
|
||
// 총금액 계산 (콤마 제거 후 숫자로 변환)
|
||
const unapproved = parseFloat(($("#ET_unapproved").val() || "0").replace(/,/g, ''));
|
||
const approved = parseFloat(($("#estimateTotal").val() || "0").replace(/,/g, ''));
|
||
const total = unapproved + approved;
|
||
$("#ET_total").val(total.toLocaleString()); // 최종 결정금액 저장
|
||
|
||
if (window.opener && !window.opener.closed) {
|
||
const $parent = window.opener.$; // 부모창의 jQuery 사용
|
||
|
||
// 수량은 있는 그대로
|
||
if ($parent("#estimateSurang").length) {
|
||
$parent("#estimateSurang").val($("#estimateSurang").val());
|
||
}
|
||
|
||
// 금액들은 toLocaleString()으로 콤마 포함
|
||
if ($parent("#estimateTotal").length) {
|
||
$parent("#estimateTotal").val(approved.toLocaleString());
|
||
}
|
||
|
||
if ($parent("#ET_unapproved").length) {
|
||
$parent("#ET_unapproved").val(unapproved.toLocaleString());
|
||
}
|
||
|
||
if ($parent("#ET_total").length) {
|
||
$parent("#ET_total").val(total.toLocaleString());
|
||
}
|
||
}
|
||
|
||
var form = $('#board_form')[0];
|
||
var datasource = new FormData(form);
|
||
|
||
if (ajaxRequest_write !== null) {
|
||
ajaxRequest_write.abort();
|
||
}
|
||
|
||
showMsgModal(2); // 파일저장중
|
||
|
||
// Ajax 요청으로 서버에 데이터 전송
|
||
ajaxRequest_write = $.ajax({
|
||
enctype: 'multipart/form-data',
|
||
processData: false,
|
||
contentType: false,
|
||
cache: false,
|
||
timeout: 600000,
|
||
url: "/estimate/insert_detail_output.php", // 산출내역 저장
|
||
type: "post",
|
||
data: datasource,
|
||
dataType: "json",
|
||
success: function(data) {
|
||
console.log(data);
|
||
setTimeout(function() {
|
||
$(opener.location).attr("href", "javascript:restorePageNumber();");
|
||
setTimeout(function() {
|
||
hideMsgModal();
|
||
hideOverlay();
|
||
window.close();
|
||
}, 1000);
|
||
}, 1000);
|
||
},
|
||
error: function(jqxhr, status, error) {
|
||
console.log(jqxhr, status, error);
|
||
}
|
||
});
|
||
}
|
||
|
||
|
||
function generatePDF() {
|
||
var title_message = '<?php echo $title_message; ?>';
|
||
var workplace = '<?php echo $outworkplace; ?>';
|
||
var deadline = '<?php echo $indate; ?>';
|
||
var deadlineDate = new Date(deadline);
|
||
var formattedDate = "(" + String(deadlineDate.getFullYear()).slice(-2) + "." + ("0" + (deadlineDate.getMonth() + 1)).slice(-2) + "." + ("0" + deadlineDate.getDate()).slice(-2) + ")";
|
||
var result = 'KD' + title_message + '(' + workplace +')' + formattedDate + '.pdf';
|
||
|
||
var element = document.getElementById('content-to-print');
|
||
var opt = {
|
||
margin: [10, 3, 12, 3], // Top, right, bottom, left margins
|
||
filename: result,
|
||
image: { type: 'jpeg', quality: 1 },
|
||
html2canvas: {
|
||
scale: 3, // ★★★ 해상도(기본은 1) → 2~4 정도로 높이면 선명해짐 4는 용량이 매우 큽니다. 보통 3M 이상
|
||
useCORS: true,
|
||
scrollY: 0,
|
||
scrollX: 0,
|
||
windowWidth: document.body.scrollWidth,
|
||
windowHeight: document.body.scrollHeight
|
||
},
|
||
jsPDF: { unit: 'mm', format: 'a4', orientation: 'portrait' },
|
||
pagebreak: {
|
||
mode: ['css', 'legacy'],
|
||
avoid: ['tr', '.avoid-break'] // 핵심
|
||
}
|
||
};
|
||
html2pdf().from(element).set(opt).save();
|
||
}
|
||
|
||
|
||
function generatePDF_server(callback) {
|
||
var workplace = '<?php echo $title_message; ?>';
|
||
var item = '<?php echo $emailTitle; ?>';
|
||
var today = new Date();
|
||
var formattedDate = "(" + String(today.getFullYear()).slice(-2) + "." + ("0" + (today.getMonth() + 1)).slice(-2) + "." + ("0" + today.getDate()).slice(-2) + ")";
|
||
var result = 'KD' + item +'(' + workplace + ')' + formattedDate + '.pdf';
|
||
|
||
var element = document.getElementById('content-to-print');
|
||
var opt = {
|
||
margin: [10, 3, 12, 3], // Top, right, bottom, left margins
|
||
filename: result,
|
||
image: { type: 'jpeg', quality: 0.70 },
|
||
html2canvas: { scale: 4 },
|
||
jsPDF: { unit: 'mm', format: 'a4', orientation: 'portrait' },
|
||
pagebreak: { mode: [''] }
|
||
};
|
||
|
||
html2pdf().from(element).set(opt).output('datauristring').then(function (pdfDataUri) {
|
||
var pdfBase64 = pdfDataUri.split(',')[1]; // Base64 인코딩된 PDF 데이터 추출
|
||
var formData = new FormData();
|
||
formData.append('pdf', pdfBase64);
|
||
formData.append('filename', result);
|
||
|
||
$.ajax({
|
||
type: 'POST',
|
||
url: '/email/save_pdf.php', // PDF 파일을 저장하는 PHP 파일
|
||
data: formData,
|
||
processData: false,
|
||
contentType: false,
|
||
success: function (response) {
|
||
var res = JSON.parse(response);
|
||
if (callback) {
|
||
callback(res.filename); // 서버에 저장된 파일 경로를 콜백으로 전달
|
||
}
|
||
},
|
||
error: function (xhr, status, error) {
|
||
Swal.fire('Error', 'PDF 저장에 실패했습니다.', 'error');
|
||
}
|
||
});
|
||
});
|
||
}
|
||
|
||
var ajaxRequest = null;
|
||
|
||
function sendmail() {
|
||
var secondordnum = '<?php echo $secondordnum; ?>'; // 서버에서 가져온 값
|
||
var item = '<?php echo $emailTitle; ?>';
|
||
|
||
if (!secondordnum) {
|
||
Swal.fire({
|
||
icon: 'warning',
|
||
title: '오류 알림',
|
||
text: '발주처 코드가 없습니다.'
|
||
});
|
||
return; // 함수 종료
|
||
}
|
||
|
||
if (typeof ajaxRequest !== 'undefined' && ajaxRequest !== null) {
|
||
ajaxRequest.abort();
|
||
}
|
||
|
||
ajaxRequest = $.ajax({
|
||
type: 'POST',
|
||
url: '/email/get_companyCode.php',
|
||
data: { secondordnum: secondordnum },
|
||
dataType: 'json',
|
||
success: function(response) {
|
||
console.log('response : ', response);
|
||
if (response.error) {
|
||
Swal.fire('Error', response.error, 'error');
|
||
} else {
|
||
var email = response.email;
|
||
var vendorName = response.vendor_name;
|
||
|
||
Swal.fire({
|
||
title: 'E메일 보내기',
|
||
text: vendorName + ' Email 주소확인',
|
||
icon: 'warning',
|
||
input: 'text', // input 창을 텍스트 필드로 설정
|
||
inputLabel: 'Email 주소 수정 가능',
|
||
inputValue: email, // 기존 이메일 주소를 기본값으로 설정
|
||
showCancelButton: true,
|
||
confirmButtonText: '보내기',
|
||
cancelButtonText: '취소',
|
||
reverseButtons: true,
|
||
inputValidator: (value) => {
|
||
if (!value) {
|
||
return '이메일 주소를 입력해주세요!';
|
||
}
|
||
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||
if (!emailPattern.test(value)) {
|
||
return '올바른 이메일 형식을 입력해주세요!';
|
||
}
|
||
}
|
||
}).then((result) => {
|
||
if (result.isConfirmed) {
|
||
const updatedEmail = result.value; // 입력된 이메일 주소 가져오기
|
||
generatePDF_server(function(filename) {
|
||
sendEmail(updatedEmail, vendorName, item, filename);
|
||
});
|
||
}
|
||
});
|
||
|
||
}
|
||
},
|
||
error: function(xhr, status, error) {
|
||
Swal.fire('Error', '전송중 오류가 발생했습니다.', 'error');
|
||
}
|
||
});
|
||
}
|
||
|
||
function sendEmail(recipientEmail, vendorName, item, filename) {
|
||
if (typeof ajaxRequest !== 'undefined' && ajaxRequest !== null) {
|
||
ajaxRequest.abort();
|
||
}
|
||
var today = new Date();
|
||
var formattedDate = "(" + String(today.getFullYear()).slice(-2) + "." + ("0" + (today.getMonth() + 1)).slice(-2) + "." + ("0" + today.getDate()).slice(-2) + ")";
|
||
|
||
ajaxRequest = $.ajax({
|
||
type: 'POST',
|
||
url: '/email/send_email.php', // 이메일 전송을 처리하는 PHP 파일
|
||
data: { email: recipientEmail, vendorName: vendorName, filename: filename, item: item, formattedDate: formattedDate },
|
||
success: function(response) {
|
||
console.log(response);
|
||
Swal.fire('Success', '정상적으로 전송되었습니다.', 'success');
|
||
},
|
||
error: function(xhr, status, error) {
|
||
Swal.fire('Error', '전송에 실패했습니다. 확인바랍니다.', 'error');
|
||
}
|
||
});
|
||
}
|
||
|
||
$(document).ready(function() {
|
||
// 재계산 버튼 클릭 이벤트
|
||
$('.initialBtn').on('click', function() {
|
||
// 재계산 확인 알림
|
||
Swal.fire({
|
||
title: '견적데이터 재계산',
|
||
text: "견적 데이터를 재계산 하시겠습니까?",
|
||
icon: 'warning',
|
||
showCancelButton: true,
|
||
confirmButtonColor: '#3085d6',
|
||
cancelButtonColor: '#d33',
|
||
confirmButtonText: '예, 재계산합니다',
|
||
cancelButtonText: '취소'
|
||
}).then((result) => {
|
||
if (result.isConfirmed) {
|
||
|
||
$("#estimateTotal").val(0); // 견적총액 저장
|
||
$("#EstimateFirstSum").val(0); // 견적초기금액
|
||
$("#EstimateUpdatetSum").val(0); // 수정금액
|
||
$("#EstimateDiffer").val(0); // 차액금액
|
||
$("#EstimateDiscountRate").val(0); // 할인율
|
||
$("#EstimateDiscount").val(0); // 할인금액
|
||
$("#EstimateFinalSum").val(0); // 최종결정금액
|
||
var form = $('#board_form')[0];
|
||
var datasource = new FormData(form);
|
||
|
||
const initialData = JSON.stringify([]); // 빈 배열로 재계산
|
||
|
||
$('#detailJson').val(initialData);
|
||
|
||
// 재계산된 데이터 저장 요청
|
||
$.ajax({
|
||
enctype: 'multipart/form-data',
|
||
processData: false,
|
||
contentType: false,
|
||
cache: false,
|
||
timeout: 600000,
|
||
url: "/estimate/insert_detail_output.php",
|
||
type: "post",
|
||
data: datasource,
|
||
dataType: "json",
|
||
success: function(response) {
|
||
Swal.fire({
|
||
title: '재계산 완료',
|
||
text: "모든 데이터가 재계산되었습니다.",
|
||
icon: 'success',
|
||
confirmButtonText: '확인'
|
||
}).then(() => {
|
||
hideMsgModal();
|
||
// 페이지 새로고침
|
||
location.reload();
|
||
});
|
||
},
|
||
error: function(jqxhr, status, error) {
|
||
Swal.fire({
|
||
title: '오류',
|
||
text: "재계산 중 오류가 발생했습니다.",
|
||
icon: 'error',
|
||
confirmButtonText: '확인'
|
||
});
|
||
console.log("AJAX Error: ", status, error);
|
||
}
|
||
});
|
||
}
|
||
});
|
||
});
|
||
});
|
||
|
||
// ───────────────────────────────────────────────────
|
||
// 할인/최종 합계 표시 함수 (이미 정의된 cleanNumber, formatNumber 사용)
|
||
function updateDiscountFooterRow() {
|
||
const rate = $('#EstimateDiscountRate').val();
|
||
const subtotal = cleanNumber($('#subtotalamount').text());
|
||
|
||
// 기존 tfoot 제거
|
||
$('#tableDetail tfoot').remove();
|
||
|
||
let discount = 0;
|
||
let finalTotal = 0;
|
||
let finalRounded = 0;
|
||
|
||
if (rate <= 0 ) {
|
||
discount = 0;
|
||
finalTotal = subtotal;
|
||
finalRounded = finalTotal;
|
||
} else {
|
||
discount = Math.round(subtotal * Number(rate) / 100);
|
||
finalTotal = subtotal - discount;
|
||
finalRounded = Math.floor(finalTotal / 1000) * 1000;
|
||
discount += (finalTotal - finalRounded);
|
||
}
|
||
|
||
if (rate > 0) {
|
||
const tfootHTML = `
|
||
<tfoot>
|
||
<tr class="bg-light">
|
||
<td colspan="8" class="text-end fw-bold">할인금액</td>
|
||
<td colspan="4" class="text-end text-danger"> - ${formatNumber(discount)}</td>
|
||
</tr>
|
||
<tr class="bg-secondary text-white">
|
||
<td colspan="8" class="text-end fw-bold">최종 합계</td>
|
||
<td colspan="4" class="text-end fw-bold">${formatNumber(finalRounded)}</td>
|
||
</tr>
|
||
</tfoot>
|
||
`;
|
||
$('#tableDetail').append(tfootHTML);
|
||
|
||
// 화면 하단 두 요소에도 최종합계 반영
|
||
$('#totalsum').text(formatNumber(finalRounded));
|
||
$('#koreantotalsum').text(KoreanNumber(finalRounded));
|
||
}
|
||
}
|
||
|
||
|
||
// ───────────────────────────────────────────────────
|
||
// 페이지 로드 시 & 할인율 변경 시 바인딩만 추가
|
||
$(document).ready(function() {
|
||
setTimeout(function() {
|
||
updateDiscountFooterRow();
|
||
}, 1000);
|
||
});
|
||
|
||
|
||
|
||
$(document).ready(function() {
|
||
// 산출내역서 보이기/숨기기
|
||
function toggleEstimateView() {
|
||
$('#showEstimateCheckbox').is(':checked')
|
||
? $('.Estimateview').show()
|
||
: $('.Estimateview').hide();
|
||
}
|
||
|
||
// 소요자재 보이기/숨기기 (vendor-send 토글 로직 제거)
|
||
function toggleListView() {
|
||
$('#showlistCheckbox').is(':checked')
|
||
? $('.listview, .vendor-send, .Novendor-send').show()
|
||
: $('.listview, .vendor-send, .Novendor-send').hide();
|
||
}
|
||
|
||
// 업체발송용 영역 토글
|
||
function toggleVendorDiv() {
|
||
|
||
// 업체발송용 클릭시 나머지 두개의 체크박스를 해제한다.
|
||
$('#showEstimateCheckbox').prop('checked', false);
|
||
$('#showlistCheckbox').prop('checked', false);
|
||
toggleEstimateView();
|
||
toggleListView();
|
||
if ($('#showVendorCheckbox').is(':checked')) {
|
||
$('.vendor-send').show();
|
||
$('.Novendor-send').hide();
|
||
} else {
|
||
$('.vendor-send').hide();
|
||
$('.Novendor-send').show();
|
||
}
|
||
}
|
||
|
||
var option = $('#option').val(); // option은 견적서 화면에서 업체발송용으로 강제 지정
|
||
|
||
if (option === 'option') {
|
||
// 초기 상태 반영
|
||
toggleEstimateView();
|
||
toggleListView();
|
||
toggleVendorDiv();
|
||
}
|
||
|
||
// 산출내역서 체크박스 이벤트
|
||
$('#showEstimateCheckbox').on('change', toggleEstimateView);
|
||
|
||
// 소요자재 체크박스 이벤트
|
||
$('#showlistCheckbox').on('change', function() {
|
||
toggleListView();
|
||
|
||
// 소요자재 클릭 시 "업체발송용" 체크 해제 + 영역 토글
|
||
$('#showVendorCheckbox').prop('checked', false);
|
||
});
|
||
|
||
// 업체발송용 체크박스 이벤트
|
||
$('#showVendorCheckbox').on('change', toggleVendorDiv);
|
||
|
||
// 할인율 변경시
|
||
$('#EstimateDiscountRate').on('input change', function() {
|
||
updateDiscountFooterRow();
|
||
calculateEstimateAmounts();
|
||
});
|
||
|
||
});
|
||
|
||
|
||
</script>
|
||
|
||
|
||
|