견적요청서 메일 발송기능 추가 (견적요청서 pdf 변환 첨부 기능은 개발전. 아직 양식 못받음)

This commit is contained in:
2026-01-19 14:13:43 +09:00
parent d6a75db657
commit 8d14504861
5 changed files with 607 additions and 13 deletions

View File

@@ -124,14 +124,10 @@ var columns = [
}
},
{headerHozAlign:'center', hozAlign:'center', widthGrow:0.6, title:'수신견적서', field:'ATTACH_FILE_CNT',
formatter: function(cell, formatterParams, onRendered){
var cnt = cell.getValue() || 0;
var iconClass = cnt > 0 ? 'file_icon' : 'file_empty_icon';
return '<a href="#" class="File ' + iconClass + '" style="width:20px; height:20px; display:inline-block;"></a>';
},
formatter: fnc_subInfoValueFormatter,
cellClick: function(e, cell){
var objId = fnc_checkNull(cell.getData().OBJID);
fn_openAttachFile(objId);
fn_openAttachFilePopUp(objId);
}
}
];
@@ -151,12 +147,44 @@ function fn_formPopUp(objId){
}
// 수신견적서 첨부파일 팝업
function fn_openAttachFile(objId){
function fn_openAttachFilePopUp(objId) {
var popup_width = 800;
var popup_height = 600;
var url = "/common/attachFilePopup.do?TARGET_OBJID=" + objId + "&DOC_TYPE=QUOTATION_RECEIVED&TITLE=수신견적서";
var popup_height = 300;
var params = "?targetObjId=" + objId + "&docType=QUOTATION_RECEIVED&docTypeName=수신견적서";
var url = "/common/FileRegistPopup.do" + params;
var popup = window.open(url, "attachFilePopUp", "width=" + popup_width + ",height=" + popup_height + ",scrollbars=yes,resizable=yes");
window.open(url, "attachFilePopup", "width="+popup_width+",height="+popup_height+",menubar=no,scrollbars=yes,resizable=yes");
// 팝업 닫힘 감지하여 해당 행의 파일 카운트 업데이트
fn_watchPopupClose(popup, objId, 'ATTACH_FILE_CNT', 'QUOTATION_RECEIVED');
}
// 팝업 닫힘 감지 및 파일 카운트 업데이트
function fn_watchPopupClose(popup, objId, fieldName, docType) {
var checkPopup = setInterval(function() {
if(popup.closed) {
clearInterval(checkPopup);
fn_updateFileCnt(objId, fieldName, docType);
}
}, 500);
}
// 파일 카운트 업데이트
function fn_updateFileCnt(objId, fieldName, docType) {
$.ajax({
url: "/common/getFileList.do",
type: "POST",
data: { targetObjId: objId, docType: docType },
dataType: "json",
success: function(data) {
var cnt = data ? data.length : 0;
var rows = _tabulGrid.getRows();
rows.forEach(function(row) {
if(row.getData().OBJID == objId) {
row.update({ [fieldName]: cnt });
}
});
}
});
}
// 삭제
@@ -256,9 +284,9 @@ function fn_sendMail(){
// 메일 발송 팝업
function fn_openMailFormPopup(quotationRequestObjId){
var popup_width = 900;
var popup_height = 750;
var url = "/salesMng/quotationRequestMailFormPopup.do?QUOTATION_REQUEST_MASTER_OBJID=" + quotationRequestObjId;
var popup_width = 850;
var popup_height = 650;
var url = "/salesMng/quotationRequestMailPopup.do?QUOTATION_REQUEST_MASTER_OBJID=" + quotationRequestObjId;
window.open(url, "quotationRequestMailForm", "width="+popup_width+",height="+popup_height+",menubar=no,scrollbars=yes,resizable=yes");
}

View File

@@ -0,0 +1,411 @@
<%@ 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" %>
<%
PersonBean person = (PersonBean)session.getAttribute(Constants.PERSON_BEAN);
String connector = person.getUserId();
String quotationRequestMasterObjid = request.getParameter("QUOTATION_REQUEST_MASTER_OBJID");
%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>견적요청서 메일 발송</title>
<style>
body {
margin: 10px;
background-color: #f5f5f5;
font-size: 13px;
}
.mail-form-container {
background: white;
padding: 15px 20px;
border-radius: 6px;
box-shadow: 0 1px 5px rgba(0,0,0,0.1);
max-width: 800px;
margin: 0 auto;
}
.form-title {
font-size: 18px;
font-weight: bold;
margin-bottom: 15px;
color: #333;
border-bottom: 2px solid #3085d6;
padding-bottom: 8px;
}
.form-group {
margin-bottom: 12px;
}
.form-group label {
display: block;
font-weight: bold;
margin-bottom: 5px;
color: #555;
font-size: 13px;
}
.form-group label.required:after {
content: " *";
color: red;
}
.form-group input[type="text"],
.form-group input[type="email"],
.form-group textarea {
width: 100%;
padding: 6px 8px;
border: 1px solid #ddd;
border-radius: 3px;
font-size: 13px;
box-sizing: border-box;
}
.form-group textarea {
min-height: 150px;
resize: vertical;
}
.button-group {
text-align: center;
margin-top: 15px;
padding-top: 15px;
border-top: 1px solid #eee;
}
.btn {
padding: 8px 20px;
margin: 0 4px;
border: none;
border-radius: 3px;
font-size: 13px;
cursor: pointer;
transition: all 0.3s;
}
.btn-primary {
background-color: #3085d6;
color: white;
}
.btn-primary:hover {
background-color: #2874c5;
}
.btn-secondary {
background-color: #6c757d;
color: white;
}
.btn-secondary:hover {
background-color: #5a6268;
}
.info-text {
font-size: 11px;
color: #666;
margin-top: 3px;
}
.order-info {
background-color: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: 3px;
padding: 10px;
margin-bottom: 12px;
}
.order-info-row {
display: flex;
margin-bottom: 5px;
}
.order-info-row:last-child {
margin-bottom: 0;
}
.order-info-label {
width: 100px;
font-weight: bold;
color: #555;
}
.order-info-value {
flex: 1;
color: #333;
}
.vendor-type-badge {
display: inline-block;
padding: 2px 8px;
border-radius: 3px;
font-size: 11px;
margin-left: 5px;
}
.vendor-supply {
background-color: #e3f2fd;
color: #1976d2;
}
.vendor-processing {
background-color: #e8f5e9;
color: #388e3c;
}
</style>
</head>
<body>
<div class="mail-form-container">
<div class="form-title">견적요청서 메일 발송</div>
<!-- 견적요청서 정보 표시 -->
<div class="order-info" id="orderInfoArea">
<div class="order-info-row">
<span class="order-info-label">견적번호:</span>
<span class="order-info-value" id="displayQuotationNo">-</span>
</div>
<div class="order-info-row">
<span class="order-info-label">업체명:</span>
<span class="order-info-value" id="displayVendorName">-</span>
</div>
<div class="order-info-row">
<span class="order-info-label">프로젝트:</span>
<span class="order-info-value" id="displayProjectNo">-</span>
</div>
</div>
<form id="mailForm">
<input type="hidden" id="quotationRequestMasterObjid" name="quotationRequestMasterObjid" value="<%=quotationRequestMasterObjid%>"/>
<!-- 수신인 이메일 -->
<div class="form-group">
<label for="toEmails" class="required">수신인 (To)</label>
<input type="text" id="toEmails" name="toEmails" placeholder="이메일 주소를 입력하세요 (여러 개는 쉼표로 구분)"/>
<div class="info-text">예: email1@example.com, email2@example.com</div>
</div>
<!-- 참조 이메일 -->
<div class="form-group">
<label for="ccEmails">참조 (CC)</label>
<input type="text" id="ccEmails" name="ccEmails" placeholder="참조 이메일 주소 (선택사항)"/>
</div>
<!-- 메일 제목 -->
<div class="form-group">
<label for="subject" class="required">제목</label>
<input type="text" id="subject" name="subject" placeholder="메일 제목을 입력하세요"/>
</div>
<!-- 메일 내용 -->
<div class="form-group">
<label for="contents" class="required">내용</label>
<textarea id="contents" name="contents" placeholder="메일 내용을 입력하세요"></textarea>
</div>
<!-- 버튼 -->
<div class="button-group">
<button type="button" class="btn btn-primary" onclick="fn_sendMail()">발송</button>
<button type="button" class="btn btn-secondary" onclick="window.close()">취소</button>
</div>
</form>
</div>
<script>
var quotationRequestInfo = null;
$(document).ready(function(){
// 견적요청서 정보 로드
fn_loadQuotationRequestInfo();
});
// 견적요청서 정보 로드
function fn_loadQuotationRequestInfo(){
var objId = $("#quotationRequestMasterObjid").val();
$.ajax({
url: "/salesMng/getQuotationRequestInfoForMail.do",
type: "POST",
data: { QUOTATION_REQUEST_MASTER_OBJID: objId },
dataType: "json",
success: function(data){
if(data.resultFlag === "S" && data.info){
quotationRequestInfo = data.info;
// 견적요청서 정보 표시
$("#displayQuotationNo").text(fnc_checkNull(quotationRequestInfo.QUOTATION_REQUEST_NO) || '-');
var vendorName = fnc_checkNull(quotationRequestInfo.VENDOR_NAME) || '-';
var vendorType = fnc_checkNull(quotationRequestInfo.VENDOR_TYPE);
var vendorBadge = '';
if(vendorType === 'SUPPLY') {
vendorBadge = '<span class="vendor-type-badge vendor-supply">공급업체</span>';
} else if(vendorType === 'PROCESSING') {
vendorBadge = '<span class="vendor-type-badge vendor-processing">가공업체</span>';
}
$("#displayVendorName").html(vendorName + vendorBadge);
$("#displayProjectNo").text(fnc_checkNull(quotationRequestInfo.PROJECT_NO) || '-');
// 수신인에 업체 이메일 자동 입력
var vendorEmail = fnc_checkNull(quotationRequestInfo.VENDOR_EMAIL);
if(vendorEmail !== ""){
$("#toEmails").val(vendorEmail);
}
// 메일 제목 자동 생성
var quotationNo = fnc_checkNull(quotationRequestInfo.QUOTATION_REQUEST_NO);
$("#subject").val("[견적요청] " + quotationNo);
// 메일 내용 템플릿 생성
fn_generateMailTemplate();
} else {
Swal.fire({
title: '오류',
text: '견적요청서 정보를 불러올 수 없습니다.',
icon: 'error'
}).then(() => {
window.close();
});
}
},
error: function(){
Swal.fire({
title: '오류',
text: '견적요청서 정보 조회 중 오류가 발생했습니다.',
icon: 'error'
}).then(() => {
window.close();
});
}
});
}
// 메일 내용 템플릿 생성
function fn_generateMailTemplate(){
var vendorName = fnc_checkNull(quotationRequestInfo.VENDOR_NAME);
var quotationNo = fnc_checkNull(quotationRequestInfo.QUOTATION_REQUEST_NO);
var projectNo = fnc_checkNull(quotationRequestInfo.PROJECT_NO);
var vendorType = fnc_checkNull(quotationRequestInfo.VENDOR_TYPE);
var template = "안녕하세요.\n\n";
template += vendorName + " 귀하\n\n";
template += "아래와 같이 견적을 요청드립니다.\n\n";
template += "견적요청번호: " + quotationNo + "\n";
if(projectNo !== ""){
template += "프로젝트번호: " + projectNo + "\n";
}
template += "\n";
// 품목 정보 추가
if(quotationRequestInfo.ITEMS && quotationRequestInfo.ITEMS.length > 0) {
template += "■ 요청 품목\n";
template += "─────────────────────────────\n";
for(var i = 0; i < quotationRequestInfo.ITEMS.length; i++) {
var item = quotationRequestInfo.ITEMS[i];
var partNo = fnc_checkNull(item.PART_NO);
var partName = fnc_checkNull(item.PART_NAME);
var qty = fnc_checkNull(item.QTY);
template += (i + 1) + ". " + partNo + " / " + partName + " / 수량: " + qty + "\n";
}
template += "─────────────────────────────\n\n";
}
template += "견적서 회신 부탁드립니다.\n\n";
template += "감사합니다.\n";
$("#contents").val(template);
}
// 메일 발송
function fn_sendMail(){
// 입력값 검증
var toEmails = $("#toEmails").val().trim();
var subject = $("#subject").val().trim();
var contents = $("#contents").val().trim();
if(toEmails === ""){
Swal.fire("수신인을 입력해주세요.");
$("#toEmails").focus();
return;
}
// 이메일 형식 검증
var emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
var emails = toEmails.split(",").map(function(e){ return e.trim(); });
for(var i = 0; i < emails.length; i++){
if(!emailPattern.test(emails[i])){
Swal.fire("올바른 이메일 형식이 아닙니다: " + emails[i]);
$("#toEmails").focus();
return;
}
}
if(subject === ""){
Swal.fire("제목을 입력해주세요.");
$("#subject").focus();
return;
}
if(contents === ""){
Swal.fire("내용을 입력해주세요.");
$("#contents").focus();
return;
}
// 발송 확인
Swal.fire({
title: '메일 발송',
text: "견적요청서를 발송하시겠습니까?",
icon: 'question',
showCancelButton: true,
confirmButtonText: '발송',
cancelButtonText: '취소'
}).then((result) => {
if(result.isConfirmed){
fn_submitMailForm();
}
});
}
// 메일 발송 요청
function fn_submitMailForm(){
Swal.fire({
title: '메일 발송 중...',
allowOutsideClick: false,
onOpen: () => {
Swal.showLoading();
}
});
var formData = {
QUOTATION_REQUEST_MASTER_OBJID: $("#quotationRequestMasterObjid").val(),
toEmails: $("#toEmails").val(),
ccEmails: $("#ccEmails").val(),
subject: $("#subject").val(),
contents: $("#contents").val()
};
$.ajax({
url: "/salesMng/sendQuotationRequestMail.do",
type: "POST",
data: formData,
dataType: "json",
timeout: 60000,
success: function(data){
Swal.close();
if(data.resultFlag === "S"){
Swal.fire({
title: '발송 완료',
text: '견적요청서가 성공적으로 발송되었습니다.',
icon: 'success'
}).then(() => {
// 부모 창 새로고침
if(window.opener && typeof window.opener.fn_search === 'function'){
window.opener.fn_search();
}
window.close();
});
} else {
Swal.fire({
title: '발송 실패',
text: data.message || '메일 발송 중 오류가 발생했습니다.',
icon: 'error'
});
}
},
error: function(){
Swal.close();
Swal.fire({
title: '오류',
text: '메일 발송 중 시스템 오류가 발생했습니다.',
icon: 'error'
});
}
});
}
</script>
</body>
</html>

View File

@@ -4685,4 +4685,12 @@ ORDER BY V.PATH2
DELETE FROM QUOTATION_REQUEST_DETAIL WHERE QUOTATION_REQUEST_MASTER_OBJID = #{QUOTATION_REQUEST_MASTER_OBJID}::NUMERIC
</delete>
<!-- 견적요청서 메일 발송 상태 업데이트 -->
<update id="updateQuotationRequestMailSent" parameterType="map">
UPDATE QUOTATION_REQUEST_MASTER
SET MAIL_SEND_YN = 'Y',
MAIL_SEND_DATE = NOW()
WHERE OBJID = #{QUOTATION_REQUEST_MASTER_OBJID}::NUMERIC
</update>
</mapper>

View File

@@ -20,6 +20,7 @@ import javax.servlet.http.HttpSession;
import org.apache.ibatis.session.SqlSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
@@ -1756,4 +1757,30 @@ public class SalesMngController {
return resultMap;
}
/**
* 견적요청서 메일 발송 팝업
*/
@RequestMapping("/salesMng/quotationRequestMailPopup.do")
public String quotationRequestMailPopup(HttpServletRequest request, Model model){
return "/salesMng/quotationRequestMailPopup";
}
/**
* 견적요청서 정보 조회 (메일 발송용)
*/
@ResponseBody
@RequestMapping("/salesMng/getQuotationRequestInfoForMail.do")
public Map getQuotationRequestInfoForMail(HttpServletRequest request, @RequestParam Map<String, Object> paramMap){
return salesMngService.getQuotationRequestInfoForMail(request, paramMap);
}
/**
* 견적요청서 메일 발송
*/
@ResponseBody
@RequestMapping("/salesMng/sendQuotationRequestMail.do")
public Map sendQuotationRequestMail(HttpServletRequest request, @RequestParam Map<String, Object> paramMap){
return salesMngService.sendQuotationRequestMail(request, paramMap);
}
}

View File

@@ -37,6 +37,7 @@ import com.pms.common.SqlMapConfig;
import com.pms.common.bean.PersonBean;
import com.pms.common.utils.CommonUtils;
import com.pms.common.utils.Constants;
import com.pms.common.utils.MailUtil;
import com.pms.service.CommonService;
/**
@@ -2581,4 +2582,123 @@ public class SalesMngService {
}
return resultList;
}
/**
* 견적요청서 정보 조회 (메일 발송용)
*/
public Map getQuotationRequestInfoForMail(HttpServletRequest request, Map paramMap) {
SqlSession sqlSession = null;
Map resultMap = new HashMap();
try {
sqlSession = SqlMapConfig.getInstance().getSqlSession();
// 견적요청서 마스터 정보 조회
Map masterInfo = (Map)sqlSession.selectOne("salesMng.getQuotationRequestMasterInfo", paramMap);
if(masterInfo != null) {
// 상세 품목 목록 조회
List<Map> items = sqlSession.selectList("salesMng.getQuotationRequestDetailList", paramMap);
masterInfo.put("ITEMS", items);
resultMap.put("info", masterInfo);
resultMap.put("resultFlag", "S");
} else {
resultMap.put("resultFlag", "F");
resultMap.put("message", "견적요청서 정보를 찾을 수 없습니다.");
}
} catch(Exception e) {
e.printStackTrace();
resultMap.put("resultFlag", "F");
resultMap.put("message", "조회 중 오류가 발생했습니다.");
} finally {
if(sqlSession != null) sqlSession.close();
}
return resultMap;
}
/**
* 견적요청서 메일 발송
*/
public Map sendQuotationRequestMail(HttpServletRequest request, Map paramMap) {
SqlSession sqlSession = null;
Map resultMap = new HashMap();
try {
sqlSession = SqlMapConfig.getInstance().getSqlSession(false);
String quotationMasterObjid = CommonUtils.checkNull(paramMap.get("QUOTATION_REQUEST_MASTER_OBJID"));
String toEmails = CommonUtils.checkNull(paramMap.get("toEmails"));
String ccEmails = CommonUtils.checkNull(paramMap.get("ccEmails"));
String subject = CommonUtils.checkNull(paramMap.get("subject"));
String contents = CommonUtils.checkNull(paramMap.get("contents"));
// 수신인 이메일 파싱
ArrayList<String> toEmailList = new ArrayList<String>();
if(!toEmails.isEmpty()) {
String[] emails = toEmails.split(",");
for(String email : emails) {
email = email.trim();
if(!email.isEmpty()) {
toEmailList.add(email);
}
}
}
// 참조 이메일 파싱
ArrayList<String> ccEmailList = null;
if(!ccEmails.isEmpty()) {
ccEmailList = new ArrayList<String>();
String[] emails = ccEmails.split(",");
for(String email : emails) {
email = email.trim();
if(!email.isEmpty()) {
ccEmailList.add(email);
}
}
}
if(toEmailList.isEmpty()) {
resultMap.put("resultFlag", "F");
resultMap.put("message", "수신인 이메일이 없습니다.");
return resultMap;
}
// 줄바꿈을 HTML <br> 태그로 변환
String htmlContents = contents.replace("\n", "<br>");
// 메일 발송 (간단한 방식)
boolean sendResult = false;
for(String toEmail : toEmailList) {
sendResult = MailUtil.sendMailNew(toEmail, subject, htmlContents);
if(!sendResult) break;
}
if(sendResult) {
// 메일 발송 상태 업데이트
Map updateParam = new HashMap();
updateParam.put("QUOTATION_REQUEST_MASTER_OBJID", quotationMasterObjid);
updateParam.put("MAIL_SEND_YN", "Y");
sqlSession.update("salesMng.updateQuotationRequestMailSent", updateParam);
sqlSession.commit();
resultMap.put("resultFlag", "S");
resultMap.put("message", "메일이 발송되었습니다.");
} else {
resultMap.put("resultFlag", "F");
resultMap.put("message", "메일 발송에 실패했습니다.");
}
} catch(Exception e) {
if(sqlSession != null) sqlSession.rollback();
e.printStackTrace();
resultMap.put("resultFlag", "F");
resultMap.put("message", "메일 발송 중 오류가 발생했습니다: " + e.getMessage());
} finally {
if(sqlSession != null) sqlSession.close();
}
return resultMap;
}
}