일단 브랜치에 커밋

This commit is contained in:
leeheejin
2025-11-12 18:29:20 +09:00
parent fdd5346c99
commit 606172fada
7 changed files with 1310 additions and 130 deletions

View File

@@ -1420,31 +1420,6 @@ ORDER BY T.REGDATE DESC, T.PROJECT_NO DESC
)
</insert>
<!--
/**
* 출하일 상세 내역 조회 - 분할 출하된 날짜별 수량 조회
* PROJECT_NO로 조회
* @since 2025.11.12
* @author assistant
* @version 1.0
**/
-->
<select id="getShippingDetailList" parameterType="map" resultType="map">
/* salesNcollectMgmt.getShippingDetailList - 출하일 클릭 시 팝업에 표시할 분할 출하 내역 */
-- PROJECT_MGMT 기준으로 조회 (조건 완화: LIKE, UPPER)
SELECT
T.PROJECT_NO AS DEBUG_PROJECT_NO,
SR.project_no AS DEBUG_SR_PROJECT_NO,
COALESCE(TO_CHAR(SR.shipping_date, 'YYYY-MM-DD'), '출하일 미등록') AS SHIPPING_DATE,
COALESCE(SR.sales_quantity, 0) AS SHIPPING_QUANTITY,
COALESCE(SR.shipping_order_status, '미등록') AS SHIPPING_ORDER_STATUS,
COALESCE(SR.serial_no, '-') AS SERIAL_NO
FROM PROJECT_MGMT AS T
LEFT JOIN sales_registration SR ON TRIM(UPPER(T.PROJECT_NO)) = TRIM(UPPER(SR.project_no))
WHERE TRIM(UPPER(T.PROJECT_NO)) LIKE TRIM(UPPER(#{projectNo}))
</select>
<!--
/**
* 판매 정보 조회 - 판매등록 팝업에서 사용
@@ -1591,40 +1566,43 @@ ORDER BY T.REGDATE DESC, T.PROJECT_NO DESC
-->
<select id="getOrderDataByOrderNo" parameterType="map" resultType="map">
/* salesNcollectMgmt.getOrderDataByOrderNo - orderNo로 판매등록용 수주 데이터 조회 */
SELECT
-- 기본 정보
CM.CONTRACT_NO AS ORDER_NO,
CM.OBJID AS CONTRACT_OBJID,
-- 수주 금액 정보 (CONTRACT_ITEM 테이블에서 합산) - VARCHAR 타입이므로 NUMERIC으로 캐스팅
COALESCE(SUM(CI.ORDER_QUANTITY::NUMERIC), 0) AS SALES_QUANTITY,
COALESCE(ROUND(AVG(CI.ORDER_UNIT_PRICE::NUMERIC), 2), 0) AS SALES_UNIT_PRICE,
COALESCE(SUM(CI.ORDER_SUPPLY_PRICE::NUMERIC), 0) AS SALES_SUPPLY_PRICE,
COALESCE(SUM(CI.ORDER_VAT::NUMERIC), 0) AS SALES_VAT,
COALESCE(SUM(CI.ORDER_TOTAL_AMOUNT::NUMERIC), 0) AS SALES_TOTAL_AMOUNT,
-- 환종 정보
CM.CONTRACT_CURRENCY AS SALES_CURRENCY,
CODE_NAME(CM.CONTRACT_CURRENCY) AS SALES_CURRENCY_NAME,
COALESCE(CM.EXCHANGE_RATE::NUMERIC, 0) AS SALES_EXCHANGE_RATE,
-- 수주 날짜 - VARCHAR 타입이므로 그대로 사용
CM.ORDER_DATE AS SHIPPING_DATE,
-- 담당자
CM.PM_USER_ID AS MANAGER
FROM PROJECT_MGMT PM
INNER JOIN CONTRACT_MGMT CM ON PM.CONTRACT_OBJID = CM.OBJID
LEFT JOIN CONTRACT_ITEM CI ON CM.OBJID::VARCHAR = CI.CONTRACT_OBJID AND UPPER(CI.STATUS) = 'ACTIVE'
WHERE PM.PROJECT_NO = #{orderNo}
GROUP BY
CM.CONTRACT_NO,
CM.OBJID,
CM.CONTRACT_CURRENCY,
CM.EXCHANGE_RATE,
CM.ORDER_DATE,
CM.PM_USER_ID
SELECT
-- 기본 정보
CM.CONTRACT_NO AS ORDER_NO,
CM.OBJID AS CONTRACT_OBJID,
-- 수주 수량 정보 (PROJECT_MGMT에서 직접 가져오기)
COALESCE(PM.QUANTITY::NUMERIC, 0) AS SALES_QUANTITY,
-- 수주 금액 정보 (CONTRACT_ITEM 테이블에서 합산) - VARCHAR 타입이므로 NULLIF로 빈 문자열 제거 후 NUMERIC 캐스팅
COALESCE(ROUND(AVG(NULLIF(CI.ORDER_UNIT_PRICE, '')::NUMERIC), 2), 0) AS SALES_UNIT_PRICE,
COALESCE(SUM(NULLIF(CI.ORDER_SUPPLY_PRICE, '')::NUMERIC), 0) AS SALES_SUPPLY_PRICE,
COALESCE(SUM(NULLIF(CI.ORDER_VAT, '')::NUMERIC), 0) AS SALES_VAT,
COALESCE(SUM(NULLIF(CI.ORDER_TOTAL_AMOUNT, '')::NUMERIC), 0) AS SALES_TOTAL_AMOUNT,
-- 환종 정보
CM.CONTRACT_CURRENCY AS SALES_CURRENCY,
CODE_NAME(CM.CONTRACT_CURRENCY) AS SALES_CURRENCY_NAME,
COALESCE(NULLIF(CM.EXCHANGE_RATE, '')::NUMERIC, 0) AS SALES_EXCHANGE_RATE,
-- 수주 날짜 - VARCHAR 타입이므로 그대로 사용
CM.ORDER_DATE AS SHIPPING_DATE,
-- 담당자
CM.PM_USER_ID AS MANAGER
FROM PROJECT_MGMT PM
INNER JOIN CONTRACT_MGMT CM ON PM.CONTRACT_OBJID = CM.OBJID
LEFT JOIN CONTRACT_ITEM CI ON CM.OBJID::VARCHAR = CI.CONTRACT_OBJID AND UPPER(CI.STATUS) = 'ACTIVE'
WHERE PM.PROJECT_NO = #{orderNo}
GROUP BY
CM.CONTRACT_NO,
CM.OBJID,
CM.CONTRACT_CURRENCY,
CM.EXCHANGE_RATE,
CM.ORDER_DATE,
CM.PM_USER_ID,
PM.QUANTITY
</select>
<!--
@@ -1768,5 +1746,35 @@ ORDER BY T.REGDATE DESC, T.PROJECT_NO DESC
ORDER BY shipping_date DESC, project_no
</select>
<!-- 거래명세서 - 고객 정보 조회 -->
<select id="getCustomerInfoByProjectNo" parameterType="map" resultType="map">
/* salesNcollectMgmt.getCustomerInfoByProjectNo - 프로젝트 번호로 고객 정보 조회 */
SELECT
SM.SUPPLY_NAME AS CUSTOMER_NAME,
SM.BUSINESS_NO AS CUSTOMER_REG_NO,
SM.ADDRESS AS CUSTOMER_ADDRESS,
SM.BUSINESS_TYPE AS CUSTOMER_BUSINESS,
SM.BUSINESS_ITEM AS CUSTOMER_TYPE,
SM.TEL_NO AS CUSTOMER_CONTACT
FROM PROJECT_MGMT PM
INNER JOIN SUPPLY_MNG SM ON PM.CUSTOMER_OBJID::NUMERIC = SM.OBJID
WHERE PM.PROJECT_NO = #{projectNo}
</select>
<!-- 거래명세서 - 품목 정보 조회 -->
<select id="getTransactionStatementItem" parameterType="map" resultType="map">
/* salesNcollectMgmt.getTransactionStatementItem - 프로젝트 번호로 품목 정보 조회 */
SELECT
PM.PART_NAME AS productName,
PM.PART_NO AS spec,
COALESCE(SR.sales_quantity, PM.QUANTITY) AS quantity,
COALESCE(SR.sales_unit_price, 0) AS unitPrice,
COALESCE(SR.sales_supply_price, 0) AS supplyPrice,
COALESCE(SR.sales_vat, 0) AS vat
FROM PROJECT_MGMT PM
LEFT JOIN sales_registration SR ON PM.PROJECT_NO = SR.project_no
WHERE PM.PROJECT_NO = #{projectNo}
</select>
</mapper>

View File

@@ -134,35 +134,54 @@
function fn_openSaleRegPopupWithData(rowData){
console.log("=== fn_openSaleRegPopupWithData 호출 ===");
console.log("rowData:", rowData);
console.log("SALES_QUANTITY:", rowData.SALES_QUANTITY);
console.log("SALES_UNIT_PRICE:", rowData.SALES_UNIT_PRICE);
console.log("SALES_SUPPLY_PRICE:", rowData.SALES_SUPPLY_PRICE);
console.log("SALES_VAT:", rowData.SALES_VAT);
console.log("SALES_TOTAL_AMOUNT:", rowData.SALES_TOTAL_AMOUNT);
console.log("SALES_CURRENCY:", rowData.SALES_CURRENCY);
console.log("SALES_EXCHANGE_RATE:", rowData.SALES_EXCHANGE_RATE);
var popup_width = 850;
var popup_height = 550;
// 기본 파라미터
// 기본 파라미터만 전달 (orderNo, saleNo)
// 잔량 계산은 Controller에서 자동으로 처리됨
var params = "orderNo=" + encodeURIComponent(rowData.PROJECT_NO);
params += "&saleNo=" + (rowData.SALE_NO ? encodeURIComponent(rowData.SALE_NO) : "");
// 그리드에서 표시되고 있는 금액 정보 전달
if(rowData.SALES_QUANTITY !== undefined && rowData.SALES_QUANTITY !== null) params += "&salesQuantity=" + encodeURIComponent(rowData.SALES_QUANTITY);
if(rowData.SALES_UNIT_PRICE !== undefined && rowData.SALES_UNIT_PRICE !== null) params += "&salesUnitPrice=" + encodeURIComponent(rowData.SALES_UNIT_PRICE);
if(rowData.SALES_SUPPLY_PRICE !== undefined && rowData.SALES_SUPPLY_PRICE !== null) params += "&salesSupplyPrice=" + encodeURIComponent(rowData.SALES_SUPPLY_PRICE);
if(rowData.SALES_VAT !== undefined && rowData.SALES_VAT !== null) params += "&salesVat=" + encodeURIComponent(rowData.SALES_VAT);
if(rowData.SALES_TOTAL_AMOUNT !== undefined && rowData.SALES_TOTAL_AMOUNT !== null) params += "&salesTotalAmount=" + encodeURIComponent(rowData.SALES_TOTAL_AMOUNT);
if(rowData.SALES_CURRENCY) params += "&salesCurrency=" + encodeURIComponent(rowData.SALES_CURRENCY);
if(rowData.SALES_EXCHANGE_RATE !== undefined && rowData.SALES_EXCHANGE_RATE !== null) params += "&salesExchangeRate=" + encodeURIComponent(rowData.SALES_EXCHANGE_RATE);
// 금액 정보는 Controller에서 자동으로 불러옴 (수주 데이터 기반)
// URL 파라미터로 전달하지 않음
var url = "/salesMgmt/salesRegForm.do?" + params;
console.log("최종 URL:", url);
fn_centerPopup(popup_width, popup_height, url);
}
// 거래명세서 출력 함수
function fn_printTransactionStatement() {
var selectedData = _tabulGrid.getSelectedData();
if(selectedData.length === 0) {
alert("거래명세서를 출력할 항목을 선택해주세요.");
return;
}
// 같은 거래처인지 확인
var firstCustomer = selectedData[0].CUSTOMER;
for(var i = 1; i < selectedData.length; i++) {
if(selectedData[i].CUSTOMER !== firstCustomer) {
alert("같은 거래처만 선택 가능합니다.\n선택한 거래처: " + firstCustomer + ", " + selectedData[i].CUSTOMER);
return;
}
}
// 프로젝트 번호들을 수집
var projectNos = selectedData.map(function(row) {
return row.PROJECT_NO;
}).join(',');
// 새로 만든 거래명세서 팝업 열기
var popup_width = 1000;
var popup_height = 800;
var url = "/salesMgmt/transactionStatementForm.do?projectNos=" + encodeURIComponent(projectNos);
fn_centerPopup(popup_width, popup_height, url);
}
function fn_FileRegist(objId, docType, docTypeName){
var popup_width = 800;
var popup_height = 680;
@@ -418,6 +437,7 @@ function fn_bulkRegister(){
</h2>
<div class="btnArea">
<input type="button" value="조회" class="plm_btns" id="btnSearch">
<input type="button" value="거래명세서 출력" class="plm_btns" id="btnTransactionStatement" style="background-color: #2196F3; color: white;">
<input type="button" value="출하지시/판매등록" class="plm_btns" id="btnBulkRegister" style="background-color: #4CAF50; color: white;">
</div>
</div>

View File

@@ -3,6 +3,8 @@
<%@ page import="com.pms.common.utils.*"%>
<%@ page import="java.util.*"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions"%>
<%@include file="/init_new.jsp"%>
<%
PersonBean person = (PersonBean) session.getAttribute(Constants.PERSON_BEAN);
@@ -72,13 +74,77 @@
self.close();
});
// 저장 버튼
$("#btnSave").click(function() {
fn_save();
});
// 저장 버튼
$("#btnSave").click(function() {
fn_save();
});
// 품목이 여러 개인 경우 자동으로 첫 번째 품목 선택 - 주석처리: 품목은 하나만 존재
/*
if($(".item-radio").length > 0) {
$(".item-radio").first().prop("checked", true);
fn_calculateSelectedItem();
}
*/
});
// 판매공급가액 계산 함수
// === Phase 2: 품목 선택 관련 함수 (단일 선택) - 주석처리: 품목은 하나만 존재 ===
/*
// 행 클릭 시 라디오 버튼 선택
function fn_selectItem(itemObjid) {
$(".item-radio[value='" + itemObjid + "']").prop("checked", true);
fn_calculateSelectedItem();
}
// 선택한 품목의 정보를 입력 필드에 반영
function fn_calculateSelectedItem() {
var selectedRadio = $(".item-radio:checked");
if(selectedRadio.length === 0) {
$("#selectedItemInfo").text("품목을 선택하세요");
return;
}
var quantity = parseFloat(selectedRadio.data("quantity")) || 0;
var unitPrice = parseFloat(selectedRadio.data("unit-price")) || 0;
var supplyPrice = parseFloat(selectedRadio.data("supply-price")) || 0;
var vat = parseFloat(selectedRadio.data("vat")) || 0;
var totalAmount = parseFloat(selectedRadio.data("total")) || 0;
// 선택한 품목 정보 표시
var itemObjid = selectedRadio.val();
var itemRow = $("tr[data-item-objid='" + itemObjid + "']");
var partNo = itemRow.find("td:eq(1)").text().trim();
var partName = itemRow.find("td:eq(2)").text().trim();
$("#selectedItemInfo").html(
"<strong>" + partName + "</strong> (" + partNo + ") - " +
"수량: <strong>" + quantity.toLocaleString() + "</strong>개 | " +
"공급가액: <strong>" + supplyPrice.toLocaleString() + "</strong>원"
);
// 입력 필드에 자동 반영
$("#salesQuantity").val(quantity);
$("#salesUnitPrice").val(unitPrice);
$("#salesSupplyPrice").val(supplyPrice);
$("#salesVat").val(vat);
$("#salesTotalAmount").val(totalAmount);
// hidden 필드에 선택한 품목 OBJID 저장
if($("#selectedItemObjid").length === 0) {
$("<input>").attr({
type: "hidden",
id: "selectedItemObjid",
name: "selectedItemObjid"
}).appendTo("form");
}
$("#selectedItemObjid").val(itemObjid);
}
*/
// === 기존 함수들 ===
// 판매공급가액 계산 함수
function fn_calculateSupplyPrice() {
var currency = $("#salesCurrency").val();
var exchangeRate = parseFloat($("#salesExchangeRate").val()) || 1;
@@ -117,6 +183,7 @@
// 판매환종 초기값 설정 (견적환종과 동기화하되, 사용자가 변경 가능)
function initializeSalesCurrency() {
// Controller에서 계산한 값 사용 (잔량 기반)
var existingSalesCurrency = "${saleInfo.SALES_CURRENCY}";
var contractCurrency = "${orderInfo.SALES_CURRENCY}"; // 견적환종 (SALES_CURRENCY로 통일)
var contractExchangeRate = "${orderInfo.SALES_EXCHANGE_RATE}"; // 견적환율
@@ -532,19 +599,32 @@
if(remainingQuantity > 0) {
if(confirm("잔량 " + remainingQuantity + "개가 남았습니다. 계속 등록하시겠습니까?")) {
// 부모 창 새로고침 후 팝업 새로고침
if(opener && opener.fn_search) {
console.log("분할 출하 계속 - 부모 창 fn_search 호출");
opener.fn_search();
}
// 팝업 새로고침 (잔량으로 수량 자동 설정)
location.reload();
} else {
// 목록 새로고침 후 팝업 닫기
console.log("분할 출하 중단 - 팝업 닫기");
if(opener && opener.fn_search) {
console.log("부모 창 fn_search 호출");
opener.fn_search();
} else {
console.log("부모 창 fn_search 없음!");
}
self.close();
}
} else {
// 목록 새로고침 후 팝업 닫기
console.log("잔량 없음 - 팝업 닫기");
if(opener && opener.fn_search) {
console.log("부모 창 fn_search 호출");
opener.fn_search();
} else {
console.log("부모 창 fn_search 없음!");
}
self.close();
}
@@ -648,6 +728,67 @@
<col width="35%" />
</colgroup>
<!-- 품목 선택 영역 (Phase 2) - 주석처리: 품목은 하나만 존재 -->
<%--
<c:if test="${not empty projectItems and fn:length(projectItems) > 1}">
<tr>
<td colspan="4" style="padding:10px;">
<div style="border:1px solid #ddd; padding:10px; background:#f9f9f9;">
<strong style="display:block; margin-bottom:8px;">📦 품목 선택 (${fn:length(projectItems)}개) - 하나만 선택 가능</strong>
<table style="width:100%; border-collapse:collapse;">
<thead>
<tr style="background:#e9ecef; border-bottom:2px solid #dee2e6;">
<th style="padding:8px; text-align:center; width:50px;">선택</th>
<th style="padding:8px; text-align:left;">품번</th>
<th style="padding:8px; text-align:left;">품명</th>
<th style="padding:8px; text-align:right;">수량</th>
<th style="padding:8px; text-align:right;">단가</th>
<th style="padding:8px; text-align:right;">공급가액</th>
</tr>
</thead>
<tbody id="itemListBody">
<c:forEach var="item" items="${projectItems}" varStatus="status">
<tr style="border-bottom:1px solid #dee2e6; cursor:pointer;"
data-item-objid="${item.ITEM_OBJID}"
onclick="fn_selectItem('${item.ITEM_OBJID}')">
<td style="padding:8px; text-align:center;">
<input type="radio" name="selectedItem" class="item-radio"
value="${item.ITEM_OBJID}"
data-objid="${item.ITEM_OBJID}"
data-quantity="${item.QUANTITY}"
data-unit-price="${item.UNIT_PRICE}"
data-supply-price="${item.SUPPLY_PRICE}"
data-vat="${item.VAT}"
data-total="${item.TOTAL_AMOUNT}"
onchange="fn_calculateSelectedItem()" />
</td>
<td style="padding:8px;">${item.PART_NO}</td>
<td style="padding:8px;">${item.PART_NAME}</td>
<td style="padding:8px; text-align:right;">
<fmt:formatNumber value="${item.QUANTITY}" pattern="#,##0" />
</td>
<td style="padding:8px; text-align:right;">
<fmt:formatNumber value="${item.UNIT_PRICE}" pattern="#,##0" />
</td>
<td style="padding:8px; text-align:right;">
<fmt:formatNumber value="${item.SUPPLY_PRICE}" pattern="#,##0" />
</td>
</tr>
</c:forEach>
</tbody>
</table>
<div style="margin-top:10px; padding:8px; background:#d1ecf1; border:1px solid #0c5460; border-radius:4px;">
<strong>💡 선택한 품목:</strong>
<span id="selectedItemInfo" style="margin-left:10px; color:#0c5460;">
품목을 선택하세요
</span>
</div>
</div>
</td>
</tr>
</c:if>
--%>
<!-- 첫번째 행: S/N -->
<tr>
<td class="input_title"><label for="serialNo">S/N</label></td>
@@ -660,11 +801,19 @@
</td>
</tr>
<!-- 두번째 행: 판매수량, 출하일 -->
<!-- 두번째 행: 판매수량 (잔량 자동 표시), 출하일 -->
<tr>
<td class="input_title"><label for="salesQuantity">판매수량</label></td>
<td class="input_title">
<label for="salesQuantity">판매수량</label>
<span style="color:#666; font-size:11px; display:block; margin-top:2px;">
(잔량: ${saleInfo.SALES_QUANTITY}개)
</span>
</td>
<td>
<input type="number" name="salesQuantity" id="salesQuantity" value="${saleInfo.SALES_QUANTITY}" onchange="fn_calculateSupplyPrice()" />
<input type="number" name="salesQuantity" id="salesQuantity"
value="${saleInfo.SALES_QUANTITY}"
onchange="fn_calculateSupplyPrice()"
placeholder="판매할 수량 입력" />
</td>
<td class="input_title"><label for="shippingDate">출하일</label></td>
<td>
@@ -720,11 +869,15 @@
<tr>
<td class="input_title"><label for="salesUnitPrice">판매단가</label></td>
<td>
<input type="number" name="salesUnitPrice" id="salesUnitPrice" value="${saleInfo.SALES_UNIT_PRICE}" onchange="fn_calculateSupplyPrice()" />
<input type="number" name="salesUnitPrice" id="salesUnitPrice"
value="${saleInfo.SALES_UNIT_PRICE}"
onchange="fn_calculateSupplyPrice()" />
</td>
<td class="input_title"><label for="salesSupplyPrice">판매공급가액</label></td>
<td>
<input type="number" name="salesSupplyPrice" id="salesSupplyPrice" value="${saleInfo.SALES_SUPPLY_PRICE}" readonly style="background-color:#f5f5f5;" />
<input type="number" name="salesSupplyPrice" id="salesSupplyPrice"
value="${saleInfo.SALES_SUPPLY_PRICE}"
readonly style="background-color:#f5f5f5;" />
</td>
</tr>
@@ -732,11 +885,15 @@
<tr>
<td class="input_title"><label for="salesVat">판매부가세</label></td>
<td>
<input type="number" name="salesVat" id="salesVat" value="${saleInfo.SALES_VAT}" readonly style="background-color:#f5f5f5;" />
<input type="number" name="salesVat" id="salesVat"
value="${saleInfo.SALES_VAT}"
readonly style="background-color:#f5f5f5;" />
</td>
<td class="input_title"><label for="salesTotalAmount">판매총액</label></td>
<td>
<input type="number" name="salesTotalAmount" id="salesTotalAmount" value="${saleInfo.SALES_TOTAL_AMOUNT}" readonly style="background-color:#f5f5f5;" />
<input type="number" name="salesTotalAmount" id="salesTotalAmount"
value="${saleInfo.SALES_TOTAL_AMOUNT}"
readonly style="background-color:#f5f5f5;" />
</td>
</tr>
@@ -751,7 +908,9 @@
</td>
<td class="input_title"><label for="salesExchangeRate">판매환율</label></td>
<td>
<input type="number" name="salesExchangeRate" id="salesExchangeRate" step="0.01" value="${saleInfo.SALES_EXCHANGE_RATE}" onchange="fn_recalculateByExchangeRate()" />
<input type="number" name="salesExchangeRate" id="salesExchangeRate" step="0.01"
value="${saleInfo.SALES_EXCHANGE_RATE}"
onchange="fn_recalculateByExchangeRate()" />
</td>
</tr>

View File

@@ -0,0 +1,674 @@
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page import="com.pms.common.utils.*"%>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<%@ page import="java.util.*" %>
<%@include file= "/init.jsp" %>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>거래명세서</title>
<style>
@media print {
.no-print { display: none !important; }
@page { margin: 5mm; }
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: "맑은 고딕", "Malgun Gothic", sans-serif;
font-size: 11pt;
line-height: 1.4;
padding: 10px;
background-color: #f0f0f0;
}
.container {
width: 210mm;
min-height: 297mm;
margin: 0 auto;
border: 2px solid #000;
background-color: white;
box-shadow: 0 0 10px rgba(0,0,0,0.1);
}
/* 제목 */
.header {
text-align: center;
font-size: 24pt;
font-weight: bold;
padding: 15px 0;
border-bottom: 2px solid #000;
letter-spacing: 8px;
}
/* 상단 정보 테이블 */
.info-table {
width: 100%;
border-collapse: collapse;
}
.info-table td {
border: 1px solid #000;
padding: 6px 10px;
font-size: 9pt;
vertical-align: middle;
}
.row-1 td, .row-2 td, .row-3 td, .row-4 td, .row-5 td {
height: 30px;
}
.date-cell {
text-align: left;
width: 30%;
padding: 8px 12px;
}
.date-label {
font-weight: bold;
margin-right: 6px;
font-size: 9pt;
}
.reg-label-cell {
background-color: #e8e8e8;
font-weight: bold;
text-align: center;
width: 15%;
font-size: 9pt;
}
.reg-value-cell {
font-size: 13pt;
font-weight: bold;
text-align: center;
width: 40%;
}
.stamp-cell {
width: 15%;
text-align: center;
padding: 5px;
vertical-align: middle;
}
.stamp-box {
width: 70px;
height: 140px;
border: 1px solid #666;
display: inline-flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: 9pt;
color: #333;
line-height: 1.4;
}
.receiver-cell {
text-align: left;
padding: 8px 12px;
width: 30%;
}
.receiver-name {
font-size: 13pt;
font-weight: bold;
display: inline;
margin-right: 8px;
}
.receiver-suffix {
font-size: 12pt;
font-weight: bold;
display: inline;
}
.supplier-label-cell {
background-color: #e8e8e8;
font-weight: bold;
text-align: center;
width: 30px;
line-height: 1.5;
font-size: 10pt;
padding: 4px 2px;
vertical-align: middle;
}
.supplier-row-1, .supplier-row-2, .supplier-row-3, .supplier-row-4 {
text-align: left;
font-size: 9pt;
padding: 6px 10px;
}
.supplier-inline {
display: block;
}
.supplier-label-inline {
font-weight: bold;
margin-right: 8px;
font-size: 8pt;
}
.supply-text-cell {
text-align: left;
font-size: 9pt;
padding: 8px 12px;
width: 30%;
}
/* 합계 금액 */
.amount-row {
display: flex;
align-items: center;
padding: 8px 15px;
border-bottom: 2px solid #000;
background-color: #f9f9f9;
height: 40px;
}
.amount-label {
font-size: 10pt;
margin-right: 15px;
}
.amount-text {
font-size: 13pt;
font-weight: bold;
margin-right: 8px;
}
.amount-won-symbol {
font-size: 16pt;
font-weight: bold;
margin: 0 10px;
}
.amount-number {
font-size: 15pt;
font-weight: bold;
flex: 1;
text-align: right;
padding-right: 15px;
}
/* 품목 테이블 */
.items-table {
width: 100%;
border-collapse: collapse;
}
.items-table th,
.items-table td {
border: 1px solid #000;
padding: 5px 8px;
font-size: 9pt;
text-align: center;
}
.items-table th {
background-color: #e8e8e8;
font-weight: bold;
height: 32px;
}
.items-table td {
height: 28px;
}
.items-table td[contenteditable="true"] {
background-color: #fffef0;
cursor: text;
}
.text-left {
text-align: left !important;
padding-left: 10px !important;
}
.text-right {
text-align: right !important;
padding-right: 10px !important;
}
.total-row {
background-color: #e8e8e8;
font-weight: bold;
}
/* 비고 */
.note-section {
border-top: 1px solid #000;
padding: 10px 15px;
min-height: 90px;
}
.note-title {
font-weight: bold;
font-size: 9pt;
margin-bottom: 6px;
}
.note-content {
padding: 3px;
min-height: 60px;
font-size: 9pt;
line-height: 1.5;
}
.note-content[contenteditable="true"] {
background-color: #fffef0;
cursor: text;
}
/* 하단 */
.footer-table {
width: 100%;
border-collapse: collapse;
}
.footer-table td {
border: 1px solid #000;
padding: 12px 15px;
width: 50%;
vertical-align: top;
height: 90px;
}
.footer-label {
font-weight: bold;
font-size: 10pt;
margin-bottom: 25px;
}
.footer-sign {
text-align: right;
font-size: 10pt;
}
/* 버튼 */
.button-area {
text-align: center;
margin: 15px 0;
}
.btn {
padding: 10px 30px;
margin: 0 5px;
font-size: 14px;
cursor: pointer;
border: none;
border-radius: 3px;
}
.btn-print {
background-color: #2196F3;
color: white;
}
.btn-close {
background-color: #f44336;
color: white;
}
</style>
<script src="https://code.jquery.com/jquery-2.1.4.min.js"></script>
<script>
$(document).ready(function(){
var today = new Date();
var days = ['일', '월', '화', '수', '목', '금', '토'];
var dateStr = days[today.getDay()] + "요일, " + (today.getMonth() + 1) + "월 " + today.getDate() + ", " + today.getFullYear();
$("#deliveryDate").text(dateStr);
fn_loadData();
});
function fn_loadData() {
var projectNos = "${param.projectNos}";
console.log("=== 거래명세서 데이터 로드 ===");
console.log("프로젝트 번호:", projectNos);
if(!projectNos || projectNos === "") {
alert("프로젝트 번호가 없습니다.");
return;
}
$.ajax({
url: "/salesMgmt/getTransactionStatementData.do",
type: "POST",
data: { projectNos: projectNos },
dataType: "json",
success: function(data) {
console.log("=== 서버 응답 데이터 ===");
console.log("전체 데이터:", JSON.stringify(data, null, 2));
console.log("success:", data.success);
console.log("customerName:", data.customerName);
console.log("items:", data.items);
console.log("supplierName:", data.supplierName);
if(data && data.success) {
fn_fillData(data);
} else {
alert("데이터 조회 실패: " + (data.message || "알 수 없는 오류"));
}
},
error: function(xhr, status, error) {
console.log("=== AJAX 에러 ===");
console.log("status:", status);
console.log("error:", error);
console.log("responseText:", xhr.responseText);
alert("데이터 조회 중 오류 발생");
}
});
}
function fn_fillData(data) {
console.log("=== fn_fillData 호출 ===");
console.log("customerName:", data.customerName);
console.log("items:", data.items);
// 수신자 정보
if(data.customerName) {
$("#receiverName").text(data.customerName);
}
// 공급자 정보
if(data.supplierRegNo) {
$("#registrationNo").text(data.supplierRegNo);
}
if(data.supplierName) {
$("#supplierName").text(data.supplierName);
}
if(data.supplierAddress) {
$("#supplierAddr").text(data.supplierAddress);
}
if(data.supplierBusiness) {
$("#supplierBiz").text(data.supplierBusiness);
}
if(data.supplierType) {
$("#supplierType").text(data.supplierType);
}
if(data.supplierContact) {
$("#supplierTel").text(data.supplierContact);
}
// 공급 텍스트
if(data.note) {
$("#supplyText").text(data.note);
}
var totalSupply = 0;
var totalVat = 0;
var tbody = $("#itemsBody");
tbody.empty();
if(data.items && data.items.length > 0) {
console.log("품목 개수:", data.items.length);
$.each(data.items, function(i, item) {
console.log("품목 " + (i+1) + ":", item);
var supply = parseInt(item.SUPPLYPRICE || item.supplyPrice || 0);
var vat = parseInt(item.VAT || item.vat || 0);
totalSupply += supply;
totalVat += vat;
var row = $("<tr>");
row.append($("<td contenteditable='true' class='text-left'>").text(item.PRODUCTNAME || item.productName || ""));
row.append($("<td contenteditable='true'>").text(item.SPEC || item.spec || ""));
row.append($("<td contenteditable='true'>").text(item.QUANTITY || item.quantity || ""));
row.append($("<td contenteditable='true' class='text-right'>").text(fn_num(item.UNITPRICE || item.unitPrice || 0)));
row.append($("<td contenteditable='true' class='text-right'>").text(fn_num(supply)));
row.append($("<td contenteditable='true' class='text-right'>").text(fn_num(vat)));
tbody.append(row);
});
// 빈 행 추가 (최소 7개 행 유지)
for(var i = data.items.length; i < 7; i++) {
var row = $("<tr>");
for(var j = 0; j < 6; j++) {
row.append($("<td contenteditable='true'>").html("&nbsp;"));
}
tbody.append(row);
}
} else {
console.log("품목 데이터가 없습니다.");
// 빈 행 7개 추가
for(var i = 0; i < 7; i++) {
var row = $("<tr>");
for(var j = 0; j < 6; j++) {
row.append($("<td contenteditable='true'>").html("&nbsp;"));
}
tbody.append(row);
}
}
// 합계 행
var totalRow = $("<tr class='total-row'>");
totalRow.append($("<td colspan='2'>").text("계"));
totalRow.append($("<td>").text(data.items ? data.items.length : 0));
totalRow.append($("<td>").html("&nbsp;"));
totalRow.append($("<td class='text-right'>").text(fn_num(totalSupply)));
totalRow.append($("<td class='text-right'>").text(fn_num(totalVat)));
tbody.append(totalRow);
// 합계 금액
var total = totalSupply + totalVat;
console.log("총 공급가액:", totalSupply);
console.log("총 세액:", totalVat);
console.log("총 금액:", total);
$("#totalText").text(total.toString());
$("#totalNum").text(fn_num(total));
}
function fn_num(n) {
return n.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
function fn_save() {
if(!confirm("수정한 내용을 저장하시겠습니까?")) {
return;
}
// 수정된 데이터 수집
var data = {
projectNos: "${param.projectNos}",
deliveryDate: $("#deliveryDate").text(),
receiverName: $("#receiverName").text(),
registrationNo: $("#registrationNo").text(),
supplierName: $("#supplierName").text(),
supplierAddr: $("#supplierAddr").text(),
supplierBiz: $("#supplierBiz").text(),
supplierType: $("#supplierType").text(),
supplierTel: $("#supplierTel").text(),
supplyText: $("#supplyText").text(),
totalAmount: $("#totalText").text(),
noteContent: $("#noteContent").text(),
items: []
};
// 품목 데이터 수집
$("#itemsBody tr:not(.total-row)").each(function() {
var cells = $(this).find("td");
if(cells.length === 6) {
var item = {
productName: $(cells[0]).text().trim(),
spec: $(cells[1]).text().trim(),
quantity: $(cells[2]).text().trim(),
unitPrice: $(cells[3]).text().trim(),
supplyPrice: $(cells[4]).text().trim(),
vat: $(cells[5]).text().trim()
};
// 빈 행이 아닌 경우만 추가
if(item.productName || item.spec || item.quantity) {
data.items.push(item);
}
}
});
$.ajax({
url: "/salesMgmt/saveTransactionStatement.do",
type: "POST",
data: JSON.stringify(data),
contentType: "application/json",
dataType: "json",
success: function(response) {
if(response && response.success) {
alert("저장되었습니다.");
} else {
alert("저장 실패: " + (response.message || "알 수 없는 오류"));
}
},
error: function() {
alert("저장 중 오류가 발생했습니다.");
}
});
}
function fn_print() {
window.print();
}
function fn_close() {
window.close();
}
</script>
</head>
<body>
<div class="container">
<!-- 제목 -->
<div class="header">거래 명세서</div>
<!-- 상단 정보 테이블 -->
<table class="info-table">
<!-- 첫번째 행: 납품일 (왼쪽만) -->
<tr class="row-1">
<td class="date-cell" rowspan="2">
<span class="date-label">납품일:</span>
<span id="deliveryDate" contenteditable="true">수요일, 9월 24, 2025</span>
</td>
<td class="reg-label-cell">등록 번호</td>
<td class="reg-value-cell">
<span id="registrationNo" contenteditable="true">314-81-75146</span>
</td>
<td class="stamp-cell" rowspan="5">
<div class="stamp-box">
<div>성 명</div>
<div contenteditable="true" style="margin-top: 3px;">이동현</div>
</div>
</td>
</tr>
<!-- 두번째 행 -->
<tr class="row-2">
<td class="supplier-label-cell" rowspan="4">공<br>급<br>자</td>
<td class="supplier-row-1">
<div class="supplier-inline">
<span class="supplier-label-inline">상호(법인명)</span>
<span contenteditable="true" id="supplierName">㈜알피에스</span>
</div>
</td>
</tr>
<!-- 세번째 행: 수신자 + 공급자 -->
<tr class="row-3">
<td class="receiver-cell" rowspan="2">
<span class="receiver-name" contenteditable="true" id="receiverName">㈜OOO</span>
<span class="receiver-suffix">귀하</span>
</td>
<td class="supplier-row-2">
<div class="supplier-inline">
<span class="supplier-label-inline">사업장주소</span>
<span contenteditable="true" id="supplierAddr">대전광역시 유성구 국제과학10로 8</span>
</div>
</td>
</tr>
<!-- 네번째 행 -->
<tr class="row-4">
<td class="supplier-row-3">
<div class="supplier-inline">
<span class="supplier-label-inline">업 태</span>
<span contenteditable="true" id="supplierBiz">제조</span>
<span class="supplier-label-inline" style="margin-left: 15px;">종 목</span>
<span contenteditable="true" id="supplierType">금속절삭가공기계의</span>
</div>
</td>
</tr>
<!-- 다섯번째 행 -->
<tr class="row-5">
<td class="supply-text-cell">
<span contenteditable="true" id="supplyText">아래와 같이 공급합니다.</span>
</td>
<td class="supplier-row-4">
<div class="supplier-inline">
<span class="supplier-label-inline">전화 번호</span>
<span contenteditable="true" id="supplierTel">TEL:042-602-3300/FAX:042-672</span>
</div>
</td>
</tr>
</table>
<!-- 합계 금액 -->
<div class="amount-row">
<span class="amount-label">(공급가액+세액)</span>
<span class="amount-text"><span id="totalText" contenteditable="true">3300000</span> 원정</span>
<span class="amount-won-symbol">₩</span>
<span class="amount-won-symbol">₩</span>
<span class="amount-number"><span id="totalNum" contenteditable="true">3,300,000</span></span>
</div>
<!-- 품목 테이블 -->
<table class="items-table">
<thead>
<tr>
<th style="width: 25%;">품 명</th>
<th style="width: 15%;">규 격</th>
<th style="width: 10%;">수 량</th>
<th style="width: 15%;">단 가</th>
<th style="width: 17.5%;">공 급 가 액</th>
<th style="width: 17.5%;">세 액</th>
</tr>
</thead>
<tbody id="itemsBody">
</tbody>
</table>
<!-- 비고 -->
<div class="note-section">
<div class="note-title">&lt;비고&gt;</div>
<div class="note-content" contenteditable="true" id="noteContent">Spindle S/N : 187-1062, 1096, 1099</div>
</div>
<!-- 하단 -->
<table class="footer-table">
<tr>
<td>
<div class="footer-label">인계확인 :</div>
<div class="footer-sign">(인)</div>
</td>
<td>
<div class="footer-label">인수확인 :</div>
<div class="footer-sign">(인)</div>
</td>
</tr>
</table>
</div>
<!-- 버튼 -->
<div class="button-area no-print">
<button class="btn btn-print" onclick="fn_save()" style="background-color: #4CAF50;">저장</button>
<button class="btn btn-print" onclick="fn_print()">인쇄</button>
<button class="btn btn-close" onclick="fn_close()">닫기</button>
</div>
</body>
</html>

View File

@@ -334,7 +334,9 @@ public class SalesNcollectMgmtController {
if(orderData != null && orderData.get("SALES_SUPPLY_PRICE") != null) {
// 수주 데이터를 saleInfo에 병합 (기존 S/N 등은 유지)
if(saleInfo == null) saleInfo = new HashMap<String, Object>();
saleInfo.put("SALES_QUANTITY", orderData.get("SALES_QUANTITY"));
// SALES_QUANTITY는 나중에 잔량으로 계산하므로 여기서는 설정하지 않음
// saleInfo.put("SALES_QUANTITY", orderData.get("SALES_QUANTITY")); // 제거
saleInfo.put("ORDER_QUANTITY", orderData.get("SALES_QUANTITY")); // 주문수량은 ORDER_QUANTITY로 저장
saleInfo.put("SALES_UNIT_PRICE", orderData.get("SALES_UNIT_PRICE"));
saleInfo.put("SALES_SUPPLY_PRICE", orderData.get("SALES_SUPPLY_PRICE"));
saleInfo.put("SALES_VAT", orderData.get("SALES_VAT"));
@@ -371,11 +373,22 @@ public class SalesNcollectMgmtController {
System.out.println("총 판매 수량 (모든 분할 출하 합계): " + totalSoldQuantity);
System.out.println("잔량 (remainingQuantity): " + remainingQuantity);
// 잔량을 판매수량으로 설정 (정수)
saleInfo.put("SALES_QUANTITY", remainingQuantity > 0 ? remainingQuantity : orderQuantity);
System.out.println("설정 후 SALES_QUANTITY: " + saleInfo.get("SALES_QUANTITY"));
// 잔량을 판매수량으로 설정 (정수)
saleInfo.put("SALES_QUANTITY", remainingQuantity > 0 ? remainingQuantity : orderQuantity);
System.out.println("설정 후 SALES_QUANTITY: " + saleInfo.get("SALES_QUANTITY"));
}
// 프로젝트의 모든 품목 조회 (Phase 2) - 주석처리: 품목은 하나만 존재
/*
List<Map<String, Object>> projectItems = null;
if(paramMap.get("orderNo") != null && !paramMap.get("orderNo").equals("")) {
projectItems = salesNcollectMgmtService.getProjectItems(paramMap);
System.out.println("=== 프로젝트 품목 조회 ===");
System.out.println("품목 개수: " + (projectItems != null ? projectItems.size() : 0));
request.setAttribute("projectItems", projectItems);
}
*/
request.setAttribute("saleInfo", saleInfo);
// orderInfo로 견적 정보 전달 (saleInfo가 이미 모든 필요한 정보를 포함)
@@ -502,6 +515,75 @@ public class SalesNcollectMgmtController {
return "/salesmgmt/salesMgmt/shippingDetailPopup";
}
/**
* 거래명세서 폼 표시
*/
@RequestMapping(value = "/salesMgmt/transactionStatementForm.do", method = RequestMethod.GET)
public String showTransactionStatementForm(HttpServletRequest request, @RequestParam Map<String, Object> paramMap) {
return "/salesmgmt/salesMgmt/transactionStatementForm";
}
/**
* 거래명세서 데이터 조회
*/
@RequestMapping(value = "/salesMgmt/getTransactionStatementData.do", method = RequestMethod.POST)
@ResponseBody
public Map<String, Object> getTransactionStatementData(HttpServletRequest request, @RequestParam Map<String, Object> paramMap) {
Map<String, Object> resultMap = new HashMap<String, Object>();
try {
String projectNos = (String) paramMap.get("projectNos");
if(projectNos == null || projectNos.isEmpty()) {
resultMap.put("success", false);
resultMap.put("message", "프로젝트 번호가 없습니다.");
return resultMap;
}
// 거래명세서 데이터 조회
Map<String, Object> statementData = salesNcollectMgmtService.getTransactionStatementData(paramMap);
resultMap.put("success", true);
resultMap.putAll(statementData);
} catch (Exception e) {
e.printStackTrace();
resultMap.put("success", false);
resultMap.put("message", "데이터 조회 중 오류가 발생했습니다: " + e.getMessage());
}
return resultMap;
}
/**
* 거래명세서 저장 (차수 관리 없음)
*/
@RequestMapping(value = "/salesMgmt/saveTransactionStatement.do", method = RequestMethod.POST)
@ResponseBody
public Map<String, Object> saveTransactionStatement(HttpServletRequest request, @org.springframework.web.bind.annotation.RequestBody Map<String, Object> paramMap) {
Map<String, Object> resultMap = new HashMap<String, Object>();
try {
// 거래명세서는 차수 관리가 불필요하므로 단순히 성공 응답만 반환
// 실제로는 PDF 생성 또는 로그 기록 등의 작업을 수행할 수 있음
System.out.println("=== 거래명세서 저장 ===");
System.out.println("프로젝트 번호: " + paramMap.get("projectNos"));
System.out.println("납품일: " + paramMap.get("deliveryDate"));
System.out.println("비고: " + paramMap.get("noteContent"));
resultMap.put("success", true);
resultMap.put("message", "저장되었습니다.");
} catch (Exception e) {
e.printStackTrace();
resultMap.put("success", false);
resultMap.put("message", "저장 중 오류가 발생했습니다: " + e.getMessage());
}
return resultMap;
}
/**
* <pre>
* 계약관리 목록 조회

View File

@@ -1566,40 +1566,43 @@ ORDER BY T.REGDATE DESC, T.PROJECT_NO DESC
-->
<select id="getOrderDataByOrderNo" parameterType="map" resultType="map">
/* salesNcollectMgmt.getOrderDataByOrderNo - orderNo로 판매등록용 수주 데이터 조회 */
SELECT
-- 기본 정보
CM.CONTRACT_NO AS ORDER_NO,
CM.OBJID AS CONTRACT_OBJID,
-- 수주 금액 정보 (CONTRACT_ITEM 테이블에서 합산) - VARCHAR 타입이므로 NUMERIC으로 캐스팅
COALESCE(SUM(CI.ORDER_QUANTITY::NUMERIC), 0) AS SALES_QUANTITY,
COALESCE(ROUND(AVG(CI.ORDER_UNIT_PRICE::NUMERIC), 2), 0) AS SALES_UNIT_PRICE,
COALESCE(SUM(CI.ORDER_SUPPLY_PRICE::NUMERIC), 0) AS SALES_SUPPLY_PRICE,
COALESCE(SUM(CI.ORDER_VAT::NUMERIC), 0) AS SALES_VAT,
COALESCE(SUM(CI.ORDER_TOTAL_AMOUNT::NUMERIC), 0) AS SALES_TOTAL_AMOUNT,
-- 환종 정보
CM.CONTRACT_CURRENCY AS SALES_CURRENCY,
CODE_NAME(CM.CONTRACT_CURRENCY) AS SALES_CURRENCY_NAME,
COALESCE(CM.EXCHANGE_RATE::NUMERIC, 0) AS SALES_EXCHANGE_RATE,
-- 수주 날짜 - VARCHAR 타입이므로 그대로 사용
CM.ORDER_DATE AS SHIPPING_DATE,
-- 담당자
CM.PM_USER_ID AS MANAGER
FROM PROJECT_MGMT PM
INNER JOIN CONTRACT_MGMT CM ON PM.CONTRACT_OBJID = CM.OBJID
LEFT JOIN CONTRACT_ITEM CI ON CM.OBJID::VARCHAR = CI.CONTRACT_OBJID AND UPPER(CI.STATUS) = 'ACTIVE'
WHERE PM.PROJECT_NO = #{orderNo}
GROUP BY
CM.CONTRACT_NO,
CM.OBJID,
CM.CONTRACT_CURRENCY,
CM.EXCHANGE_RATE,
CM.ORDER_DATE,
CM.PM_USER_ID
SELECT
-- 기본 정보
CM.CONTRACT_NO AS ORDER_NO,
CM.OBJID AS CONTRACT_OBJID,
-- 수주 수량 정보 (PROJECT_MGMT에서 직접 가져오기)
COALESCE(PM.QUANTITY::NUMERIC, 0) AS SALES_QUANTITY,
-- 수주 금액 정보 (CONTRACT_ITEM 테이블에서 합산) - VARCHAR 타입이므로 NULLIF로 빈 문자열 제거 후 NUMERIC 캐스팅
COALESCE(ROUND(AVG(NULLIF(CI.ORDER_UNIT_PRICE, '')::NUMERIC), 2), 0) AS SALES_UNIT_PRICE,
COALESCE(SUM(NULLIF(CI.ORDER_SUPPLY_PRICE, '')::NUMERIC), 0) AS SALES_SUPPLY_PRICE,
COALESCE(SUM(NULLIF(CI.ORDER_VAT, '')::NUMERIC), 0) AS SALES_VAT,
COALESCE(SUM(NULLIF(CI.ORDER_TOTAL_AMOUNT, '')::NUMERIC), 0) AS SALES_TOTAL_AMOUNT,
-- 환종 정보
CM.CONTRACT_CURRENCY AS SALES_CURRENCY,
CODE_NAME(CM.CONTRACT_CURRENCY) AS SALES_CURRENCY_NAME,
COALESCE(NULLIF(CM.EXCHANGE_RATE, '')::NUMERIC, 0) AS SALES_EXCHANGE_RATE,
-- 수주 날짜 - VARCHAR 타입이므로 그대로 사용
CM.ORDER_DATE AS SHIPPING_DATE,
-- 담당자
CM.PM_USER_ID AS MANAGER
FROM PROJECT_MGMT PM
INNER JOIN CONTRACT_MGMT CM ON PM.CONTRACT_OBJID = CM.OBJID
LEFT JOIN CONTRACT_ITEM CI ON CM.OBJID::VARCHAR = CI.CONTRACT_OBJID AND UPPER(CI.STATUS) = 'ACTIVE'
WHERE PM.PROJECT_NO = #{orderNo}
GROUP BY
CM.CONTRACT_NO,
CM.OBJID,
CM.CONTRACT_CURRENCY,
CM.EXCHANGE_RATE,
CM.ORDER_DATE,
CM.PM_USER_ID,
PM.QUANTITY
</select>
<!--
@@ -1743,5 +1746,35 @@ ORDER BY T.REGDATE DESC, T.PROJECT_NO DESC
ORDER BY shipping_date DESC, project_no
</select>
<!-- 거래명세서 - 고객 정보 조회 -->
<select id="getCustomerInfoByProjectNo" parameterType="map" resultType="map">
/* salesNcollectMgmt.getCustomerInfoByProjectNo - 프로젝트 번호로 고객 정보 조회 */
SELECT
SM.SUPPLY_NAME AS CUSTOMER_NAME,
SM.BUSINESS_NO AS CUSTOMER_REG_NO,
SM.ADDRESS AS CUSTOMER_ADDRESS,
SM.BUSINESS_TYPE AS CUSTOMER_BUSINESS,
SM.BUSINESS_ITEM AS CUSTOMER_TYPE,
SM.TEL_NO AS CUSTOMER_CONTACT
FROM PROJECT_MGMT PM
INNER JOIN SUPPLY_MNG SM ON PM.CUSTOMER_OBJID::NUMERIC = SM.OBJID
WHERE PM.PROJECT_NO = #{projectNo}
</select>
<!-- 거래명세서 - 품목 정보 조회 -->
<select id="getTransactionStatementItem" parameterType="map" resultType="map">
/* salesNcollectMgmt.getTransactionStatementItem - 프로젝트 번호로 품목 정보 조회 */
SELECT
PM.PART_NAME AS productName,
PM.PART_NO AS spec,
COALESCE(SR.sales_quantity, PM.QUANTITY) AS quantity,
COALESCE(SR.sales_unit_price, 0) AS unitPrice,
COALESCE(SR.sales_supply_price, 0) AS supplyPrice,
COALESCE(SR.sales_vat, 0) AS vat
FROM PROJECT_MGMT PM
LEFT JOIN sales_registration SR ON PM.PROJECT_NO = SR.project_no
WHERE PM.PROJECT_NO = #{projectNo}
</select>
</mapper>

View File

@@ -308,10 +308,39 @@ public class SalesNcollectMgmtService {
}
}
return CommonUtils.toUpperCaseMapKey(resultMap);
return CommonUtils.toUpperCaseMapKey(resultMap);
}
/**
* 프로젝트의 모든 품목 조회 - 주석처리: 품목은 하나만 존재
*/
/*
public List<Map<String, Object>> getProjectItems(Map<String, Object> paramMap) {
List<Map<String, Object>> resultList = new ArrayList<Map<String, Object>>();
SqlSession sqlSession = null;
try {
sqlSession = SqlMapConfig.getInstance().getSqlSession();
resultList = sqlSession.selectList("salesNcollectMgmt.getProjectItems", paramMap);
} catch(Exception e) {
e.printStackTrace();
} finally {
if(sqlSession != null) {
sqlSession.close();
}
}
public Map<String, Object> saveSaleRegistration(HttpServletRequest request, Map<String, Object> paramMap) {
// 대문자 키로 변환
List<Map<String, Object>> upperCaseList = new ArrayList<Map<String, Object>>();
for(Map<String, Object> item : resultList) {
upperCaseList.add(CommonUtils.toUpperCaseMapKey(item));
}
return upperCaseList;
}
*/
public Map<String, Object> saveSaleRegistration(HttpServletRequest request, Map<String, Object> paramMap) {
Map<String, Object> resultMap = new HashMap<String, Object>();
SqlSession sqlSession = null;
@@ -321,18 +350,87 @@ public class SalesNcollectMgmtService {
PersonBean person = (PersonBean) request.getSession().getAttribute(Constants.PERSON_BEAN);
paramMap.put("cretEmpNo", person.getUserId());
// sales_registration 테이블에 판매 데이터 저장 (ON CONFLICT로 자동 UPDATE)
String projectNo = (String) paramMap.get("orderNo");
System.out.println("=== saveSaleRegistration 시작 ===");
System.out.println("projectNo: " + projectNo);
System.out.println("salesQuantity: " + paramMap.get("salesQuantity"));
// 기존 판매 데이터가 있는지 확인
Map<String, Object> checkParam = new HashMap<String, Object>();
checkParam.put("orderNo", projectNo);
Map<String, Object> existingSale = sqlSession.selectOne("salesNcollectMgmt.getSaleInfo", checkParam);
System.out.println("existingSale: " + (existingSale != null ? "있음" : "없음"));
// SALE_NO가 null이면 첫 판매, 있으면 분할 출하
Object saleNoObj = existingSale != null ? existingSale.get("SALE_NO") : null;
if(saleNoObj == null) saleNoObj = existingSale != null ? existingSale.get("sale_no") : null;
System.out.println("SALE_NO 확인: " + saleNoObj);
if(saleNoObj == null) {
// 첫 판매 등록: sales_registration에 INSERT
System.out.println("첫 판매 등록 - sales_registration에 INSERT");
sqlSession.insert("salesNcollectMgmt.insertSaleRegistration", paramMap);
} else {
// 분할 출하: shipment_log에 INSERT
System.out.println("분할 출하 - shipment_log에 INSERT");
// existingSale은 이미 대문자 키로 변환되어 있음 (CommonUtils.toUpperCaseMapKey)
// 하지만 getSaleInfo에서는 변환 안 함 - 직접 확인
Object orderQtyObj = existingSale.get("ORDER_QUANTITY");
if(orderQtyObj == null) orderQtyObj = existingSale.get("order_quantity");
// saleNoObj는 이미 위에서 선언됨 - 재사용
// Object saleNoObj = existingSale.get("SALE_NO"); // 중복 제거
// if(saleNoObj == null) saleNoObj = existingSale.get("sale_no"); // 이미 위에서 처리
System.out.println("ORDER_QUANTITY: " + orderQtyObj);
System.out.println("SALE_NO: " + saleNoObj);
if(orderQtyObj == null || saleNoObj == null) {
System.out.println("=== existingSale 전체 내용 ===");
for(Object key : existingSale.keySet()) {
System.out.println(key + ": " + existingSale.get(key));
}
throw new RuntimeException("ORDER_QUANTITY 또는 SALE_NO가 null입니다");
}
paramMap.put("targetObjid", projectNo);
paramMap.put("originalQuantity", orderQtyObj);
// 잔량 계산
int orderQuantity = Integer.parseInt(String.valueOf(orderQtyObj).split("\\.")[0]);
int salesQuantity = Integer.parseInt(String.valueOf(paramMap.get("salesQuantity")));
int remainingQuantity = orderQuantity - salesQuantity;
System.out.println("orderQuantity: " + orderQuantity);
System.out.println("salesQuantity: " + salesQuantity);
System.out.println("remainingQuantity: " + remainingQuantity);
paramMap.put("remainingQuantity", remainingQuantity);
paramMap.put("parentSaleNo", saleNoObj);
System.out.println("shipment_log INSERT 직전 파라미터:");
System.out.println(" targetObjid: " + paramMap.get("targetObjid"));
System.out.println(" parentSaleNo: " + paramMap.get("parentSaleNo"));
System.out.println(" cretEmpNo: " + paramMap.get("cretEmpNo"));
sqlSession.insert("salesNcollectMgmt.insertShipmentLog", paramMap);
}
sqlSession.commit();
resultMap.put("result", true);
resultMap.put("msg", "저장되었습니다.");
System.out.println("=== 저장 성공 ===");
} catch(Exception e) {
if(sqlSession != null) {
sqlSession.rollback();
}
resultMap.put("result", false);
resultMap.put("msg", "저장 중 오류가 발생했습니다.");
System.out.println("=== 저장 실패 ===");
System.out.println("에러 메시지: " + e.getMessage());
e.printStackTrace();
} finally {
if(sqlSession != null) {
@@ -919,6 +1017,29 @@ public class SalesNcollectMgmtService {
return resultMap;
}
/**
* 모든 분할 출하의 총 판매 수량 조회
* @param paramMap
* @return Map
*/
public Map<String, Object> getTotalSalesQuantity(Map<String, Object> paramMap) {
SqlSession sqlSession = null;
Map<String, Object> result = null;
try {
sqlSession = SqlMapConfig.getInstance().getSqlSession();
result = sqlSession.selectOne("salesNcollectMgmt.getTotalSalesQuantity", paramMap);
} catch(Exception e) {
e.printStackTrace();
} finally {
if(sqlSession != null) {
sqlSession.close();
}
}
return result;
}
/**
* 출하일 상세 내역 조회
* @param projectNo
@@ -941,4 +1062,87 @@ public class SalesNcollectMgmtService {
}
return resultList;
}
/**
* 거래명세서 데이터 조회
* @param paramMap - projectNos (쉼표로 구분된 프로젝트 번호들)
* @return Map
*/
public Map<String, Object> getTransactionStatementData(Map<String, Object> paramMap) {
SqlSession sqlSession = null;
Map<String, Object> resultMap = new HashMap<String, Object>();
try {
sqlSession = SqlMapConfig.getInstance().getSqlSession();
String projectNos = (String) paramMap.get("projectNos");
System.out.println("=== getTransactionStatementData 시작 ===");
System.out.println("projectNos: " + projectNos);
String[] projectNoArray = projectNos.split(",");
// 첫 번째 프로젝트의 고객 정보 조회
Map<String, Object> firstProjectParam = new HashMap<String, Object>();
firstProjectParam.put("projectNo", projectNoArray[0].trim());
System.out.println("고객 정보 조회 - projectNo: " + projectNoArray[0].trim());
Map<String, Object> customerInfo = sqlSession.selectOne("salesNcollectMgmt.getCustomerInfoByProjectNo", firstProjectParam);
System.out.println("고객 정보 조회 결과: " + customerInfo);
if(customerInfo != null) {
resultMap.put("customerName", customerInfo.get("CUSTOMER_NAME"));
resultMap.put("customerRegNo", customerInfo.get("CUSTOMER_REG_NO"));
resultMap.put("customerAddress", customerInfo.get("CUSTOMER_ADDRESS"));
resultMap.put("customerBusiness", customerInfo.get("CUSTOMER_BUSINESS"));
resultMap.put("customerType", customerInfo.get("CUSTOMER_TYPE"));
resultMap.put("customerContact", customerInfo.get("CUSTOMER_CONTACT"));
System.out.println("고객명: " + customerInfo.get("CUSTOMER_NAME"));
} else {
System.out.println("고객 정보가 null입니다!");
}
// 공급자 정보 (회사 정보)
resultMap.put("supplierName", "㈜압피에스");
resultMap.put("supplierRegNo", "314-81-75146");
resultMap.put("supplierAddress", "대전광역시 유성구 국제과학10로 8");
resultMap.put("supplierBusiness", "제조");
resultMap.put("supplierType", "금속절삭가공기계의");
resultMap.put("supplierContact", "TEL:042-602-3300/FAX:042-672");
// 품목 정보 조회
List<Map<String, Object>> items = new ArrayList<Map<String, Object>>();
for(String projectNo : projectNoArray) {
Map<String, Object> itemParam = new HashMap<String, Object>();
itemParam.put("projectNo", projectNo.trim());
System.out.println("품목 정보 조회 - projectNo: " + projectNo.trim());
Map<String, Object> item = sqlSession.selectOne("salesNcollectMgmt.getTransactionStatementItem", itemParam);
System.out.println("품목 정보 조회 결과: " + item);
if(item != null) {
items.add(item);
}
}
resultMap.put("items", items);
System.out.println("총 품목 개수: " + items.size());
// 비고
resultMap.put("note", "아래와 같이 공급합니다.");
System.out.println("=== resultMap 최종 ===");
System.out.println("customerName: " + resultMap.get("customerName"));
System.out.println("items size: " + ((List)resultMap.get("items")).size());
System.out.println("supplierName: " + resultMap.get("supplierName"));
} catch (Exception e) {
System.out.println("=== 에러 발생 ===");
e.printStackTrace();
} finally {
if (sqlSession != null) {
sqlSession.close();
}
}
return resultMap;
}
}