diff --git a/db/ddl.sql b/db/ddl.sql new file mode 100644 index 0000000..5209a2e --- /dev/null +++ b/db/ddl.sql @@ -0,0 +1,75 @@ +create table ENS_SND_DTL_KKO_TALK +( + SEND_DETAIL_ID NUMBER(19) not null + primary key, + TITLE VARCHAR2(40), + LINK VARCHAR2(500), + HASH VARCHAR2(99), + GUIDE VARCHAR2(2000), + PAYLOAD VARCHAR2(500), + READ_EXPIRES_AT VARCHAR2(20), + REVIEW_EXPIRES_AT VARCHAR2(20), + USE_NON_PERSONALIZED_NOTI VARCHAR2(10), + CI VARCHAR2(88), + PHONE_NUMBER VARCHAR2(30), + NAME VARCHAR2(50), + BIRTHDAY VARCHAR2(8), + EXTERNAL_ID VARCHAR2(40), + ENVELOPE_ID VARCHAR2(34), + ERROR_CODE VARCHAR2(40), + ERROR_MESSAGE CLOB, + LAST_UPDT_DT TIMESTAMP(6), + REGIST_DT TIMESTAMP(6), + BILL_UID VARCHAR2(45 char) + constraint FK_BILL_UID + references ENS_BILL (BILL_UID), + SEND_MAST_ID NUMBER(19) + constraint FK_SEND_MAST_ID + references ENS_SND_MAST(SEND_MAST_ID) +); +comment on table ENS_SND_DTL_KKO_TALK is '카카오톡 상세'; +comment on column ENS_SND_DTL_KKO_TALK.SEND_DETAIL_ID is '발송 상세 ID'; +comment on column ENS_SND_DTL_KKO_TALK.SEND_MAST_ID is '발송 마스터 ID'; +comment on column ENS_SND_DTL_KKO_TALK.TITLE is '제목'; +comment on column ENS_SND_DTL_KKO_TALK.LINK is '웹링크'; +comment on column ENS_SND_DTL_KKO_TALK.HASH is '해시'; +comment on column ENS_SND_DTL_KKO_TALK.GUIDE is '메시지'; +comment on column ENS_SND_DTL_KKO_TALK.PAYLOAD is 'PAYLOAD'; +comment on column ENS_SND_DTL_KKO_TALK.READ_EXPIRES_AT is '최초 열람 만료 일시'; +comment on column ENS_SND_DTL_KKO_TALK.REVIEW_EXPIRES_AT is '재열람 만료 일시'; +comment on column ENS_SND_DTL_KKO_TALK.USE_NON_PERSONALIZED_NOTI is '알림톡 개인정보 제거 여부'; +comment on column ENS_SND_DTL_KKO_TALK.CI is 'CI'; +comment on column ENS_SND_DTL_KKO_TALK.PHONE_NUMBER is '수신인 전화번호'; +comment on column ENS_SND_DTL_KKO_TALK.NAME is '수신인 이름'; +comment on column ENS_SND_DTL_KKO_TALK.BIRTHDAY is '수신인 생년월일'; +comment on column ENS_SND_DTL_KKO_TALK.EXTERNAL_ID is '문서 매핑 식별 ID'; +comment on column ENS_SND_DTL_KKO_TALK.ENVELOPE_ID is '문서 고유 ID'; +comment on column ENS_SND_DTL_KKO_TALK.STATUS is '문서 상태'; +comment on column ENS_SND_DTL_KKO_TALK.SENT_AT is '문서 송신 일시'; +comment on column ENS_SND_DTL_KKO_TALK.RECEIVED_AT is '문서 수신 일시'; +comment on column ENS_SND_DTL_KKO_TALK.READ_AT is '문서 열람 일시'; +comment on column ENS_SND_DTL_KKO_TALK.AUTHENTICATED_AT is '문서 열람 인증 일시'; +comment on column ENS_SND_DTL_KKO_TALK.OTT_VERIFIED_AT is '토큰 검증 일시'; +comment on column ENS_SND_DTL_KKO_TALK.IS_NOTIFICATION_UNAVAILABLE is '알림톡 수신 가능 여부'; +comment on column ENS_SND_DTL_KKO_TALK.USER_NOTIFIED_AT is '알림톡 수신 일시'; +comment on column ENS_SND_DTL_KKO_TALK.DISTRIBUTION_RECEIVED_AT is '유통정보 수신 일시'; +comment on column ENS_SND_DTL_KKO_TALK.ERROR_CODE is '에러 코드'; +comment on column ENS_SND_DTL_KKO_TALK.ERROR_MESSAGE is '에러 메시지'; +comment on column ENS_SND_DTL_KKO_TALK.REGIST_DT is '등록 일시'; +comment on column ENS_SND_DTL_KKO_TALK.LAST_UPDT_DT is '수정 일시'; + +/* FIXME: 카카오톡에 맞게 변경 필요 */ +create table ENS_TMPLT_MNG_KKO_TALK +( + CI_TRANS_USE_YN VARCHAR2(1 char) not null, + CS_NAME VARCHAR2(10 char) not null, + CS_NUMBER VARCHAR2(20 char) not null, + PRY_MESSAGE VARCHAR2(2000 char), + TMPLT_CS_INFO_USE_YN VARCHAR2(1 char) not null, + TMPLT_MSG_USE_YN VARCHAR2(1 char) not null, + ORG_CD VARCHAR2(255 char) not null, + TMPLT_CD VARCHAR2(30 char) not null, + primary key (ORG_CD, TMPLT_CD), + constraint FK_TMPLT_MNG_KKO_TALK + foreign key (ORG_CD, TMPLT_CD) references ENS_TMPLT_MNG +); diff --git a/src/main/java/cokr/xit/ens/core/utils/DateUtil.java b/src/main/java/cokr/xit/ens/core/utils/DateUtil.java index ae46ec6..a10c4df 100644 --- a/src/main/java/cokr/xit/ens/core/utils/DateUtil.java +++ b/src/main/java/cokr/xit/ens/core/utils/DateUtil.java @@ -1,8 +1,6 @@ package cokr.xit.ens.core.utils; -import lombok.extern.slf4j.Slf4j; - import java.sql.Timestamp; import java.text.SimpleDateFormat; import java.time.LocalDateTime; @@ -11,6 +9,10 @@ import java.util.Calendar; import java.util.Date; import java.util.Optional; +import org.apache.commons.lang.StringUtils; + +import lombok.extern.slf4j.Slf4j; + @Slf4j public class DateUtil { @@ -406,5 +408,14 @@ public class DateUtil { return new SimpleDateFormat(pattern).format(date); } - + /** + * yyyy-MM-dd'T'HH:mm:ss String > fmt 포맷으로 변환(null 인경우 yyyy-MM-dd HH:mm:ss String 으로 반환) + * @param timeT + * @param fmt 변환할 fromat + * @return + */ + public static String getTimeOfTimeT(String timeT, String fmt) { + return LocalDateTime.parse(timeT, DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss")) + .format(DateTimeFormatter.ofPattern(StringUtils.isEmpty(fmt)? "DEFAULT_YMD_DT_FMT": fmt)); + } } diff --git a/src/main/java/cokr/xit/ens/modules/common/code/PostSeCd.java b/src/main/java/cokr/xit/ens/modules/common/code/PostSeCd.java index 431bba6..363f519 100644 --- a/src/main/java/cokr/xit/ens/modules/common/code/PostSeCd.java +++ b/src/main/java/cokr/xit/ens/modules/common/code/PostSeCd.java @@ -13,7 +13,9 @@ public enum PostSeCd implements CodeMapperType { ,kkoAlimtalk("카카오페이 알림톡") ,nvSigntalk("네이버 고지서(인증톡)") ,ktSigntalk("KT 인증톡") - ,ktGibis("KT 인증톡(지비스)"); + ,ktGibis("KT 인증톡(지비스)") + ,kkoTalk("카카오톡 인증톡") + ; @Getter diff --git a/src/main/java/cokr/xit/ens/modules/common/ctgy/intgrnnoti/domain/IntgrnSendDetail.java b/src/main/java/cokr/xit/ens/modules/common/ctgy/intgrnnoti/domain/IntgrnSendDetail.java index 3c34e76..5c46787 100644 --- a/src/main/java/cokr/xit/ens/modules/common/ctgy/intgrnnoti/domain/IntgrnSendDetail.java +++ b/src/main/java/cokr/xit/ens/modules/common/ctgy/intgrnnoti/domain/IntgrnSendDetail.java @@ -1,16 +1,35 @@ package cokr.xit.ens.modules.common.ctgy.intgrnnoti.domain; +import javax.persistence.Column; +import javax.persistence.Embedded; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Index; +import javax.persistence.JoinColumn; +import javax.persistence.Lob; +import javax.persistence.ManyToOne; +import javax.persistence.OneToOne; +import javax.persistence.Table; +import javax.persistence.TableGenerator; + import cokr.xit.ens.modules.common.code.IntgrnDtlStatCd; import cokr.xit.ens.modules.common.code.PostSeCd; import cokr.xit.ens.modules.common.ctgy.intgrnbill.support.entity.Bill; import cokr.xit.ens.modules.common.domain.BaseEntity; import cokr.xit.ens.modules.common.domain.support.FieldError; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.*; +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; import lombok.experimental.SuperBuilder; -import javax.persistence.*; - @Entity //@Data @Getter @@ -31,27 +50,27 @@ public class IntgrnSendDetail extends BaseEntity { @TableGenerator(table = "ens_seq_generator", name = "IntgrnSendDetail_Generator" , pkColumnName = "seq_name", pkColumnValue = "IntgrnSendDetail_id" , initialValue = 0, allocationSize = 50) - - private Long intSendDetailId; + + private Long intSendDetailId; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "int_send_mast_id") - private IntgrnSendMast intgrnSendMast; + private IntgrnSendMast intgrnSendMast; + - @Column(name = "linked_uuid", length = 40) @Setter private String linkedUuid; - + @Column(name = "ci", nullable = true) private String ci; - + @Column(name = "jid", nullable = true, length = 24) private String jid; - + @Column(name = "mblphonNo", nullable = true) private String mblphonNo; @@ -60,12 +79,12 @@ public class IntgrnSendDetail extends BaseEntity { // @Lob // private String jKkoBillLinkInfo; - + @Column(name = "json_tmplt_msg_data", nullable = true) @Lob private String jTmpltMsgData; - + @Column(name = "json_mbl_page_data", nullable = true) @Lob private String jMblPageData; @@ -74,15 +93,24 @@ public class IntgrnSendDetail extends BaseEntity { @Column(name = "j_acpt_doc_kko_at", nullable = true) @Lob private String jAcptDocKkoAt; + @Column(name = "j_acpt_doc_kko_md", nullable = true) @Lob private String jAcptDocKkoMd; + + // FIXME: kkotalk 추가 + @Column(name = "j_acpt_doc_kko_talk", nullable = true) + @Lob + private String jAcptDocKkoTalk; + @Column(name = "j_acpt_doc_nv_st", nullable = true) @Lob private String jAcptDocNvSt; + @Column(name = "j_acpt_doc_kt_st", nullable = true) @Lob private String jAcptDocKtSt; + @Column(name = "j_acpt_doc_kt_gbs", nullable = true) @Lob private String jAcptDocKtGbs; @@ -91,61 +119,71 @@ public class IntgrnSendDetail extends BaseEntity { @Column(name = "cur_post_se", nullable = true) @Enumerated(EnumType.STRING) private PostSeCd curPostSe; + @Setter @Column(name = "send_detail_id_kko_at", nullable = true) private Long sendDetailIdKkoAt; + @Setter @Column(name = "send_detail_id_kko_md", nullable = true) private Long sendDetailIdKkoMd; + + // FIXME: kkotalk 추가 + @Setter + @Column(name = "send_detail_id_kko_talk", nullable = true) + private Long sendDetailIdKkoTalk; + @Setter @Column(name = "send_detail_id_nv_st", nullable = true) private Long sendDetailIdNvSt; + @Setter @Column(name = "send_detail_id_kt_st", nullable = true) private Long sendDetailIdKtSt; + @Setter @Column(name = "send_detail_id_kt_gbs", nullable = true) private Long sendDetailIdKtGbs; - + @Setter @Enumerated(EnumType.STRING) @Column(name = "cur_stat_cd", nullable = false) private IntgrnDtlStatCd curStatCd; - + @Column(name = "doc_sent_dt", nullable = true, length = 14) @Setter private String docSentDt; - + @Column(name = "doc_received_dt", nullable = true, length = 14) @Setter private String docReceivedDt; - + @Column(name = "doc_auth_frst_dt", nullable = true, length = 14) @Setter private String docAuthFrstDt; - + @Column(name = "doc_token_vrfy_frst_dt", nullable = true, length = 14) @Setter private String docTokenVrfyFrstDt; - + @Column(name = "doc_read_frst_dt", nullable = true, length = 14) @Setter private String docReadFrstDt; - + @Column(name = "doc_user_notied_dt", nullable = true, length = 14) @Setter private String docUserNotiedDt; - + @Column(name = "mk_bill_use_yn", nullable = true, length = 1) private String mkBillUseYn; // @Column(name = "mk_bill_uid", nullable = true, length = 45) // private String mkBillUid; - + @JoinColumn(name = "bill_uid", referencedColumnName = "bill_uid") @OneToOne(fetch = FetchType.LAZY) private Bill bill; diff --git a/src/main/java/cokr/xit/ens/modules/common/ctgy/intgrnnoti/model/config/AcptData.java b/src/main/java/cokr/xit/ens/modules/common/ctgy/intgrnnoti/model/config/AcptData.java index 7c7e602..da844f3 100644 --- a/src/main/java/cokr/xit/ens/modules/common/ctgy/intgrnnoti/model/config/AcptData.java +++ b/src/main/java/cokr/xit/ens/modules/common/ctgy/intgrnnoti/model/config/AcptData.java @@ -1,14 +1,19 @@ package cokr.xit.ens.modules.common.ctgy.intgrnnoti.model.config; +import javax.validation.Valid; + import cokr.xit.ens.modules.kkoalimtalk.model.config.DocumentConfKkoAt; import cokr.xit.ens.modules.kkomydoc.model.config.DocumentConfKkoMd; +import cokr.xit.ens.modules.kkotalk.model.KkotalkApiDTO; import cokr.xit.ens.modules.ktsigntalk.direct.model.config.DocumentConfKtSt; import cokr.xit.ens.modules.ktsigntalk.gibis.model.config.DocumentConfKtGbs; import cokr.xit.ens.modules.nvsigntalk.model.config.DocumentConfNvSt; import io.swagger.v3.oas.annotations.media.Schema; -import lombok.*; - -import javax.validation.Valid; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; @Getter @ToString @@ -26,6 +31,11 @@ public class AcptData { @Valid private DocumentConfKkoAt kko_at; + // FIXME: 카카오톡 접수요청 추가 + @Schema(required = false, title = "카카오톡 접수요청", example = " ") + @Valid + private KkotalkApiDTO.Envelope kko_talk; + @Schema(required = false, title = "네이버 고지서(인증톡) 접수요청", example = " ") @Valid private DocumentConfNvSt nv_st; diff --git a/src/main/java/cokr/xit/ens/modules/common/ctgy/intgrnnoti/service/support/AcceptDataMakerFactory.java b/src/main/java/cokr/xit/ens/modules/common/ctgy/intgrnnoti/service/support/AcceptDataMakerFactory.java index c788351..d7b5d14 100644 --- a/src/main/java/cokr/xit/ens/modules/common/ctgy/intgrnnoti/service/support/AcceptDataMakerFactory.java +++ b/src/main/java/cokr/xit/ens/modules/common/ctgy/intgrnnoti/service/support/AcceptDataMakerFactory.java @@ -1,26 +1,28 @@ package cokr.xit.ens.modules.common.ctgy.intgrnnoti.service.support; +import java.util.List; + import cokr.xit.ens.modules.common.code.PostSeCd; import cokr.xit.ens.modules.common.ctgy.intgrnnoti.domain.IntgrnSendDetail; import cokr.xit.ens.modules.common.ctgy.intgrnnoti.domain.IntgrnSendMast; import cokr.xit.ens.modules.common.ctgy.intgrnnoti.service.support.kkoalimtalk.AcceptDataByKkoAlimtalk; import cokr.xit.ens.modules.common.ctgy.intgrnnoti.service.support.kkomydoc.AcceptDataByKkoMydoc; +import cokr.xit.ens.modules.common.ctgy.intgrnnoti.service.support.kkotalk.AcceptDataByKkoTalk; import cokr.xit.ens.modules.common.ctgy.intgrnnoti.service.support.ktGibis.AcceptDataByKtGibis; import cokr.xit.ens.modules.common.ctgy.intgrnnoti.service.support.ktSigntalk.AcceptDataByKtSigntalk; import cokr.xit.ens.modules.common.ctgy.intgrnnoti.service.support.nvsigntalk.AcceptDataByNvSigntalk; import lombok.RequiredArgsConstructor; -import java.util.List; - +// TODO: Sender에서 call @RequiredArgsConstructor -public class AcceptDataMakerFactory extends AcceptDataFactory { +public class AcceptDataMakerFactory extends AcceptDataFactory> { private final IntgrnSendMast sendMast; private final List sendDetails; private final Boolean isReserveSend; @Override - public AcceptData get(PostSeCd postSeCd) { + public AcceptData get(PostSeCd postSeCd) { switch (postSeCd) { case kkoAlimtalk: return new AcceptDataByKkoAlimtalk(sendMast, sendDetails, isReserveSend); @@ -32,6 +34,9 @@ public class AcceptDataMakerFactory extends AcceptDataFactory { return new AcceptDataByKtSigntalk(sendMast, sendDetails, isReserveSend); case ktGibis: return new AcceptDataByKtGibis(sendMast, sendDetails, isReserveSend); + // FIXME: kkotalk 추가 + case kkoTalk: + return new AcceptDataByKkoTalk(sendMast, sendDetails, isReserveSend); default: throw new IllegalAccessError(postSeCd.getCode() + "에 대한 전자고지 접수 Class가 정의되지 않았습니다."); } diff --git a/src/main/java/cokr/xit/ens/modules/common/ctgy/intgrnnoti/service/support/IntgrnNotiAcceptor.java b/src/main/java/cokr/xit/ens/modules/common/ctgy/intgrnnoti/service/support/IntgrnNotiAcceptor.java index 6d253bd..e771e15 100644 --- a/src/main/java/cokr/xit/ens/modules/common/ctgy/intgrnnoti/service/support/IntgrnNotiAcceptor.java +++ b/src/main/java/cokr/xit/ens/modules/common/ctgy/intgrnnoti/service/support/IntgrnNotiAcceptor.java @@ -1,5 +1,24 @@ package cokr.xit.ens.modules.common.ctgy.intgrnnoti.service.support; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; + +import javax.validation.ConstraintViolation; +import javax.validation.Validation; +import javax.validation.Validator; + +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + import cokr.xit.ens.core.aop.EnsResponseVO; import cokr.xit.ens.core.exception.EnsException; import cokr.xit.ens.core.exception.code.EnsErrCd; @@ -26,21 +45,10 @@ import cokr.xit.ens.modules.common.ctgy.intgrnnoti.model.TmpltMngIntgrnDTO; import cokr.xit.ens.modules.common.ctgy.sys.mng.domain.OrgMng; import cokr.xit.ens.modules.common.ctgy.sys.mng.service.OrgMngService; import cokr.xit.ens.modules.common.ctgy.sys.mng.service.TmpltMngService; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; -import org.springframework.transaction.annotation.Propagation; -import org.springframework.transaction.annotation.Transactional; - -import javax.validation.ConstraintViolation; -import javax.validation.Validation; -import javax.validation.Validator; -import java.util.*; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.stream.Collectors; +// TODO: Accept @Slf4j @Component @RequiredArgsConstructor @@ -118,6 +126,28 @@ public class IntgrnNotiAcceptor implements EnsPhaseProcSupport { + + private final ApplicationEventPublisher applicationEventPublisher; + private final KkoTalkService kkoTalkService; + private final IntgrnSendDetailRepository intgrnSendDetailRepository; + + + @Override + public void accept(AcceptDataByKkoTalk ev) { + final PostSeCd POST_SE_CD = PostSeCd.kkoMydoc; + EnsResponseVO responseVO = null; + try { + + responseVO = kkoTalkService.accept(ev.createReqDto()); + + + Map resultInfo = (Map) responseVO.getResultInfo(); + Long sendMastId = (Long) resultInfo.get("sendMastId"); + List sendDetails = (List) resultInfo.get("sendDetails"); + if (EnsErrCd.OK.equals(responseVO.getErrCode())) { + Map sendDetailIdMap = convertSendDetailIdMap(sendDetails); + this.acceptSuccess(ev.getIntgrnSendDetails(), POST_SE_CD, sendDetailIdMap); + intgrnSendDetailRepository.saveAll(ev.getIntgrnSendDetails()); + } else { + this.acceptFail(ev.getIntgrnSendDetails(), POST_SE_CD, responseVO.getErrCode(), String.format("%s %s", responseVO.getErrMsg(), responseVO.getResultInfo().toString())); + intgrnSendDetailRepository.saveAll(ev.getIntgrnSendDetails()); + return; + } + + + if (ev.IsReserveSend()) { + KkoMydocSendReserveEvent event = KkoMydocSendReserveEvent.builder() + .sendMastIds(Arrays.asList(sendMastId)) + .build(); + applicationEventPublisher.publishEvent(event); + + } else { + KkoMydocSendRealtimeEvent event = KkoMydocSendRealtimeEvent.builder() + .sendMastIds(Arrays.asList(sendMastId)) +// .callback(() -> this.fetch(sendMastId, ev.getSendDetails())) + .build(); + applicationEventPublisher.publishEvent(event); + } + } catch (Exception e) { + this.acceptFail(ev.getIntgrnSendDetails(), POST_SE_CD, EnsErrCd.ACPT999, e.getMessage()); + intgrnSendDetailRepository.saveAll(ev.getIntgrnSendDetails()); + responseVO = EnsResponseVO.errBuilder().errCode(EnsErrCd.ERR999).errMsg(e.getMessage()).build(); + } finally { + if (!EnsErrCd.OK.equals(responseVO.getErrCode())) { + String message = createFailMessage(ev.getIntgrnSendDetails().get(0).getIntgrnSendMast().getIntSendMastId(), POST_SE_CD, responseVO); + applicationEventPublisher.publishEvent(MonitorEvent.builder() + .message(MessageByPhase.builder() + .oClass(getClass().getSimpleName() + "." + new Throwable().getStackTrace()[0].getMethodName()) + .postSeCd(PostSeCd.intgrnNoti) + .statCd(StatCd.sendfail) + .errCd(responseVO.getErrCode()) + .message(message) + .build()) + .build()); + } + } + } + + @Override + public Map convertSendDetailIdMap(List sendDetails) { + + Map sendDetailIdMap = sendDetails.stream() + .map(row -> { + Map m = new HashMap<>(); + m.put("key", row.getExternalId()); + m.put("value", row.getSendDetailId()); + return m; + }) + // .map(row -> { + // Map m = new HashMap<>(); + // m.put("key", row.getPropExternalDocumentUuid()); + // m.put("value", row.getSendDetailId()); + // return m; + // }) + .collect(Collectors.toMap(m -> String.valueOf(m.get("key")), m -> (Long) m.get("value"), (k1, k2) -> k1)); + + return sendDetailIdMap; + } + + @Override + public void setSendDetailId(IntgrnSendDetail intgrnSendDetail, Long sendDetailId) { + intgrnSendDetail.setSendDetailIdKkoTalk(sendDetailId); + } +} diff --git a/src/main/java/cokr/xit/ens/modules/common/ctgy/intgrnnoti/service/support/kkotalk/AcceptDataByKkoTalk.java b/src/main/java/cokr/xit/ens/modules/common/ctgy/intgrnnoti/service/support/kkotalk/AcceptDataByKkoTalk.java new file mode 100644 index 0000000..cd7487c --- /dev/null +++ b/src/main/java/cokr/xit/ens/modules/common/ctgy/intgrnnoti/service/support/kkotalk/AcceptDataByKkoTalk.java @@ -0,0 +1,92 @@ +package cokr.xit.ens.modules.common.ctgy.intgrnnoti.service.support.kkotalk; + +import java.time.format.DateTimeFormatter; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import cokr.xit.ens.core.model.EnsBillAcptReqDTO; +import cokr.xit.ens.core.utils.CmmnUtil; +import cokr.xit.ens.core.utils.crypto.AES256; +import cokr.xit.ens.core.utils.crypto.Crypto; +import cokr.xit.ens.modules.common.ctgy.intgrnnoti.domain.IntgrnSendDetail; +import cokr.xit.ens.modules.common.ctgy.intgrnnoti.domain.IntgrnSendMast; +import cokr.xit.ens.modules.common.ctgy.intgrnnoti.service.support.AcceptData; +import cokr.xit.ens.modules.kkomydoc.model.config.XitProperty; +import cokr.xit.ens.modules.kkotalk.model.KkotalkApiDTO; +import cokr.xit.ens.modules.kkotalk.model.KkotalkDTO; +import cokr.xit.ens.modules.kkotalk.model.config.Document; + +// FIXME: 카카오톡 신규 추가 +public class AcceptDataByKkoTalk extends AcceptData { + + private AES256 aes256 = new AES256(Crypto.AES256.getKey()); + + public AcceptDataByKkoTalk(IntgrnSendMast intgrnSendMast, List intgrnSendDetails, Boolean isReserveSend) { + super(intgrnSendMast, intgrnSendDetails, isReserveSend); + } + + @Override + public KkotalkDTO.KkoTalkAcceptReqDTO createReqDto() { + return KkotalkDTO.KkoTalkAcceptReqDTO.builder() + .vender(intgrnSendMast.getVender().getCode()) + .org_cd(intgrnSendMast.getOrgCd()) + .tmplt_cd(intgrnSendMast.getTmpltCd()) + .post_bundle_title(intgrnSendMast.getPostBundleTitle()) + .send_dt(intgrnSendMast.getSendDt().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"))) + .close_dt(intgrnSendMast.getCloseDt().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"))) + .documents(intgrnSendDetails.stream().map(this::createDocument).collect(Collectors.toList())) + .build(); + } + + protected Document createDocument(IntgrnSendDetail intgrnSendDetail) { + + KkotalkApiDTO.Envelope envelope = gson.fromJson(intgrnSendDetail.getJAcptDocKkoTalk(), KkotalkApiDTO.Envelope.class); + + return Document.builder() + .title(envelope.getTitle()) + .content(envelope.getContent()) + .hash(envelope.getHash()) + .guide(envelope.getGuide()) + .payload(envelope.getPayload()) + .readExpiresAt(envelope.getReadExpiresAt()) + .reviewExpiresAt(envelope.getReviewExpiresAt()) + .useNonPersonalizedNotification(envelope.getUseNonPersonalizedNotification()) + .ci(envelope.getCi()) + .phoneNumber(envelope.getPhoneNumber()) + .name(envelope.getName()) + .birthday(envelope.getBirthday()) + .externalId(intgrnSendDetail.getLinkedUuid()) + .xit_property(this.createXitProperty(intgrnSendDetail)) + .build(); + } + + protected XitProperty createXitProperty(IntgrnSendDetail intgrnSendDetail) { + return XitProperty.builder() + .mbl_page_data(CmmnUtil.isEmpty(intgrnSendDetail.getJMblPageData()) ? null : gson.fromJson(intgrnSendDetail.getJMblPageData(), Map.class)) +// .bill_link_info(this.createBillLinkInfo(intgrnSendDetail)) + .bill_acpt_data(this.createBillAcptData(intgrnSendDetail)) +// .jid(intgrnSendDetail.getJids()) + .jid(aes256.decrypt(intgrnSendDetail.getJid())) + .tmplt_msg_data(this.createTmpltMsgData(intgrnSendDetail)) + .build(); + } + + protected EnsBillAcptReqDTO createBillAcptData(IntgrnSendDetail intgrnSendDetail) { + if (CmmnUtil.isEmpty(intgrnSendDetail.getBill())) + return null; + return EnsBillAcptReqDTO.builder() + .billUid(intgrnSendDetail.getBill().getBillUid()) + .billerUserKey(intgrnSendDetail.getBill().getBillerUserKey()) + .billSe(intgrnSendDetail.getBill().getBillSeCd()) + .build(); + } + + protected Map createTmpltMsgData(IntgrnSendDetail intgrnSendDetail) { + if (CmmnUtil.isEmpty(intgrnSendDetail.getJTmpltMsgData())) + return null; + else + return gson.fromJson(intgrnSendDetail.getJTmpltMsgData(), Map.class); + } + +} diff --git a/src/main/java/cokr/xit/ens/modules/common/ctgy/sys/mng/mapper/IOrgMngMapper.java b/src/main/java/cokr/xit/ens/modules/common/ctgy/sys/mng/mapper/IOrgMngMapper.java new file mode 100644 index 0000000..0ca2788 --- /dev/null +++ b/src/main/java/cokr/xit/ens/modules/common/ctgy/sys/mng/mapper/IOrgMngMapper.java @@ -0,0 +1,18 @@ +package cokr.xit.ens.modules.common.ctgy.sys.mng.mapper; + +/** + *
+ * description :
+ * packageName : cokr.xit.ens.modules.common.ctgy.sys.mng.mapper
+ * fileName    : IOrgMngMapper
+ * author      : limju
+ * date        : 2024 9월 03
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2024 9월 03   limju       최초 생성
+ *
+ * 
+ */ +public interface IOrgMngMapper { +} diff --git a/src/main/java/cokr/xit/ens/modules/common/ctgy/sys/mng/presentation/TmpltMngController.java b/src/main/java/cokr/xit/ens/modules/common/ctgy/sys/mng/presentation/TmpltMngController.java index 255e081..1547671 100644 --- a/src/main/java/cokr/xit/ens/modules/common/ctgy/sys/mng/presentation/TmpltMngController.java +++ b/src/main/java/cokr/xit/ens/modules/common/ctgy/sys/mng/presentation/TmpltMngController.java @@ -1,5 +1,19 @@ package cokr.xit.ens.modules.common.ctgy.sys.mng.presentation; +import java.util.List; +import java.util.Map; + +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + import cokr.xit.ens.core.aop.EnsResponseVO; import cokr.xit.ens.modules.common.ctgy.sys.mng.model.TmpltMngDTO; import cokr.xit.ens.modules.common.ctgy.sys.mng.model.TmpltMngSearchDTO; @@ -13,13 +27,6 @@ import io.swagger.v3.oas.annotations.media.ExampleObject; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -import java.util.List; -import java.util.Map; @Tag(name = "TmpltMngController") @Slf4j @@ -59,6 +66,7 @@ public class TmpltMngController { } + // FIXME: 카카오톡 템플릿 신규 등록 추가 확인 @Operation(summary = "템플릿 신규 등록") @Parameters({ @Parameter(in = ParameterIn.PATH, name = "dType"), @@ -85,7 +93,10 @@ public class TmpltMngController { , value = "{\"orgCd\":\"EX_ORG001\",\"tmpltCd\":\"EX_TMPLT006\",\"title\":\"테스트 템플릿\",\"message\":\"해당 안내문은 다음과 같습니다.\",\"sndTelNo\":\"0212345678\",\"sendTel\":\"0256781234\",\"ciTransUseYn\":\"Y\",\"tmpltMsgUseYn\":\"Y\",\"tmpltCsInfoUseYn\":\"N\"}"), @ExampleObject(name = "Example_07" , summary = "KT 인증톡(ktgbs)" - , value = "{\"orgCd\":\"EX_ORG001\",\"tmpltCd\":\"EX_TMPLT007\",\"title\":\"테스트 템플릿\",\"message\":\"해당 안내문은 다음과 같습니다.\",\"msgCd\":\"MC001\",\"mtype\":\"mms\",\"optType\":1,\"sndTelNo\":\"044-211-3377\",\"sendTel\":\"044-211-3377\",\"ciTransUseYn\":\"Y\",\"tmpltMsgUseYn\":\"Y\",\"tmpltCsInfoUseYn\":\"Y\"}") + , value = "{\"orgCd\":\"EX_ORG001\",\"tmpltCd\":\"EX_TMPLT007\",\"title\":\"테스트 템플릿\",\"message\":\"해당 안내문은 다음과 같습니다.\",\"msgCd\":\"MC001\",\"mtype\":\"mms\",\"optType\":1,\"sndTelNo\":\"044-211-3377\",\"sendTel\":\"044-211-3377\",\"ciTransUseYn\":\"Y\",\"tmpltMsgUseYn\":\"Y\",\"tmpltCsInfoUseYn\":\"Y\"}"), + @ExampleObject(name = "Example_08" + , summary = "카카오톡 인증톡(kkotalk)" + , value = "{\"orgCd\":\"EX_ORG001\",\"tmpltCd\":\"EX_TMPLT008\",\"title\":\"테스트 템플릿\",\"message\":\"해당 안내문은 다음과 같습니다.\",\"csNumber\":\"02-123-1234\",\"csName\":\"고객센터\",\"ciTransUseYn\":\"Y\",\"tmpltMsgUseYn\":\"Y\",\"tmpltCsInfoUseYn\":\"N\"}") }) }) @PostMapping(value = "/sys/mng/tmplt/{dType}", produces = MediaType.APPLICATION_JSON_VALUE) @@ -96,6 +107,7 @@ public class TmpltMngController { } + // FIXME: 카카오톡 템플릿 변경 추가 확인 @Operation(summary = "템플릿 수정") @Parameters({ @Parameter(in = ParameterIn.PATH, name = "dType"), @@ -122,7 +134,10 @@ public class TmpltMngController { , value = "{\"orgCd\":\"EX_ORG001\",\"tmpltCd\":\"EX_TMPLT006\",\"title\":\"테스트 템플릿\",\"message\":\"해당 안내문은 다음과 같습니다.\",\"sndTelNo\":\"0212345678\",\"sendTel\":\"0256781234\",\"ciTransUseYn\":\"Y\",\"tmpltMsgUseYn\":\"Y\",\"tmpltCsInfoUseYn\":\"N\"}"), @ExampleObject(name = "Example_07" , summary = "KT 인증톡(ktgbs)" - , value = "{\"orgCd\":\"EX_ORG001\",\"tmpltCd\":\"EX_TMPLT007\",\"title\":\"테스트 템플릿\",\"message\":\"해당 안내문은 다음과 같습니다.\",\"msgCd\":\"MC001\",\"mtype\":\"mms\",\"optType\":1,\"sndTelNo\":\"044-211-3377\",\"sendTel\":\"044-211-3377\",\"ciTransUseYn\":\"Y\",\"tmpltMsgUseYn\":\"Y\",\"tmpltCsInfoUseYn\":\"Y\"}") + , value = "{\"orgCd\":\"EX_ORG001\",\"tmpltCd\":\"EX_TMPLT007\",\"title\":\"테스트 템플릿\",\"message\":\"해당 안내문은 다음과 같습니다.\",\"msgCd\":\"MC001\",\"mtype\":\"mms\",\"optType\":1,\"sndTelNo\":\"044-211-3377\",\"sendTel\":\"044-211-3377\",\"ciTransUseYn\":\"Y\",\"tmpltMsgUseYn\":\"Y\",\"tmpltCsInfoUseYn\":\"Y\"}"), + @ExampleObject(name = "Example_08" + , summary = "카카오톡 인증톡(kkotalk)" + , value = "{\"orgCd\":\"EX_ORG001\",\"tmpltCd\":\"EX_TMPLT008\",\"title\":\"테스트 템플릿\",\"message\":\"해당 안내문은 다음과 같습니다.\",\"csNumber\":\"02-123-1234\",\"csName\":\"고객센터\",\"ciTransUseYn\":\"Y\",\"tmpltMsgUseYn\":\"Y\",\"tmpltCsInfoUseYn\":\"N\"}") }) }) @PutMapping(value = "/sys/mng/tmplt/{dType}", produces = MediaType.APPLICATION_JSON_VALUE) diff --git a/src/main/java/cokr/xit/ens/modules/kkotalk/domain/SendDetailKkoTalk.java b/src/main/java/cokr/xit/ens/modules/kkotalk/domain/SendDetailKkoTalk.java new file mode 100644 index 0000000..b62a33d --- /dev/null +++ b/src/main/java/cokr/xit/ens/modules/kkotalk/domain/SendDetailKkoTalk.java @@ -0,0 +1,154 @@ +package cokr.xit.ens.modules.kkotalk.domain; + +import javax.persistence.Column; +import javax.persistence.Embedded; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Index; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToOne; +import javax.persistence.Table; +import javax.persistence.TableGenerator; + +import cokr.xit.ens.modules.common.ctgy.intgrnbill.support.entity.Bill; +import cokr.xit.ens.modules.common.domain.BaseEntity; +import cokr.xit.ens.modules.common.domain.SendMast; +import cokr.xit.ens.modules.common.domain.support.FieldError; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.experimental.SuperBuilder; + +// FIXME: 카카오톡 신규 추가 +@Entity +@Getter +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@Table(name = "ens_snd_dtl_kko_talk", schema = "", catalog = "", indexes = { + @Index(name = "idx_snd_dtl_kko_talk_uk_id", columnList = "envelope_id"), + @Index(name = "idx_snd_dtl_kko_talk_uk", columnList = "envelope_id, external_id"), +// @Index(name = "idx_kkomd_bill_uid", columnList = "mk_bill_uid"), +}) +public class SendDetailKkoTalk extends BaseEntity { + + @Id +// @GeneratedValue(strategy=GenerationType.AUTO) + @GeneratedValue(strategy = GenerationType.TABLE, generator = "SendDetailKkoTalk_Generator") + @TableGenerator(table = "ens_seq_generator", name = "SendDetailKkoTalk_Generator" + , pkColumnName = "seq_name", pkColumnValue = "SendDetailKkoTalk_id" + , initialValue = 0, allocationSize = 200) + private Long sendDetailId; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "send_mast_id") + private SendMast sendMast; + + @Column(name = "title", nullable = true, length = 40) + @Setter + private String title; + + @Column(name = "link", nullable = true, length = 99) + @Setter + private String link; + + @Column(name = "hash", nullable = true, length = 99) + @Setter + private String hash; + + @Column(name = "read_expires_at", nullable = true) + private String readExpiresAt; + + + @Column(name = "review_expires_at", nullable = true) + private String reviewExpiresAt; + + @Column(name = "guide", nullable = true) + private String guide; + + /** + * payload + */ + @Column(name = "payload", nullable = true) + private String payload; + + /** + *
+     * 알림톡 내용에 개인정보 제거 여부
+     * default: false
+     * 
+ */ + @Column(name = "is_notification_unavailable", nullable = true) + private Boolean useNonPersonalizedNotification = false; + + /** + * 수신자 CI + */ + @Column(name = "ci", nullable = true) + private String ci; + + /** + *
+     * 수신자 전화번호
+     * ci 미전송시 필수
+     * 
+ */ + @Column(name = "phone_number", nullable = true) + private String phoneNumber; + + /** + *
+     * 수신자 이름
+     * ci 미전송시 필수
+     * 
+ */ + @Column(name = "name", nullable = true) + private String name; + + /** + *
+     * 수신자 생년월일 (YYYY-MM-DD 형식)
+     * ci 미전송시 필수
+     * 
+ */ + @Column(name = "birthday", nullable = true) + private String birthday; + + /** + *
+     * 문서매핑용 식별자 - 최대 40자
+     * 
+ */ + @Column(name = "external_id", nullable = true) + private String externalId; + + @Column(name = "envelope_id", nullable = true) + private String envelopeId; + + + @Embedded + @Setter + private FieldError error; + + + @Column(name = "mk_bill_use_yn", nullable = true, length = 1) + private String mkBillUseYn; + +// @Column(name = "mk_bill_uid", nullable = true, length = 45) +// private String mkBillUid; + + @JoinColumn(name = "bill_uid", referencedColumnName = "bill_uid") + @OneToOne(fetch = FetchType.LAZY) + private Bill bill; + + @Column(name = "mk_jid", nullable = true, length = 24) + private String mkJid; + + @Column(name = "mk_tmplt_msg_json_data", nullable = true, length = 4000) + private String mkTmpltMsgJsonData; +} diff --git a/src/main/java/cokr/xit/ens/modules/kkotalk/domain/TmpltMngKkoTalk.java b/src/main/java/cokr/xit/ens/modules/kkotalk/domain/TmpltMngKkoTalk.java new file mode 100644 index 0000000..c0d806c --- /dev/null +++ b/src/main/java/cokr/xit/ens/modules/kkotalk/domain/TmpltMngKkoTalk.java @@ -0,0 +1,78 @@ +package cokr.xit.ens.modules.kkotalk.domain; + +import java.util.Objects; + +import javax.persistence.Column; +import javax.persistence.DiscriminatorValue; +import javax.persistence.Entity; +import javax.persistence.Table; + +import org.hibernate.proxy.HibernateProxy; + +import cokr.xit.ens.modules.common.ctgy.sys.mng.domain.TmpltMng; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; +import lombok.experimental.SuperBuilder; + +// FIXME: 카카오톡 신규 추가 +@Entity +@Getter +@ToString +@SuperBuilder +@NoArgsConstructor +@Table(name = "ens_tmplt_mng_kko_talk", schema = "", catalog = "") +@DiscriminatorValue("kkotalk") +public class TmpltMngKkoTalk extends TmpltMng { + + + @Column(name = "pryMessage", nullable = true, length = 2000) + @Setter + private String pryMessage; + + + @Column(name = "cs_number", nullable = false, length = 20) + @Setter + private String csNumber; + + + @Column(name = "cs_name", nullable = false, length = 10) + @Setter + private String csName; + + + @Column(name = "ci_trans_use_yn", nullable = false, length = 1) + private String ciTransUseYn; + + + @Column(name = "tmplt_msg_use_yn", nullable = false, length = 1) + private String tmpltMsgUseYn; + + + @Column(name = "tmplt_cs_info_use_yn", nullable = false, length = 1) + private String tmpltCsInfoUseYn; + + @Override + public final boolean equals(Object o) { + if (this == o) + return true; + if (o == null) + return false; + Class oEffectiveClass = o instanceof HibernateProxy ? + ((HibernateProxy)o).getHibernateLazyInitializer().getPersistentClass() : o.getClass(); + Class thisEffectiveClass = this instanceof HibernateProxy ? + ((HibernateProxy)this).getHibernateLazyInitializer().getPersistentClass() : this.getClass(); + if (thisEffectiveClass != oEffectiveClass) + return false; + TmpltMngKkoTalk that = (TmpltMngKkoTalk)o; + return getOrgMng() != null && Objects.equals(getOrgMng(), that.getOrgMng()); + } + + @Override + public final int hashCode() { + return this instanceof HibernateProxy ? + ((HibernateProxy)this).getHibernateLazyInitializer().getPersistentClass().hashCode() : + getClass().hashCode(); + } +} diff --git a/src/main/java/cokr/xit/ens/modules/kkotalk/domain/repository/SendDetailKkoTalkRepository.java b/src/main/java/cokr/xit/ens/modules/kkotalk/domain/repository/SendDetailKkoTalkRepository.java new file mode 100644 index 0000000..cadf641 --- /dev/null +++ b/src/main/java/cokr/xit/ens/modules/kkotalk/domain/repository/SendDetailKkoTalkRepository.java @@ -0,0 +1,22 @@ +// package cokr.xit.ens.modules.kkotalk.domain.repository; +// +// import java.util.List; +// import java.util.Optional; +// +// import org.springframework.data.jpa.repository.JpaRepository; +// +// import cokr.xit.ens.modules.common.domain.SendMast; +// import cokr.xit.ens.modules.kkotalk.domain.SendDetailKkoTalk; +// +// public interface SendDetailKkoTalkRepository +// extends JpaRepository, SendDetailKkoTalkRepositoryCustom { +// +// List findAllBySendMast(SendMast sendMast); +// +// List findAllBySendMastAndDocumentBinderUuidIsNotNull(SendMast sendMast); +// +// Optional findByPropExternalDocumentUuidAndDocumentBinderUuid(String propExternalDocumentUuid, String documentBinderUuid); +// +// Optional findByPropExternalDocumentUuid(String propExternalDocumentUuid); +// +// } diff --git a/src/main/java/cokr/xit/ens/modules/kkotalk/domain/repository/SendDetailKkoTalkRepositoryCustom.java b/src/main/java/cokr/xit/ens/modules/kkotalk/domain/repository/SendDetailKkoTalkRepositoryCustom.java new file mode 100644 index 0000000..b49e279 --- /dev/null +++ b/src/main/java/cokr/xit/ens/modules/kkotalk/domain/repository/SendDetailKkoTalkRepositoryCustom.java @@ -0,0 +1,13 @@ +// package cokr.xit.ens.modules.kkotalk.domain.repository; +// +// import java.util.List; +// import java.util.Optional; +// +// import cokr.xit.ens.modules.common.biztmplt.SendDetailRepositorySupport; +// import cokr.xit.ens.modules.kkotalk.domain.SendDetailKkoTalk; +// +// public interface SendDetailKkoTalkRepositoryCustom extends SendDetailRepositorySupport { +// +// List findAllFetchBySendMastId(Long sendMastId); +// Optional findFetchByPropExternalDocumentUuidAndDocumentBinderUuid(String propExternalDocumentUuid, String documentBinderUuid); +// } diff --git a/src/main/java/cokr/xit/ens/modules/kkotalk/domain/repository/SendDetailKkoTalkRepositoryImpl.java b/src/main/java/cokr/xit/ens/modules/kkotalk/domain/repository/SendDetailKkoTalkRepositoryImpl.java new file mode 100644 index 0000000..e4bb3a7 --- /dev/null +++ b/src/main/java/cokr/xit/ens/modules/kkotalk/domain/repository/SendDetailKkoTalkRepositoryImpl.java @@ -0,0 +1,57 @@ +// package cokr.xit.ens.modules.kkotalk.domain.repository; +// +// import static cokr.xit.ens.modules.common.ctgy.intgrnbill.support.entity.QBill.*; +// import static cokr.xit.ens.modules.common.domain.QSendMast.*; +// import static cokr.xit.ens.modules.kkomydoc.domain.QSendDetailKkoMydoc.*; +// +// import java.util.List; +// import java.util.Optional; +// +// import com.querydsl.core.BooleanBuilder; +// import com.querydsl.jpa.impl.JPAQueryFactory; +// +// import cokr.xit.ens.core.utils.CmmnUtil; +// import cokr.xit.ens.modules.kkotalk.domain.SendDetailKkoTalk; +// import lombok.RequiredArgsConstructor; +// +// @RequiredArgsConstructor +// public class SendDetailKkoTalkRepositoryImpl implements SendDetailKkoTalkRepositoryCustom { +// private final JPAQueryFactory query; +// +// // @Override +// // public List findAllFetchBySendMastIdAndMkBillUseYAndUrlIsNull(Long sendMastId, boolean urlIsNull) { +// // return query.selectFrom(sendDetailKkoMydoc) +// // .innerJoin(sendDetailKkoMydoc.sendMast, sendMast) +// // .fetchJoin() +// // .leftJoin(sendDetailKkoMydoc.kkoBillMast, kkoBillMast) +// // .fetchJoin() +// // .where(sendDetailKkoMydoc.sendMast.sendMastId.eq(sendMastId) +// // .and(sendDetailKkoMydoc.mkBillUseYn.eq("Y")) +// // .and(urlIsNull ? sendDetailKkoMydoc.kkoBillMast.url.isNull() : sendDetailKkoMydoc.kkoBillMast.url.isNotNull()) +// // ) +// // .fetch(); +// // } +// +// @Override +// public List findAllFetchBySendMastId(Long sendMastId) { +// return query.selectFrom(sendDetailKkoTalk) +// .innerJoin(sendDetailKkoTalk.sendMast, sendMast).fetchJoin() +// // .leftJoin(sendDetailKkoMydoc.kkoBillMast, kkoBillMast) +// .leftJoin(sendDetailKkTalk.bill, bill).fetchJoin() +// .where(sendDetailKkoTalk.sendMast.sendMastId.eq(sendMastId)) +// .fetch(); +// } +// +// @Override +// public Optional findFetchByPropExternalDocumentUuidAndDocumentBinderUuid(String propExternalDocumentUuid, String documentBinderUuid) { +// BooleanBuilder builder = new BooleanBuilder(); +// builder.and(sendDetailKkoMydoc.documentBinderUuid.eq(documentBinderUuid)); +// if (!CmmnUtil.isEmpty(propExternalDocumentUuid)) +// builder.and(sendDetailKkoTalk.propExternalDocumentUuid.eq(propExternalDocumentUuid)); +// +// return Optional.ofNullable(query.selectFrom(sendDetailKkoTalk) +// .innerJoin(sendDetailKkoTalk.sendMast, sendMast).fetchJoin() +// .where(builder) +// .fetchOne()); +// } +// } diff --git a/src/main/java/cokr/xit/ens/modules/kkotalk/domain/repository/TmpltMngKkoTalkRepository.java b/src/main/java/cokr/xit/ens/modules/kkotalk/domain/repository/TmpltMngKkoTalkRepository.java new file mode 100644 index 0000000..cdf8dc3 --- /dev/null +++ b/src/main/java/cokr/xit/ens/modules/kkotalk/domain/repository/TmpltMngKkoTalkRepository.java @@ -0,0 +1,10 @@ +package cokr.xit.ens.modules.kkotalk.domain.repository; + +import org.springframework.data.jpa.repository.JpaRepository; + +import cokr.xit.ens.modules.kkotalk.domain.TmpltMngKkoTalk; + +// FIXME: 카카오톡 신규 추가 +public interface TmpltMngKkoTalkRepository extends JpaRepository, + TmpltMngKkoTalkRepositoryCustom { +} diff --git a/src/main/java/cokr/xit/ens/modules/kkotalk/domain/repository/TmpltMngKkoTalkRepositoryCustom.java b/src/main/java/cokr/xit/ens/modules/kkotalk/domain/repository/TmpltMngKkoTalkRepositoryCustom.java new file mode 100644 index 0000000..6d1e8df --- /dev/null +++ b/src/main/java/cokr/xit/ens/modules/kkotalk/domain/repository/TmpltMngKkoTalkRepositoryCustom.java @@ -0,0 +1,11 @@ +package cokr.xit.ens.modules.kkotalk.domain.repository; + +import cokr.xit.ens.modules.common.ctgy.sys.mng.domain.repository.TmpltMngRepositorySupport; +import cokr.xit.ens.modules.common.ctgy.sys.mng.model.TmpltMngSearchDTO; +import cokr.xit.ens.modules.kkotalk.domain.TmpltMngKkoTalk; +import cokr.xit.ens.modules.kkotalk.model.TmpltMngKkoTalkDTO; + +// FIXME: 카카오톡 신규 추가 +public interface TmpltMngKkoTalkRepositoryCustom extends TmpltMngRepositorySupport { + +} diff --git a/src/main/java/cokr/xit/ens/modules/kkotalk/domain/repository/TmpltMngKkoTalkRepositoryImpl.java b/src/main/java/cokr/xit/ens/modules/kkotalk/domain/repository/TmpltMngKkoTalkRepositoryImpl.java new file mode 100644 index 0000000..11e7efe --- /dev/null +++ b/src/main/java/cokr/xit/ens/modules/kkotalk/domain/repository/TmpltMngKkoTalkRepositoryImpl.java @@ -0,0 +1,110 @@ +package cokr.xit.ens.modules.kkotalk.domain.repository; + +import static cokr.xit.ens.modules.common.ctgy.sys.mng.domain.QOrgMng.*; +import static cokr.xit.ens.modules.kkotalk.domain.QTmpltMngKkoTalk.*; + +import java.util.List; +import java.util.Optional; + +import com.querydsl.core.BooleanBuilder; +import com.querydsl.core.types.Projections; +import com.querydsl.core.types.QBean; +import com.querydsl.jpa.impl.JPAQueryFactory; + +import cokr.xit.ens.core.utils.CmmnUtil; +import cokr.xit.ens.modules.common.ctgy.sys.mng.model.TmpltMngSearchDTO; +import cokr.xit.ens.modules.kkotalk.domain.TmpltMngKkoTalk; +import cokr.xit.ens.modules.kkotalk.model.TmpltMngKkoTalkDTO; +import lombok.RequiredArgsConstructor; + +// FIXME: 카카오톡 신규 추가 +@RequiredArgsConstructor +public class TmpltMngKkoTalkRepositoryImpl implements TmpltMngKkoTalkRepositoryCustom { + + private final JPAQueryFactory query; + + @Override + public Optional findFetchByOrgCdAndTmpltCd(String orgCd, String tmpltCd) { + return Optional.ofNullable(query.selectFrom(tmpltMngKkoTalk) + .where(tmpltMngKkoTalk.orgMng.orgCd.eq(orgCd) + .and(tmpltMngKkoTalk.tmpltCd.eq(tmpltCd))) + .fetchOne()); + } + + @Override + public List findAllFetchBySearchDTO(TmpltMngSearchDTO searchDTO) { + return query.selectFrom(tmpltMngKkoTalk) + .innerJoin(tmpltMngKkoTalk.orgMng, orgMng) + .fetchJoin() + .where(eqSearchDTO(searchDTO)) + .fetch(); + } + + @Override + public Optional findDtoByOrgCdAndTmpltCd(String orgCd, String tmpltCd) { + return Optional.ofNullable(query.select(mappedDto()) + .from(tmpltMngKkoTalk) + .where(tmpltMngKkoTalk.orgMng.orgCd.eq(orgCd) + .and(tmpltMngKkoTalk.tmpltCd.eq(tmpltCd))) + .fetchOne()); + } + + + @Override + public List findAllDtoBySearchDTO(TmpltMngSearchDTO searchDTO) { + return query.select(mappedDto()) + .from(tmpltMngKkoTalk) + .innerJoin(tmpltMngKkoTalk.orgMng, orgMng) + .where(eqSearchDTO(searchDTO)) + .fetch(); + } + + private QBean mappedDto() { + return Projections.fields(TmpltMngKkoTalkDTO.class + , tmpltMngKkoTalk.dtype + , tmpltMngKkoTalk.orgMng.orgCd + , tmpltMngKkoTalk.orgMng.orgNm + , tmpltMngKkoTalk.tmpltCd + , tmpltMngKkoTalk.title + , tmpltMngKkoTalk.message + , tmpltMngKkoTalk.useYn + , tmpltMngKkoTalk.csNumber + , tmpltMngKkoTalk.csName + , tmpltMngKkoTalk.ciTransUseYn + , tmpltMngKkoTalk.tmpltMsgUseYn + , tmpltMngKkoTalk.tmpltCsInfoUseYn + , tmpltMngKkoTalk.pryMessage + ); + } + + @Override + public Optional findFetchByOrgCdAndTmpltCdAndUseYn(String orgCd, String tmpltCd, String useYn) { + return Optional.ofNullable( + query.selectFrom(tmpltMngKkoTalk) + .innerJoin(tmpltMngKkoTalk.orgMng, orgMng) + .fetchJoin() + .where( + tmpltMngKkoTalk.orgMng.orgCd.eq(orgCd) + .and(tmpltMngKkoTalk.tmpltCd.eq(tmpltCd)) + .and(tmpltMngKkoTalk.useYn.eq(useYn)) + ) + .fetchOne()); + } + + private BooleanBuilder eqSearchDTO(TmpltMngSearchDTO searchDTO) { + BooleanBuilder builder = new BooleanBuilder(); + + if (!CmmnUtil.isEmpty(searchDTO.getSchOrgCd())) + builder.and(tmpltMngKkoTalk.orgMng.orgCd.eq(searchDTO.getSchOrgCd())); + + if (!CmmnUtil.isEmpty(searchDTO.getSchTmpltCd())) + builder.and(tmpltMngKkoTalk.tmpltCd.eq(searchDTO.getSchTmpltCd())); + + if (!CmmnUtil.isEmpty(searchDTO.getSchTitle())) + builder.and(tmpltMngKkoTalk.title.like(searchDTO.getSchTitle() + "%")); + + + return builder; + } + +} diff --git a/src/main/java/cokr/xit/ens/modules/kkotalk/mapper/IKkoTalkMapper.java b/src/main/java/cokr/xit/ens/modules/kkotalk/mapper/IKkoTalkMapper.java new file mode 100644 index 0000000..0e95b0c --- /dev/null +++ b/src/main/java/cokr/xit/ens/modules/kkotalk/mapper/IKkoTalkMapper.java @@ -0,0 +1,34 @@ +package cokr.xit.ens.modules.kkotalk.mapper; + +import java.util.List; +import java.util.Optional; + +import org.apache.ibatis.annotations.Mapper; + +import cokr.xit.ens.modules.kkotalk.model.KkotalkDTO; + +/** + *
+ * description :
+ * packageName : cokr.xit.ens.modules.kkotalk.mapper
+ * fileName    : IKkoTalkMapper
+ * author      : limju
+ * date        : 2024 9월 03
+ * ======================================================================
+ * 변경일         변경자        변경 내용
+ * ----------------------------------------------------------------------
+ * 2024 9월 03   limju       최초 생성
+ *
+ * 
+ */ +// FIXME: 카카오톡 신규 추가 +@Mapper +public interface IKkoTalkMapper { + void saveSndDtlKkoTalk(KkotalkDTO.SendDetailKkoTalkDTO sendDetailKkoTalk); + + List findAllBySendMastId(Long sendMastId); + + + List findAllFetchBySendMastId(Long sendMastId); + Optional findFetchByExternalIdAndEnvelopeId(String externalId, String envelopeId); +} diff --git a/src/main/java/cokr/xit/ens/modules/kkotalk/model/KkotalkDTO.java b/src/main/java/cokr/xit/ens/modules/kkotalk/model/KkotalkDTO.java index ea1d8c0..7888e69 100644 --- a/src/main/java/cokr/xit/ens/modules/kkotalk/model/KkotalkDTO.java +++ b/src/main/java/cokr/xit/ens/modules/kkotalk/model/KkotalkDTO.java @@ -1,10 +1,13 @@ package cokr.xit.ens.modules.kkotalk.model; +import java.time.LocalDateTime; import java.util.List; import javax.validation.Valid; import javax.validation.constraints.Size; +import cokr.xit.ens.core.model.EnsAcceptReqDTO; +import cokr.xit.ens.modules.kkotalk.model.config.Document; import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Data; @@ -27,6 +30,7 @@ import lombok.experimental.SuperBuilder; * * */ +// FIXME: 카카오톡 신규 추가 public class KkotalkDTO extends KkotalkApiDTO { //------------------ envelop ---------------------------------------------------------------------- @@ -108,4 +112,36 @@ public class KkotalkDTO extends KkotalkApiDTO { } //------------------ bulk ---------------------------------------------------------------------- + @SuperBuilder + @Data + @Schema(name = "KkoTalkAcceptReqDTO") + @NoArgsConstructor + @EqualsAndHashCode(callSuper = true) + public static class KkoTalkAcceptReqDTO extends EnsAcceptReqDTO { + + @Valid + private List documents; + } + + @SuperBuilder + @Data + @Schema(name = "SendDetailKkoTalkDTO") + @NoArgsConstructor + @EqualsAndHashCode(callSuper = true) + public static class SendDetailKkoTalkDTO extends Envelope { + private String link; + private Long sendDetailId; + private Long sendMastId; + private String errorCode; + private String errorMessage; + //private FieldError error; + private String mkBillUseYn; + private String billUid; + private String mkJid; + private String mkTmpltMsgJsonData; + + // Add created and updated timestamps from BaseEntity if needed + private LocalDateTime registAt; + private LocalDateTime lastUpdtDt; + } } diff --git a/src/main/java/cokr/xit/ens/modules/kkotalk/model/TmpltMngKkoTalkDTO.java b/src/main/java/cokr/xit/ens/modules/kkotalk/model/TmpltMngKkoTalkDTO.java new file mode 100644 index 0000000..972c243 --- /dev/null +++ b/src/main/java/cokr/xit/ens/modules/kkotalk/model/TmpltMngKkoTalkDTO.java @@ -0,0 +1,53 @@ +package cokr.xit.ens.modules.kkotalk.model; + +import java.io.Serializable; + +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.Pattern; + +import org.hibernate.validator.constraints.Length; + +import cokr.xit.ens.modules.common.ctgy.sys.mng.model.TmpltMngByTypeDTO; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +// FIXME: 카카오톡 신규 추가 +@EqualsAndHashCode(callSuper = true) +@Data +@SuperBuilder +@NoArgsConstructor +@Schema(name = "TmpltMngKkoTalkDTO") +public class TmpltMngKkoTalkDTO extends TmpltMngByTypeDTO implements Serializable { + + @NotEmpty(message = "고객센터 전화번호는 필수 입력값 입니다.") + @Length(max = 20, message = "고객센터 전화번호의 최대 길이를 초과 했습니다.") + @Schema(required = true, title = "고객센터 전화번호", example = "02-123-4567") + private String csNumber; + + @NotEmpty(message = "고객센터 명은 필수 입력값 입니다.") + @Length(max = 10, message = "고객센터 명의 최대 길이를 초과 했습니다.") + @Schema(required = true, title = "고객센터 명", example = "콜센터") + private String csName; + + @NotEmpty(message = "CI변환사용여부는 필수 입력값 입니다.") + @Length(max = 1, message = "CI변환사용여부의 최대 길이를 초과 했습니다.") + @Pattern(regexp = "(Y|N)", message = "CI변환사용여부는 Y 또는 N 만 입력 할 수 있습니다.") + @Schema(required = true, title = "CI변환사용여부", example = "Y") + private String ciTransUseYn; + + @NotEmpty(message = "템플릿 메시지 사용여부는 필수 입력값 입니다.") + @Length(max = 1, message = "템플릿 메시지 사용여부의 최대 길이를 초과 했습니다.") + @Pattern(regexp = "(Y|N)", message = "템플릿 메시지 사용여부는 Y 또는 N 만 입력 할 수 있습니다.") + @Schema(required = true, title = "템플릿 메시지 사용여부", example = "Y") + private String tmpltMsgUseYn; + + @NotEmpty(message = "템플릿 CS 정보 사용여부는 필수 입력값 입니다.") + @Length(max = 1, message = "템플릿 CS 정보 사용여부의 최대 길이를 초과 했습니다.") + @Pattern(regexp = "(Y|N)", message = "템플릿 CS 정보 사용여부는 Y 또는 N 만 입력 할 수 있습니다.") + @Schema(required = true, title = "템플릿 CS 정보 사용여부", example = "Y") + private String tmpltCsInfoUseYn; + +} diff --git a/src/main/java/cokr/xit/ens/modules/kkotalk/model/config/Document.java b/src/main/java/cokr/xit/ens/modules/kkotalk/model/config/Document.java new file mode 100644 index 0000000..f738dee --- /dev/null +++ b/src/main/java/cokr/xit/ens/modules/kkotalk/model/config/Document.java @@ -0,0 +1,36 @@ +package cokr.xit.ens.modules.kkotalk.model.config; + +import javax.validation.Valid; +import javax.validation.constraints.Size; + +import cokr.xit.ens.modules.kkomydoc.model.config.XitProperty; +import cokr.xit.ens.modules.kkotalk.model.KkotalkApiDTO; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; +import lombok.experimental.SuperBuilder; + +// FIXME: 카카오톡 신규 추가 +@Getter +@ToString +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +@Schema(name = "Document") +public class Document extends KkotalkApiDTO.Envelope { + + /** + *
+     * 상품 코드 - 필수
+     * D10_1|D10_2|D11_1|D11_2
+     * 
+ */ + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, title = "상품코드", example = "D10_2", allowableValues = {"D10_1","D10_2","D11_1","D11_2"}) + @Size(min = 3, max = 5, message = "상품 코드는 필수 입니다(\"D10_1\",\"D10_2\",\"D11_1\",\"D11_2\"") + private String productCode = "D10_2"; + + @Valid + private XitProperty xit_property; +} diff --git a/src/main/java/cokr/xit/ens/modules/kkotalk/model/struct/TmpltMngKkoTalkMapper.java b/src/main/java/cokr/xit/ens/modules/kkotalk/model/struct/TmpltMngKkoTalkMapper.java new file mode 100644 index 0000000..f25aba2 --- /dev/null +++ b/src/main/java/cokr/xit/ens/modules/kkotalk/model/struct/TmpltMngKkoTalkMapper.java @@ -0,0 +1,25 @@ +package cokr.xit.ens.modules.kkotalk.model.struct; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +import cokr.xit.ens.core.mapstruct.GenericMapper; +import cokr.xit.ens.core.mapstruct.StructMapperConfig; +import cokr.xit.ens.modules.kkotalk.domain.TmpltMngKkoTalk; +import cokr.xit.ens.modules.kkotalk.model.TmpltMngKkoTalkDTO; + +// FIXME: 카카오톡 신규 추가 +@Mapper(config = StructMapperConfig.class) +public interface TmpltMngKkoTalkMapper extends GenericMapper { + + + @Override + @Mapping(target = "orgMng.orgCd", source = "orgCd") + TmpltMngKkoTalk toEntity(TmpltMngKkoTalkDTO tmpltMngKkoTalkDTO); + + @Override + @Mapping(target = "orgCd", expression = "java(tmpltMngKkoTalk.getOrgMng().getOrgCd())") + TmpltMngKkoTalkDTO toDto(TmpltMngKkoTalk tmpltMngKkoTalk); + + +} diff --git a/src/main/java/cokr/xit/ens/modules/kkotalk/service/KkoTalkService.java b/src/main/java/cokr/xit/ens/modules/kkotalk/service/KkoTalkService.java new file mode 100644 index 0000000..3eda39f --- /dev/null +++ b/src/main/java/cokr/xit/ens/modules/kkotalk/service/KkoTalkService.java @@ -0,0 +1,497 @@ +package cokr.xit.ens.modules.kkotalk.service; + +import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.apache.commons.lang.StringUtils; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +import cokr.xit.ens.core.aop.EnsResponseVO; +import cokr.xit.ens.core.exception.EnsException; +import cokr.xit.ens.core.exception.code.EnsErrCd; +import cokr.xit.ens.core.utils.CmmnUtil; +import cokr.xit.ens.modules.common.code.PostSeCd; +import cokr.xit.ens.modules.common.code.StatCd; +import cokr.xit.ens.modules.common.ctgy.sys.mng.domain.OrgMng; +import cokr.xit.ens.modules.common.ctgy.sys.mng.service.OrgMngService; +import cokr.xit.ens.modules.common.domain.SendMast; +import cokr.xit.ens.modules.common.domain.repository.SendMastRepository; +import cokr.xit.ens.modules.common.domain.support.FieldError; +import cokr.xit.ens.modules.kkomydoc.domain.SendDetailKkoMydocTokenHist; +import cokr.xit.ens.modules.kkomydoc.domain.repository.SendDetailKkoMydocTokenHistRepository; +import cokr.xit.ens.modules.kkotalk.model.KkotalkApiDTO; +import cokr.xit.ens.modules.kkotalk.model.KkotalkDTO; +import cokr.xit.ens.modules.kkotalk.service.support.IKkoTalkApiService; +import cokr.xit.ens.modules.kkotalk.service.support.KkoTalkAcceptor; +import cokr.xit.ens.modules.kkotalk.service.support.KkoTalkMaker; +import cokr.xit.ens.modules.kkotalk.service.support.KkoTalkRsltFetcher; +import cokr.xit.ens.modules.kkotalk.service.support.KkoTalkRsltProvider; +import cokr.xit.ens.modules.kkotalk.service.support.KkoTalkSender; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +// FIXME: 카카오톡 신규 추가 +@Slf4j +@Service +@RequiredArgsConstructor +public class KkoTalkService { + private final KkoTalkAcceptor kkoTalkAcceptor; + + private final KkoTalkMaker kkoTalkMaker; + private final KkoTalkSender kkoTalkSender; + private final KkoTalkRsltFetcher kkoTalkRsltFetcher; + private final KkoTalkRsltProvider kkoTalkRsltProvider; + + private final IKkoTalkApiService kkoTalkApi; + + private final SendMastRepository sendMastRepository; + private final SendDetailKkoMydocTokenHistRepository sendDetailKkoMydocTokenHistRepository; + private final OrgMngService orgMngService; + + + /** + * 접수 서비스 + * + * @param reqDTO + * @return + */ + @Transactional(propagation = Propagation.REQUIRES_NEW) + public EnsResponseVO accept(KkotalkDTO.KkoTalkAcceptReqDTO reqDTO) { + EnsResponseVO responseVO = null; + try { + responseVO = kkoTalkAcceptor.statBegin(reqDTO); + if (!EnsErrCd.OK.equals(responseVO.getErrCode())) + return responseVO; + + responseVO = kkoTalkAcceptor.execute(reqDTO); + } catch (EnsException e) { + responseVO = EnsResponseVO.errBuilder() + .errCode(e.getErrCd()) + .errMsg(e.getMessage()) + .build(); + } + + return responseVO; + } + + + /** + * 제작 서비스 + * + * @param sendMastId + * @return + */ + @Transactional(propagation = Propagation.SUPPORTS) + public EnsResponseVO remake(Long sendMastId) { + try { + SendMast sendMast = sendMastRepository.findById(sendMastId) + .orElseThrow(() -> new EnsException(EnsErrCd.SEND404, "일치하는 자료가 없습니다.")); + if (!PostSeCd.kkoMydoc.equals(sendMast.getPostSe())) + throw new EnsException(EnsErrCd.SEND404, String.format("%s 자료가 아닙니다.", PostSeCd.kkoMydoc.getCodeNm())); + if (!(StatCd.accept.equals(sendMast.getStatCd()) + || StatCd.makefail.equals(sendMast.getStatCd()))) + throw new EnsException(EnsErrCd.SEND404, "접수/제작실패(accept/makefail) 단계가 아닙니다."); + } catch (EnsException e) { + return EnsResponseVO.errBuilder() + .errCode(e.getErrCd()) + .errMsg(e.getMessage()) + .build(); + } + + kkoTalkMaker.statReady(Collections.singletonList(sendMastId)); + + + return this.make(sendMastId); + } + + /** + * (일괄)제작 서비스 + * + * @return + */ + @Transactional(propagation = Propagation.SUPPORTS) + public EnsResponseVO makeAll() { + List sendMastIds = sendMastRepository.findAllByPostSeAndStatCdIn(PostSeCd.kkoMydoc, Arrays.asList(StatCd.accept)) + .stream() + .map(row -> row.getSendMastId()) + .collect(Collectors.toList()); + if (sendMastIds.isEmpty()) + return EnsResponseVO.errBuilder() + .errCode(EnsErrCd.MAKE404) + .errMsg("\"접수(accept)\" 상태인 자료가 없습니다") + .build(); + + kkoTalkMaker.statReady(sendMastIds); + + List resultInfo = sendMastIds.stream() + .map(sendMastId -> this.make(sendMastId)) + .collect(Collectors.toList()); + + return EnsResponseVO.okBuilder().resultInfo(resultInfo).build(); + } + + private EnsResponseVO make(Long sendMastId) { + EnsResponseVO responseVO = kkoTalkMaker.statBegin(sendMastId); + if (!EnsErrCd.OK.equals(responseVO.getErrCode())) + return responseVO; + + Optional sendMast = sendMastRepository.findById(sendMastId); + try { +// +// responseVO = this.makeBillLink(sendMastId); +// if (!EnsErrCd.OK.equals(responseVO.getErrCode())) +// throw new EnsException(responseVO.getErrCode(), responseVO.getErrMsg()); + + responseVO = kkoTalkMaker.execute(sendMastId); + if (!EnsErrCd.OK.equals(responseVO.getErrCode())) + throw new EnsException(responseVO.getErrCode(), responseVO.getErrMsg()); + } catch (EnsException e) { + return EnsResponseVO.errBuilder() + .errCode(e.getErrCd()) + .errMsg(e.getMessage()) + .build(); + } + + return responseVO; + } + + /** + * 제작(청구서링크) 서비스 + * + * @param sendMastId + * @return + */ +// @Transactional(propagation = Propagation.SUPPORTS) +// public EnsResponseVO makeBillLink(Long sendMastId) { +// EnsResponseVO responseVO = kkoMydocMakerOfBillLink.execute(sendMastId); +// +// return responseVO; +// } + + + /** + * (대량)전송 서비스 + * + * @return + */ + @Transactional(propagation = Propagation.SUPPORTS) + public EnsResponseVO sendBulk(Long sendMastId) { + + try { + SendMast sendMast = sendMastRepository.findById(sendMastId) + .orElseThrow(() -> new EnsException(EnsErrCd.SEND404, "일치하는 자료가 없습니다.")); + if (!PostSeCd.kkoMydoc.equals(sendMast.getPostSe())) + throw new EnsException(EnsErrCd.SEND404, String.format("%s 자료가 아닙니다.", PostSeCd.kkoMydoc.getCodeNm())); + if (!StatCd.makeok.equals(sendMast.getStatCd())) + throw new EnsException(EnsErrCd.SEND404, "제작성공(makeok) 단계가 아닙니다."); + } catch (EnsException e) { + return EnsResponseVO.errBuilder() + .errCode(e.getErrCd()) + .errMsg(e.getMessage()) + .build(); + } + + List sendMastIds = Arrays.asList(sendMastId); + EnsResponseVO responseVO = kkoTalkSender.statReady(sendMastIds); + if (!EnsErrCd.OK.equals(responseVO.getErrCode())) + return responseVO; + + return this.send(sendMastIds); + } + + /** + * (대량)전송 서비스-일괄 + * + * @return + */ + @Transactional(propagation = Propagation.SUPPORTS) + public EnsResponseVO sendBulkAll() { + List sendMastIds = sendMastRepository.findAllByPostSeAndStatCdInAndSendDtBefore(PostSeCd.kkoMydoc, Arrays.asList(StatCd.makeok), LocalDateTime.now()) + .stream() + .map(row -> row.getSendMastId()) + .collect(Collectors.toList()); + if (sendMastIds.size() < 1) + return EnsResponseVO.errBuilder() + .errCode(EnsErrCd.SEND404) + .errMsg("발송시간이 경과한 자료 중 \"제작성공(makeok)\" 상태인 자료가 없습니다") + .build(); + + EnsResponseVO responseVO = kkoTalkSender.statReady(sendMastIds); + if (!EnsErrCd.OK.equals(responseVO.getErrCode())) + return responseVO; + + return this.send(sendMastIds); + } + + private EnsResponseVO send(List sendMastIds) { + List resultInfo = sendMastIds.stream() + .map(sendMastId -> { + kkoTalkSender.statBegin(sendMastId); + return sendMastId; + }) + .map(sendMastId -> kkoTalkSender.execute(sendMastId)) + .collect(Collectors.toList()); + + return EnsResponseVO.okBuilder().resultInfo(resultInfo).build(); + } + + + /** + * (대량)문서상태조회 서비스 + * + * @return + */ + @Transactional(propagation = Propagation.SUPPORTS) + public EnsResponseVO statBulk(Long sendMastId) { + try { + SendMast sendMast = sendMastRepository.findById(sendMastId) + .orElseThrow(() -> new EnsException(EnsErrCd.RSLT404, "일치하는 자료가 없습니다.")); + if (!PostSeCd.kkoMydoc.equals(sendMast.getPostSe())) + throw new EnsException(EnsErrCd.RSLT404, String.format("%s 자료가 아닙니다.", PostSeCd.kkoMydoc.getCodeNm())); + if (!Arrays.asList(StatCd.sendok, StatCd.sendcmplt, StatCd.open).stream() + .anyMatch(statCd -> statCd.equals(sendMast.getStatCd()))) + throw new EnsException(EnsErrCd.RSLT404, "전송성공/전송완료/열람중(sendok/sendcmplt/open) 단계가 아닙니다."); + } catch (EnsException e) { + return EnsResponseVO.errBuilder() + .errCode(e.getErrCd()) + .errMsg(e.getMessage()) + .build(); + } + List responseVOS = (List) this.stat(Arrays.asList(sendMastId)).getResultInfo(); + return responseVOS.get(0); + } + + /** + * (대량)문서상태조회 서비스-일괄 + * + * @return + */ + @Transactional(propagation = Propagation.SUPPORTS) + public EnsResponseVO statBulkAll() { + List sendMastIds = sendMastRepository.findAllByPostSeAndStatCdIn(PostSeCd.kkoMydoc, Arrays.asList(StatCd.sendok, StatCd.sendcmplt, StatCd.open)) + .stream() + .map(row -> row.getSendMastId()) + .collect(Collectors.toList()); + if (sendMastIds.size() < 1) + return EnsResponseVO.errBuilder() + .errCode(EnsErrCd.RSLT404) + .errMsg("\"전송성공/전송완료/열람중(sendok/sendcmplt/open)\" 상태인 자료가 없습니다") + .build(); + + return this.stat(sendMastIds); + } + + private EnsResponseVO stat(List sendMastIds) { + List resultInfo = sendMastIds.stream() + .map(sendMastId -> kkoTalkRsltFetcher.execute(sendMastId)) + .collect(Collectors.toList()); + + return EnsResponseVO.okBuilder().resultInfo(resultInfo).build(); + } + + + /** + * 토큰검증 서비스 + * + * @param externalDocumentUuid + * @param token + * @param externalDocumentUuid + * @return + */ + @Transactional + public EnsResponseVO tokenVerify(String orgCd, String envelopId, String token, String externalDocumentUuid) { + EnsResponseVO responseVO = null; + + try { + if (CmmnUtil.isEmpty(envelopId)) + throw new EnsException(EnsErrCd.ERR401, "문서식별번호(은)는 필수조건 입니다."); + if (CmmnUtil.isEmpty(token)) + throw new EnsException(EnsErrCd.ERR401, "토큰(은)는 필수조건 입니다."); + OrgMng orgMng = orgMngService.find(orgCd).getResultInfo(); + + KkotalkApiDTO.ValidTokenResponse resp = kkoTalkApi.validToken( + KkotalkApiDTO.ValidTokenRequest.builder() + .signguCode(orgMng.getOrgCd()) + .ffnlgCode("11") + .envelopeId(envelopId) + .token(token) + .build() + ); + + if (!StringUtils.isEmpty(resp.getErrorCode())){ + responseVO = EnsResponseVO.errRsltBuilder() + .errCode(EnsErrCd.ERR600) + .errMsg(String.format("%s %s", resp.getErrorCode(), resp.getErrorMessage())) + .resultInfo(resp) + .build(); + }else { + + responseVO = EnsResponseVO.okBuilder() + .resultInfo(resp) + .build(); + } + + // ResponseEntity resp = kkoMydocApi.token(orgMng.getKkoMdAccessToken(), documentBinderUuid, token); + // if (resp.getStatusCode() != HttpStatus.OK) + // throw new EnsException(EnsErrCd.ERR620, String.format("토큰검증 실패. 실패사유: %s %s", resp.getStatusCode().toString(), resp.getBody())); + + // Map mResponse = null; + // try { + // Gson gson = new GsonBuilder().disableHtmlEscaping().registerTypeAdapter(Map.class, new MapDeserailizer()).serializeNulls().create(); + // mResponse = gson.fromJson(resp.getBody(), Map.class); + // } catch (Exception e) { + // throw new EnsException(EnsErrCd.ERR505, String.format("토큰검증 응답데이터 파싱 실패. %s", resp.getBody())); + // } + + // String tokenUsedAt = (String) mResponse.get("token_status"); + // if ("USED".equals(tokenUsedAt)) { + // responseVO = EnsResponseVO.okBuilder() + // .resultInfo(mResponse) + // .build(); + // } else { + // String errorCode = (String) mResponse.get("error_code"); + // String errorMessage = (String) mResponse.get("error_message"); + // responseVO = EnsResponseVO.errRsltBuilder() + // .errCode(EnsErrCd.ERR600) + // .errMsg(String.format("%s %s", errorCode, errorMessage)) + // .resultInfo(mResponse) + // .build(); + // } + + + } catch (EnsException e) { + responseVO = EnsResponseVO.errBuilder() + .errCode(e.getErrCd()) + .errMsg(e.getMessage()) + .build(); + } catch (Exception e) { + responseVO = EnsResponseVO.errBuilder() + .errCode(EnsErrCd.ERR999) + .errMsg(e.getMessage()) + .build(); + } finally { + if (EnsErrCd.OK.equals(responseVO.getErrCode())) { + Map resultInfo = (Map) responseVO.getResultInfo(); + sendDetailKkoMydocTokenHistRepository.save(SendDetailKkoMydocTokenHist.builder() + .externalDocumentUuid(externalDocumentUuid) + .documentBinderUuid(envelopId) + .token(token) + .tokenStatus((String) resultInfo.get("token_status")) + .tokenUsedAt((Long) resultInfo.get("token_used_at")) + .docBoxSentAt((Long) resultInfo.get("doc_box_sent_at")) + .docBoxReceivedAt((Long) resultInfo.get("doc_box_received_at")) + .authenticatedAt((Long) resultInfo.get("authenticated_at")) + .userNotifiedAt((Long) resultInfo.get("user_notified_at")) + .payload((String) resultInfo.get("payload")) + .signedAt((String) resultInfo.get("signed_at")) + .build()); + } else { + sendDetailKkoMydocTokenHistRepository.save(SendDetailKkoMydocTokenHist.builder() + .externalDocumentUuid(externalDocumentUuid) + .documentBinderUuid(envelopId) + .token(token) + .error(FieldError.initBuilder() + .errorCode(responseVO.getErrCode().getCode()) + .errorMessage(responseVO.getErrMsg()) + .build()) + .build()); + } + } + + return responseVO; + } + + + /** + * 문서상태변경 서비스 + * -.문서열람 시 본 서비스를 호출하지 않으면 "최초열람시간(doc_box_read_at)"은 제공되지 않는다. + * + * @param envelopeId + * @return + */ + @Transactional(propagation = Propagation.SUPPORTS) + public EnsResponseVO readCmplt(String orgCd, String envelopeId) { + EnsResponseVO responseVO = null; + + try { + if (CmmnUtil.isEmpty(orgCd)) + throw new EnsException(EnsErrCd.ERR401, "기관코드(은)는 필수조건 입니다."); + if (CmmnUtil.isEmpty(envelopeId)) + throw new EnsException(EnsErrCd.ERR401, "문서식별번호(은)는 필수조건 입니다."); + OrgMng orgMng = orgMngService.find(orgCd).getResultInfo(); + + KkotalkApiDTO.KkotalkErrorDTO resp = kkoTalkApi.modifyStatus( + KkotalkDTO.EnvelopeId.builder() + .envelopeId(envelopeId) + .signguCode(orgMng.getOrgCd()) + .ffnlgCode("11") + + .build() + ); + + if (StringUtils.isEmpty(resp.getErrorCode())){ + responseVO = EnsResponseVO.okBuilder().build(); + }else{ + responseVO = EnsResponseVO.errBuilder() + .errCode(EnsErrCd.ERR600) + .errMsg(String.format("%s %s", resp.getErrorCode(), resp.getErrorMessage())) + .build(); + } + // ResponseEntity resp = kkoTalkApi.readCompleted(orgMng.getKkoMdAccessToken(), documentBinderUuid); + // + // + // if (resp.getStatusCode().equals(HttpStatus.NO_CONTENT)) { + // responseVO = EnsResponseVO.okBuilder().build(); + // } else { + // Map mResponse = null; + // try { + // Gson gson = new GsonBuilder().disableHtmlEscaping().create(); + // mResponse = gson.fromJson(resp.getBody(), Map.class); + // } catch (Exception e) { + // throw new EnsException(EnsErrCd.ERR505, String.format("문서상태변경 응답데이터 파싱 실패. %s", resp.getBody())); + // } + // String errorCode = (String) mResponse.get("error_code"); + // String errorMessage = (String) mResponse.get("error_message"); + // responseVO = EnsResponseVO.errBuilder() + // .errCode(EnsErrCd.ERR600) + // .errMsg(String.format("%s %s", errorCode, errorMessage)) + // .build(); + // } + + } catch (EnsException e) { + responseVO = EnsResponseVO.errBuilder() + .errCode(e.getErrCd()) + .errMsg(e.getMessage()) + .build(); + } catch (Exception e) { + responseVO = EnsResponseVO.errBuilder() + .errCode(EnsErrCd.ERR999) + .errMsg(e.getMessage()) + .build(); + } + + return responseVO; + } + + + /** + * 전송결과 제공 + * + * @param sendMastId + * @return + */ + @Transactional(propagation = Propagation.SUPPORTS) + public EnsResponseVO sendResultProvide(Long sendMastId) { + + return kkoTalkRsltProvider.execute(sendMastId); + } + + +} diff --git a/src/main/java/cokr/xit/ens/modules/kkotalk/service/strategy/TmpltMngStrategyKkoTalk.java b/src/main/java/cokr/xit/ens/modules/kkotalk/service/strategy/TmpltMngStrategyKkoTalk.java new file mode 100644 index 0000000..f5633ff --- /dev/null +++ b/src/main/java/cokr/xit/ens/modules/kkotalk/service/strategy/TmpltMngStrategyKkoTalk.java @@ -0,0 +1,103 @@ +package cokr.xit.ens.modules.kkotalk.service.strategy; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +import javax.validation.ConstraintViolation; +import javax.validation.Validation; +import javax.validation.Validator; + +import org.mapstruct.factory.Mappers; +import org.springframework.stereotype.Component; + +import cokr.xit.ens.core.exception.EnsException; +import cokr.xit.ens.core.exception.code.EnsErrCd; +import cokr.xit.ens.modules.common.ctgy.sys.mng.domain.OrgMng; +import cokr.xit.ens.modules.common.ctgy.sys.mng.domain.repository.OrgMngRepository; +import cokr.xit.ens.modules.common.ctgy.sys.mng.domain.repository.TmpltMngRepository; +import cokr.xit.ens.modules.common.ctgy.sys.mng.model.TmpltMngDTO; +import cokr.xit.ens.modules.common.ctgy.sys.mng.model.TmpltMngSearchDTO; +import cokr.xit.ens.modules.common.ctgy.sys.mng.service.strategy.TmpltMngStrategyTemplate; +import cokr.xit.ens.modules.kkotalk.domain.TmpltMngKkoTalk; +import cokr.xit.ens.modules.kkotalk.domain.repository.TmpltMngKkoTalkRepository; +import cokr.xit.ens.modules.kkotalk.model.TmpltMngKkoTalkDTO; +import cokr.xit.ens.modules.kkotalk.model.struct.TmpltMngKkoTalkMapper; +import lombok.RequiredArgsConstructor; + +// FIXME: 카카오톡 신규 추가 +@Component("tmpltMngStrategy_kkotalk") +@RequiredArgsConstructor +public class TmpltMngStrategyKkoTalk extends TmpltMngStrategyTemplate, TmpltMngKkoTalkDTO> { + + private final OrgMngRepository orgMngRepository; + private final TmpltMngRepository tmpltMngRepository; + private final TmpltMngKkoTalkRepository tmpltMngKkoTalkRepository; + private final TmpltMngKkoTalkMapper kkoTalkMapper = Mappers.getMapper(TmpltMngKkoTalkMapper.class); +// private final RedisTemplate redisTemplate; + + @Override + public List findAll(TmpltMngSearchDTO tmpltMngSearchDTO) { + return tmpltMngKkoTalkRepository.findAllDtoBySearchDTO(tmpltMngSearchDTO); + } + + @Override + public Optional find(String orgCd, String tmpltCd) { + return tmpltMngKkoTalkRepository.findDtoByOrgCdAndTmpltCd(orgCd, tmpltCd); + +// String key = super.pushCache(redisTemplate, orgCd, tmpltCd, tmpltMngKkoMydocRepository); +// HashOperations hashOperations = redisTemplate.opsForHash(); +// return Optional.ofNullable(hashOperations.entries(KEY).get(key)); + } + + + @Override + protected TmpltMngKkoTalkDTO validate(Map params) { + try { + TmpltMngKkoTalkDTO dto = mapper.convertValue(params, TmpltMngKkoTalkDTO.class); + + final Validator validator = Validation.buildDefaultValidatorFactory().getValidator(); + Set> list = validator.validate(dto); + if (!list.isEmpty()) { + throw new EnsException(EnsErrCd.ERR410, "유효하지 않은 요청 값 입니다.", list.stream() + .map(row -> String.format("%s [ %s ]", row.getMessageTemplate(), row.getPropertyPath())) + .collect(Collectors.toList()) + ); + } + return dto; + }catch (IllegalArgumentException e){ + List fields = Arrays.stream(TmpltMngDTO.class.getDeclaredFields()).map(row->row.getName()).collect(Collectors.toList()); + fields.addAll(Arrays.stream(TmpltMngKkoTalkDTO.class.getDeclaredFields()).map(row->row.getName()).collect(Collectors.toList())); + throw new EnsException(EnsErrCd.ERR403, "유효하지 않은 파라미터 입니다. 사용가능한 파라미터는 resultInfo 를 참고하시기 바랍니다.", fields); + } + } + + @Override + protected void addProc(TmpltMngKkoTalkDTO dto) { + OrgMng orgMng = orgMngRepository.findById(dto.getOrgCd()).orElseThrow(() -> new EnsException(EnsErrCd.ERR404, String.format("기관코드(%s)가 일치하는 자료가 없습니다.", dto.getOrgCd()))); + tmpltMngRepository.findFetchByOrgCdAndTmpltCd(dto.getOrgCd(), dto.getTmpltCd()) + .ifPresent(tmpltMng -> { + throw new EnsException(EnsErrCd.ERR540, String.format("이미 등록된 템플릿코드[기관코드 %s 템플릿코드 %s] 입니다.", tmpltMng.getOrgMng().getOrgCd(), tmpltMng.getTmpltCd())); + }); + + TmpltMngKkoTalk tmpltMng = kkoTalkMapper.toEntity(dto); + tmpltMng.setOrgMng(orgMng); + tmpltMng.setRegistId("ENS_SYS"); + tmpltMngKkoTalkRepository.save(tmpltMng); + } + + @Override + protected void modifyProc(TmpltMngKkoTalkDTO dto) { + TmpltMngKkoTalk tmpltMng = tmpltMngKkoTalkRepository.findFetchByOrgCdAndTmpltCd((dto).getOrgCd(), (dto).getTmpltCd()) + .orElseThrow(() -> new EnsException(EnsErrCd.ERR404, String.format("기관코드(%s) 및 템플릿코드(%s)가 일치하는 자료가 없습니다.", dto.getOrgCd(), dto.getTmpltCd()))); + if(!"Y".equals(tmpltMng.getUseYn())) + throw new EnsException(EnsErrCd.ERR402, "\"미사용\" 상태의 템플릿은 수정 할 수 없습니다."); + kkoTalkMapper.updateFromDto(dto, tmpltMng); + tmpltMng.setUpdId("ENS_SYS"); + +// super.deleteCache(redisTemplate, dto.getOrgCd(), dto.getTmpltCd()); + } +} diff --git a/src/main/java/cokr/xit/ens/modules/kkotalk/service/IKkotalkEltrcDocService.java b/src/main/java/cokr/xit/ens/modules/kkotalk/service/support/IKkoTalkApiService.java similarity index 95% rename from src/main/java/cokr/xit/ens/modules/kkotalk/service/IKkotalkEltrcDocService.java rename to src/main/java/cokr/xit/ens/modules/kkotalk/service/support/IKkoTalkApiService.java index e693444..d39bd8e 100644 --- a/src/main/java/cokr/xit/ens/modules/kkotalk/service/IKkotalkEltrcDocService.java +++ b/src/main/java/cokr/xit/ens/modules/kkotalk/service/support/IKkoTalkApiService.java @@ -1,4 +1,4 @@ -package cokr.xit.ens.modules.kkotalk.service; +package cokr.xit.ens.modules.kkotalk.service.support; import cokr.xit.ens.modules.kkotalk.model.KkotalkApiDTO; import cokr.xit.ens.modules.kkotalk.model.KkotalkDTO; @@ -17,7 +17,7 @@ import cokr.xit.ens.modules.kkotalk.model.KkotalkDTO; * * */ -public interface IKkotalkEltrcDocService { +public interface IKkoTalkApiService { /** *
@@ -48,7 +48,7 @@ public interface IKkotalkEltrcDocService {
      * 
* @param reqDTO KkotalkDTO.EnvelopeId */ - void modifyStatus(final KkotalkDTO.EnvelopeId reqDTO); + KkotalkApiDTO.KkotalkErrorDTO modifyStatus(final KkotalkDTO.EnvelopeId reqDTO); /** @@ -91,4 +91,3 @@ public interface IKkotalkEltrcDocService { //KkotalkApiDTO.ValidTokenResponse findKkotalkReadyAndMblPage(KkotalkApiDTO.ValidTokenRequest reqDTO); } - diff --git a/src/main/java/cokr/xit/ens/modules/kkotalk/service/support/KkoTalkAcceptor.java b/src/main/java/cokr/xit/ens/modules/kkotalk/service/support/KkoTalkAcceptor.java new file mode 100644 index 0000000..8459294 --- /dev/null +++ b/src/main/java/cokr/xit/ens/modules/kkotalk/service/support/KkoTalkAcceptor.java @@ -0,0 +1,270 @@ +package cokr.xit.ens.modules.kkotalk.service.support; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; + +import javax.validation.ConstraintViolation; +import javax.validation.Validation; +import javax.validation.Validator; + +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +import cokr.xit.ens.core.aop.EnsResponseVO; +import cokr.xit.ens.core.exception.EnsException; +import cokr.xit.ens.core.exception.code.EnsErrCd; +import cokr.xit.ens.core.utils.CmmnUtil; +import cokr.xit.ens.core.utils.DateUtil; +import cokr.xit.ens.core.utils.IdGenerator; +import cokr.xit.ens.core.utils.MapDeserailizer; +import cokr.xit.ens.core.utils.crypto.AES256; +import cokr.xit.ens.core.utils.crypto.Crypto; +import cokr.xit.ens.modules.common.biztmplt.EnsPhaseProcSupport; +import cokr.xit.ens.modules.common.code.PostSeCd; +import cokr.xit.ens.modules.common.code.StatCd; +import cokr.xit.ens.modules.common.code.VenderCd; +import cokr.xit.ens.modules.common.ctgy.intgrnbill.support.entity.Bill; +import cokr.xit.ens.modules.common.ctgy.intgrnbill.support.entity.repository.BillRepository; +import cokr.xit.ens.modules.common.ctgy.mblpage.domain.SendDetailMblPage; +import cokr.xit.ens.modules.common.ctgy.mblpage.domain.repository.SendDetailMblPageRepository; +import cokr.xit.ens.modules.common.ctgy.sys.mng.domain.OrgMng; +import cokr.xit.ens.modules.common.ctgy.sys.mng.service.OrgMngService; +import cokr.xit.ens.modules.common.ctgy.sys.mng.service.TmpltMngService; +import cokr.xit.ens.modules.common.domain.SendMast; +import cokr.xit.ens.modules.common.domain.repository.SendMastRepository; +import cokr.xit.ens.modules.kkomydoc.model.config.XitProperty; +import cokr.xit.ens.modules.kkotalk.mapper.IKkoTalkMapper; +import cokr.xit.ens.modules.kkotalk.model.KkotalkDTO; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +// FIXME: 카카오톡 신규 추가 +@Slf4j +@Component +@RequiredArgsConstructor +public class KkoTalkAcceptor implements EnsPhaseProcSupport, KkotalkDTO.KkoTalkAcceptReqDTO> { + + private final SendMastRepository sendMastRepository; + private final IKkoTalkMapper kkoTalkMapper; + private final SendDetailMblPageRepository sendDetailMblPageRepository; + private final BillRepository billRepository; + private final OrgMngService orgMngService; + private final TmpltMngService tmpltMngService; + + private Gson gson = new GsonBuilder().registerTypeAdapter(Map.class, new MapDeserailizer()).disableHtmlEscaping().create(); + + @Override + public EnsResponseVO statReady(List reqDTOs) { + log.info("no process"); + return null; + } + + @Override + public EnsResponseVO statBegin(KkotalkDTO.KkoTalkAcceptReqDTO reqDTO) { + final Validator validator = Validation.buildDefaultValidatorFactory().getValidator(); + Set> list = validator.validate(reqDTO); + if (!list.isEmpty()) { + return EnsResponseVO.errRsltBuilder() + .errCode(EnsErrCd.ACPT402) + .errMsg("유효하지 않은 요청값 입니다.") + .resultInfo(list.stream() + .map(row -> String.format("%s [ %s ]", row.getMessageTemplate(), row.getPropertyPath())) + .collect(Collectors.toList())) + .build(); + } + List expiredDateValidateList = expiredDateVaildate(reqDTO); + if (!expiredDateValidateList.isEmpty()) { + return EnsResponseVO.errRsltBuilder() + .errCode(EnsErrCd.ACPT402) + .errMsg("유효하지 않은 유효기한 입니다. 자료의 유효기한은 마감일시 이내이어야 합니다.") + .resultInfo(expiredDateValidateList) + .build(); + } + EnsResponseVO orgMng = orgMngService.find(reqDTO.getOrg_cd()); + if (!EnsErrCd.OK.equals(orgMng.getErrCode())) + return EnsResponseVO.errRsltBuilder() + .errCode(EnsErrCd.ACPT404) + .errMsg("일치하는 기관정보가 없습니다.") + .resultInfo(orgMng.getErrMsg()) + .build(); + EnsResponseVO tmpltMng = tmpltMngService.find(reqDTO.getOrg_cd(), reqDTO.getTmplt_cd(), "kkotalk"); + if (!EnsErrCd.OK.equals(tmpltMng.getErrCode())) + return EnsResponseVO.errRsltBuilder() + .errCode(EnsErrCd.ACPT404) + .errMsg("일치하는 템플릿정보가 없습니다.") + .resultInfo(tmpltMng.getErrMsg()) + .build(); + + return EnsResponseVO.okBuilder().build(); + } + + private List expiredDateVaildate(KkotalkDTO.KkoTalkAcceptReqDTO reqDTO) { + List result = new ArrayList<>(); + + AtomicInteger i = new AtomicInteger(); + reqDTO.getDocuments().forEach(document -> { + if (!CmmnUtil.isEmpty(document)) { + if (!CmmnUtil.isEmpty(document.getReadExpiresAt())) { + String expDate = DateUtil.getTimeOfTimeT(document.getReadExpiresAt(), "yyyyMMddHHmmss"); + int sec = DateUtil.secByFromBetweenTo(DateUtil.toLocalDateTime(expDate), DateUtil.toLocalDateTime(reqDTO.getClose_dt())); + if (sec < 0) + result.add(String.format("마감일시보다 \"처리마감시간\"이 느립니다. [ document[%d].acpt_data.kko_talk.read_expires_at ]", i.get())); + } + if (!CmmnUtil.isEmpty(document.getReviewExpiresAt())) { + String expDate = DateUtil.getTimeOfTimeT(document.getReviewExpiresAt(), "yyyyMMddHHmmss"); + int sec = DateUtil.secByFromBetweenTo(DateUtil.toLocalDateTime(expDate), DateUtil.toLocalDateTime(reqDTO.getClose_dt())); + if (sec < 0) + result.add(String.format("마감일시보다 \"처리마감시간\"이 느립니다. [ document[%d].acpt_data.kko_talk.read_expires_at ]", i.get())); + } + } + i.getAndIncrement(); + }); + + return result; + } + + @Override + @Transactional(propagation = Propagation.REQUIRES_NEW) + public EnsResponseVO execute(KkotalkDTO.KkoTalkAcceptReqDTO reqDTO) throws EnsException { + AES256 aes256 = new AES256(Crypto.AES256.getKey()); + EnsResponseVO responseVO = null; + try { + SendMast sendMast = SendMast.builder() + .vender(VenderCd.valueOfEnum(reqDTO.getVender())) + .postSe(PostSeCd.kkoTalk) + .orgCd(reqDTO.getOrg_cd()) + .tmpltCd(reqDTO.getTmplt_cd()) + .postBundleTitle(reqDTO.getPost_bundle_title()) + .statCd(StatCd.accept) + .sendDt(DateUtil.toLocalDateTime(reqDTO.getSend_dt())) + .sendCnt(reqDTO.getDocuments().size()) + .closeDt(DateUtil.toLocalDateTime(reqDTO.getClose_dt())) + .build(); + sendMastRepository.save(sendMast); + + + Gson gson = new GsonBuilder().disableHtmlEscaping().create(); + List sendDetails = new ArrayList<>(); + List> sendDetailMblPages = new ArrayList<>(); + List bills = new ArrayList<>(); + final String prefixBillUid = sendMast.getPostSe().getCode() + "-" + IdGenerator.getCurrentTimeSec(); + AtomicInteger i = new AtomicInteger(); + reqDTO.getDocuments().forEach(document -> { + i.getAndIncrement(); + try { + + Bill bill = null; + if (!CmmnUtil.isEmpty(document.getXit_property()) + && !CmmnUtil.isEmpty(document.getXit_property().getBill_acpt_data())) { +// if (document.getXit_property().getBill_acpt_data().getUse_bill_uid()) { + if (!CmmnUtil.isEmpty(document.getXit_property().getBill_acpt_data().getBillUid())) { + bill = billRepository.findByBillUid(document.getXit_property().getBill_acpt_data().getBillUid()) + .orElseThrow(() -> new RuntimeException("등록된 청구서가 없습니다.")); + } else { + bill = Bill.builder() +// .billId(document.getXit_property().getBill_acpt_data().getBill_id()) + .billId(null) + .billUid(IdGenerator.getShortUUID(prefixBillUid)) + .billerUserKey(document.getXit_property().getBill_acpt_data().getBillerUserKey()) + .billSeCd(document.getXit_property().getBill_acpt_data().getBillSe()) + .orgMng(OrgMng.builder().orgCd(reqDTO.getOrg_cd()).build()) +// .docBillKko(CmmnUtil.isEmpty(document.getXit_property().getBill_acpt_data().getBill_kko()) ? null : gson.toJson(document.getXit_property().getBill_acpt_data().getBill_kko())) +// .docBillNv(CmmnUtil.isEmpty(document.getXit_property().getBill_acpt_data().getBill_nv()) ? null : gson.toJson(document.getXit_property().getBill_acpt_data().getBill_nv())) + .build(); + } + bills.add(bill); + } + + + XitProperty xitProperty = Optional.ofNullable(document.getXit_property()).orElse(XitProperty.builder().build()); + + + KkotalkDTO.SendDetailKkoTalkDTO sendDetail = KkotalkDTO.SendDetailKkoTalkDTO.builder() + .sendMastId(sendMast.getSendMastId()) + .title(document.getTitle()) + .link(document.getContent().getLink()) + .hash(document.getHash()) + .guide(document.getGuide()) + .payload(document.getPayload()) + .readExpiresAt(document.getReadExpiresAt()) + .reviewExpiresAt(document.getReviewExpiresAt()) + .useNonPersonalizedNotification(document.getUseNonPersonalizedNotification()) + .ci(document.getCi()) + .phoneNumber(document.getPhoneNumber()) + .name(document.getName()) + .birthday(document.getBirthday()) + .externalId(document.getExternalId()) + .mkBillUseYn(CmmnUtil.isEmpty(bill) ? "N" : "Y") +// .mkBillUid(bill == null ? null : bill.getBillUid()) + .billUid(Objects.requireNonNull(bill).getBillUid()) +// .mkJid(xitProperty.getJid()) + .mkJid(aes256.encrypt(xitProperty.getJid())) + .mkTmpltMsgJsonData(CmmnUtil.isEmpty(xitProperty.getTmplt_msg_data()) ? null : gson.toJson(xitProperty.getTmplt_msg_data())) + .build(); + sendDetails.add(sendDetail); + + if (!(CmmnUtil.isEmpty(document.getXit_property()) || CmmnUtil.isEmpty(document.getXit_property().getMbl_page_data()))) { + Map mMblPage = new HashMap<>(); + mMblPage.put("sendDetail", sendDetail); + mMblPage.put("details", gson.toJson(document.getXit_property().getMbl_page_data())); + sendDetailMblPages.add(mMblPage); + } + + } catch (Exception e) { + throw new EnsException(EnsErrCd.ACPT500, String.format("%d번째 데이터 접수처리 중 오류 발생. 실패사유: %s", i.get(), e.getMessage())); + } + }); + if (CmmnUtil.isEmpty(sendDetails)) + throw new EnsException(EnsErrCd.ACPT410, "발송상세 자료가 없습니다."); + + + billRepository.saveAll(bills.stream() + .filter(row -> CmmnUtil.isEmpty(row.getBillId())) + .collect(Collectors.toList()) + ); + + // kkotalk 발송상세 저장 + sendDetails.forEach(kkoTalkMapper::saveSndDtlKkoTalk); + + if (!sendDetailMblPages.isEmpty()) + + sendDetailMblPageRepository.saveAll( + sendDetailMblPages.stream() + .map(data -> { + KkotalkDTO.SendDetailKkoTalkDTO sendDetail = (KkotalkDTO.SendDetailKkoTalkDTO) data.get("sendDetail"); + return SendDetailMblPage.builder() + .postSe(PostSeCd.kkoTalk.getCode()) + .sendDetailId(sendDetail.getSendDetailId()) + .details((String) data.get("details")) + .build(); + }) + .collect(Collectors.toList()) + ); + + + Map resultInfo = new HashMap<>(); + resultInfo.put("sendMastId", sendMast.getSendMastId()); + resultInfo.put("sendDetails", sendDetails); + responseVO = EnsResponseVO.okBuilder().resultInfo(resultInfo).build(); + } catch (EnsException e) { + throw e; + } catch (Exception e) { + throw new EnsException(EnsErrCd.ACPT500, String.format("오류가 발생하여 접수에 실패 했습니다. 실패사유: %s", e.getMessage())); + } + + return responseVO; + } + + +} diff --git a/src/main/java/cokr/xit/ens/modules/kkotalk/service/KkotalkEltrcDocService.java b/src/main/java/cokr/xit/ens/modules/kkotalk/service/support/KkoTalkApiService.java similarity index 97% rename from src/main/java/cokr/xit/ens/modules/kkotalk/service/KkotalkEltrcDocService.java rename to src/main/java/cokr/xit/ens/modules/kkotalk/service/support/KkoTalkApiService.java index 6410882..c394c64 100644 --- a/src/main/java/cokr/xit/ens/modules/kkotalk/service/KkotalkEltrcDocService.java +++ b/src/main/java/cokr/xit/ens/modules/kkotalk/service/support/KkoTalkApiService.java @@ -1,4 +1,4 @@ -package cokr.xit.ens.modules.kkotalk.service; +package cokr.xit.ens.modules.kkotalk.service.support; import java.util.ArrayList; import java.util.Collections; @@ -41,11 +41,12 @@ import lombok.extern.slf4j.Slf4j; * * */ +// FIXME: 카카오톡 신규 추가 @Slf4j @RequiredArgsConstructor @Component -public class KkotalkEltrcDocService implements - IKkotalkEltrcDocService { +public class KkoTalkApiService implements + IKkoTalkApiService { @Value("${contract.kakao.talk.host}") private String HOST; @@ -140,12 +141,12 @@ public class KkotalkEltrcDocService implements * @param reqDTO KkopayDocAttrDTO.EnvelopeId */ @Override - public void modifyStatus(final KkotalkDTO.EnvelopeId reqDTO){ + public KkotalkApiDTO.KkotalkErrorDTO modifyStatus(final KkotalkDTO.EnvelopeId reqDTO){ validate(reqDTO.getEnvelopeId(), null); final String url = HOST + API_MODIFY_STATUS[0].replace(ENVELOPE_ID, reqDTO.getEnvelopeId()); - webClient.exchangeKkotalk(url, HttpMethod.valueOf(API_MODIFY_STATUS[1]), null, Void.class, getRlaybsnmInfo(reqDTO)); + return webClient.exchangeKkotalk(url, HttpMethod.valueOf(API_MODIFY_STATUS[1]), null, KkotalkApiDTO.KkotalkErrorDTO.class, getRlaybsnmInfo(reqDTO)); } /** diff --git a/src/main/java/cokr/xit/ens/modules/kkotalk/service/support/KkoTalkMaker.java b/src/main/java/cokr/xit/ens/modules/kkotalk/service/support/KkoTalkMaker.java new file mode 100644 index 0000000..bf39942 --- /dev/null +++ b/src/main/java/cokr/xit/ens/modules/kkotalk/service/support/KkoTalkMaker.java @@ -0,0 +1,259 @@ +package cokr.xit.ens.modules.kkotalk.service.support; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +import cokr.xit.ens.core.aop.EnsResponseVO; +import cokr.xit.ens.core.exception.EnsException; +import cokr.xit.ens.core.exception.code.EnsErrCd; +import cokr.xit.ens.core.monitor.slack.event.MonitorEvent; +import cokr.xit.ens.core.utils.CmmnUtil; +import cokr.xit.ens.core.utils.IdGenerator; +import cokr.xit.ens.core.utils.MapDeserailizer; +import cokr.xit.ens.core.utils.crypto.AES256; +import cokr.xit.ens.core.utils.crypto.Crypto; +import cokr.xit.ens.core.utils.crypto.SHA256; +import cokr.xit.ens.modules.common.biztmplt.MakeProcTemplate; +import cokr.xit.ens.modules.common.code.PostSeCd; +import cokr.xit.ens.modules.common.code.StatCd; +import cokr.xit.ens.modules.common.ctgy.nicedici.service.NiceDiCiService; +import cokr.xit.ens.modules.common.ctgy.sys.mng.domain.OrgMng; +import cokr.xit.ens.modules.common.ctgy.sys.mng.service.OrgMngService; +import cokr.xit.ens.modules.common.domain.SendMast; +import cokr.xit.ens.modules.common.domain.repository.SendMastRepository; +import cokr.xit.ens.modules.common.domain.support.FieldError; +import cokr.xit.ens.modules.common.event.SendMastStatUpdateEvent; +import cokr.xit.ens.modules.common.monitor.MessageByPhase; +import cokr.xit.ens.modules.kkotalk.mapper.IKkoTalkMapper; +import cokr.xit.ens.modules.kkotalk.model.KkotalkDTO; +import cokr.xit.ens.modules.kkotalk.model.TmpltMngKkoTalkDTO; +import cokr.xit.ens.modules.kkotalk.service.strategy.TmpltMngStrategyKkoTalk; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import lombok.val; + +// FIXME: 카카오톡 신규 추가 +@Slf4j +@Component +@RequiredArgsConstructor +public class KkoTalkMaker extends MakeProcTemplate { + private final ApplicationEventPublisher applicationEventPublisher; + private final SendMastRepository sendMastRepository; + //private final SendDetailKkoMydocRepository sendDetailKkoMydocRepository; + private final IKkoTalkMapper kkoTalkMapper; + private final OrgMngService orgMngService; + private final TmpltMngStrategyKkoTalk tmpltMngService; + private final NiceDiCiService niceDiCiService; + + + @Override + @Transactional(propagation = Propagation.REQUIRES_NEW) + public EnsResponseVO execute(Long sendMastId) { + AES256 aes256 = new AES256(Crypto.AES256.getKey()); + SendMast sendMast = null; + List sendDetails = null; + EnsResponseVO respVO = null; + try { + if (CmmnUtil.isEmpty(sendMastId)) + throw new EnsException(EnsErrCd.MAKE410, "발송마스터ID(은)는 필수조건 입니다."); + + sendMast = sendMastRepository.findById(sendMastId) + .orElseThrow(() -> new EnsException(EnsErrCd.MAKE404, String.format("일치하는 발송마스터 자료가 없습니다. [ sendMastId %s ]", sendMastId))); + // kkotalk 발송상세 조회 + sendDetails = kkoTalkMapper.findAllBySendMastId(sendMastId); + SendMast finalSendMast = sendMast; + OrgMng orgMng = orgMngService.find(sendMast.getOrgCd()).getResultInfo(); +// TmpltMngKkoMydoc tmpltMngDTO = tmpltMngRepository.findFetchByOrgCdAndTmpltCdAndUseYn(sendMast.getOrgCd(), sendMast.getTmpltCd(), "Y") +// .orElseThrow(() -> new EnsException(EnsErrCd.MAKE404, String.format("일치하는 템플릿 자료가 없거나 미사용 상태의 템플릿 입니다. [ sendMastId %s orgCd %s TmpltCd %s ]", sendMastId, finalSendMast.getOrgCd(), finalSendMast.getTmpltCd()))); + TmpltMngKkoTalkDTO tmpltMngDTO = tmpltMngService.find(sendMast.getOrgCd(), sendMast.getTmpltCd()) + .orElseThrow(() -> new EnsException(EnsErrCd.MAKE404, String.format("일치하는 템플릿 자료가 없습니다. [ sendMastId %s orgCd %s TmpltCd %s ]", sendMastId, finalSendMast.getOrgCd(), finalSendMast.getTmpltCd()))); + if (!"Y".equals(tmpltMngDTO.getUseYn())) + throw new EnsException(EnsErrCd.MAKE521, String.format("미사용 상태의 템플릿 입니다. [ sendMastId %s orgCd %s TmpltCd %s ]", sendMastId, finalSendMast.getOrgCd(), finalSendMast.getTmpltCd())); + + + Map mCi = new HashMap<>(); + if ("Y".equals(tmpltMngDTO.getCiTransUseYn())) { + List jids = sendDetails.stream() + // .filter(row -> "Y".equals(row.getMkCiTransUseYn())) + .filter(row -> !CmmnUtil.isEmpty(row.getMkJid())) +// .map(row -> row.getMkJid()) + .map(row -> aes256.decrypt(row.getMkJid())) + .collect(Collectors.toList()); + if (!CmmnUtil.isEmpty(jids)) { + val ciResponseVO = niceDiCiService.ci(orgMng.getNiceCdSiteCode(), orgMng.getNiceCdSitePw(), orgMng.getNiceCdClientId(), orgMng.getNiceCdClientSercet(), jids); + if (!EnsErrCd.OK.equals(ciResponseVO.getErrCode())) + throw new EnsException(EnsErrCd.MAKE513, String.format("%s %s", ciResponseVO.getErrCode(), ciResponseVO.getErrMsg())); + + final List responseVOList = (List)ciResponseVO.getResultInfo(); + mCi = responseVOList.stream() + .map(vo -> { + Map resultInfo = (Map) vo.getResultInfo(); + Map m = new HashMap<>(); + m.put("key", resultInfo.get("jid")); + m.put("value", CmmnUtil.isEmpty(resultInfo.get("ci")) ? "" : resultInfo.get("ci")); + return m; + }) + .collect(Collectors.toMap(m -> m.get("key"), m -> m.get("value"), (k1, k2) -> k1)); + } + } + + Map finalMCi = mCi; + Optional cntSuccess = sendDetails.stream() + .map(row -> { + try { + + row.setTitle(Optional.ofNullable(tmpltMngDTO.getTitle()) + .orElseThrow(() -> new EnsException(EnsErrCd.MAKE521, String.format("title 생성에 실패 했습니다. [ orgCd %s tmpltCd %s]", tmpltMngDTO.getOrgCd(), tmpltMngDTO.getTmpltCd())))); + + if ("Y".equals(tmpltMngDTO.getTmpltCsInfoUseYn())) { + row.setPhoneNumber(Optional.ofNullable(tmpltMngDTO.getCsNumber()) + .orElseThrow(() -> new EnsException(EnsErrCd.MAKE521, + String.format("phoneNumber 생성에 실패 했습니다. [ orgCd %s tmpltCd %s]", + tmpltMngDTO.getOrgCd(), tmpltMngDTO.getTmpltCd())))); + + + row.setName(Optional.ofNullable(tmpltMngDTO.getCsName()) + .orElseThrow(() -> new EnsException(EnsErrCd.MAKE521, + String.format("name 생성에 실패 했습니다. [ orgCd %s tmpltCd %s]", + tmpltMngDTO.getOrgCd(), tmpltMngDTO.getTmpltCd())))); + } + //if ("Y".equals(tmpltMngDTO.getTmpltMsgUseYn())) { + // boolean usePryMsg = CmmnUtil.isEmpty(tmpltMngDTO.getPryMessage()) ? false : true; + // row.setPropMessage(Optional.ofNullable(this.msgTmplateToMessage(usePryMsg, tmpltMngDTO, row.getMkTmpltMsgJsonData())) + // .orElseThrow(() -> new EnsException(EnsErrCd.MAKE521, String.format("propMessage 생성에 실패 했습니다. [ orgCd %s tmpltCd %s tmpltMsgJsonData %s ]", tmpltMngDTO.getOrgCd(), tmpltMngDTO.getTmpltCd(), row.getMkTmpltMsgJsonData())))); + //} + + if (CmmnUtil.isEmpty(row.getExternalId())) + row.setExternalId(Optional.ofNullable(IdGenerator.getUUID()) + .orElseThrow(() -> new EnsException(EnsErrCd.MAKE521, String.format("propExternalDocumentUuid 생성에 실패 했습니다")))); + //if (CmmnUtil.isEmpty(row.getPropExternalDocumentUuid())) + // row.setPropExternalDocumentUuid(Optional.ofNullable(IdGenerator.getUUID()) + // .orElseThrow(() -> new EnsException(EnsErrCd.MAKE521, String.format("propExternalDocumentUuid 생성에 실패 했습니다")))); + + if (CmmnUtil.isEmpty(row.getHash())) + row.setHash(Optional.ofNullable(this.createHash(row)) + .orElseThrow(() -> new EnsException(EnsErrCd.MAKE521, String.format("hash 생성에 실패 했습니다.")))); + + //if ("Y".equals(tmpltMngDTO.getCiTransUseYn()) && !CmmnUtil.isEmpty(row.getMkJid())) + // row.setRecvCi(Optional.ofNullable(finalMCi.get(aes256.decrypt(row.getMkJid()).replaceAll("[^0-9]", ""))) + // .map(ci -> CmmnUtil.isEmpty(ci) ? null : ci) + // .orElseThrow(() -> new EnsException(EnsErrCd.MAKE610, String.format("recvCi 생성에 실패 했습니다. 주민번호에 대한 CI 값이 없습니다. [ jid %s ]", aes256.decrypt(row.getMkJid()))))); + + //row.setError(FieldError.initBuilder().build()); + return 1; + } catch (EnsException e) { + row.setErrorCode(e.getErrCd().getCode()); + row.setErrorMessage(e.getMessage()); + //row.setError(FieldError.initBuilder() + // .errorCode(e.getErrCd().getCode()) + // .errorMessage(e.getMessage()) + // .build()); + return 0; + } + }) + .reduce(Integer::sum); + + + + if (cntSuccess.get() > 0) + respVO = EnsResponseVO.okBuilder().resultInfo(String.format("총 %d건 중 %d건 제작 성공. [ sendMastId %s ]", sendDetails.size(), cntSuccess.get(), sendMastId)).build(); + else + throw new EnsException(EnsErrCd.MAKE500, String.format("총 %d 건 제작 실패", sendDetails.size())); + } catch (EnsException e) { + + respVO = EnsResponseVO.errBuilder().errCode(e.getErrCd()).errMsg(e.getMessage()).build(); + } catch (Exception e) { + + respVO = EnsResponseVO.errBuilder().errCode(EnsErrCd.MAKE500).errMsg(e.getMessage()).build(); + } finally { + if (!CmmnUtil.isEmpty(sendMast)) + if (EnsErrCd.OK.equals(respVO.getErrCode())) { + sendMast.setStatCd(StatCd.makeok); + sendMast.setError(FieldError.initBuilder().build()); + } else { + sendMast.setStatCd(StatCd.makefail); + sendMast.setError(FieldError.initBuilder() + .errorCode(respVO.getErrCode().getCode()) + .errorMessage(respVO.getErrMsg()) + .build()); + + applicationEventPublisher.publishEvent(MonitorEvent.builder() + .message(MessageByPhase.builder() + .oClass(getClass().getSimpleName() + "." + new Throwable().getStackTrace()[0].getMethodName()) + .postSeCd(PostSeCd.kkoMydoc) + .statCd(StatCd.makefail) + .errCd(respVO.getErrCode()) + .message("-.SendMastId: " + sendMastId + "\n" + respVO.getErrMsg()) + .build()) + .build()); + } + + + applicationEventPublisher.publishEvent(SendMastStatUpdateEvent.builder().sendMastId(sendMastId).build()); + } + + + return respVO; + } + + /** + * Hash값을 생성 한다. + * + * @param vo + * @return + */ + private String createHash(KkotalkDTO.SendDetailKkoTalkDTO vo) { + String hash = null; + try { + String text = vo.getExternalId() + + vo.getLink(); + // String text = new StringBuilder() + // .append(vo.getPropExternalDocumentUuid()) + // .append(vo.getPropLink()) + // .append(vo.getPropMessage()) + // .toString(); + hash = SHA256.encrypt(text); + } catch (Exception e) { + log.error("hash 값 생성 실패. [ sendDetailId {} ]", vo.getSendDetailId()); + } + return hash; + } + + + /** + * 메시지템플릿을 메시지로 변환 후 반환 한다. + * + * @param usePryMsg + * @param tmpltMngDTO + * @param tmplateMsgData + * @return + */ + private String msgTmplateToMessage(boolean usePryMsg, TmpltMngKkoTalkDTO tmpltMngDTO, String tmplateMsgData) { + String message = usePryMsg ? tmpltMngDTO.getPryMessage() : tmpltMngDTO.getMessage(); + if (CmmnUtil.isEmpty(tmplateMsgData)) + return message; + + Gson gson = new GsonBuilder().registerTypeAdapter(Map.class, new MapDeserailizer()).serializeNulls().create(); + try { + Map mMsgData = gson.fromJson(tmplateMsgData, Map.class); + for (String key : mMsgData.keySet()) { + message = message.replace(key, String.valueOf(mMsgData.get(key))); + } + } catch (Exception e) { + log.error("template Message 변환 실패. [ orgCd {} tmpltCd {} msgData {} ]", tmpltMngDTO.getOrgCd(), tmpltMngDTO.getTmpltCd(), tmplateMsgData, e); + return null; + } + return message; + } +} diff --git a/src/main/java/cokr/xit/ens/modules/kkotalk/service/support/KkoTalkRsltFetcher.java b/src/main/java/cokr/xit/ens/modules/kkotalk/service/support/KkoTalkRsltFetcher.java new file mode 100644 index 0000000..ad20553 --- /dev/null +++ b/src/main/java/cokr/xit/ens/modules/kkotalk/service/support/KkoTalkRsltFetcher.java @@ -0,0 +1,380 @@ +package cokr.xit.ens.modules.kkotalk.service.support; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +import com.google.common.collect.Lists; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +import cokr.xit.ens.core.aop.EnsResponseVO; +import cokr.xit.ens.core.exception.EnsException; +import cokr.xit.ens.core.exception.code.EnsErrCd; +import cokr.xit.ens.core.utils.CmmnUtil; +import cokr.xit.ens.core.utils.DateUtil; +import cokr.xit.ens.core.utils.MapDeserailizer; +import cokr.xit.ens.modules.common.biztmplt.ResultProcTemplate; +import cokr.xit.ens.modules.common.code.StatCd; +import cokr.xit.ens.modules.common.ctgy.sys.mng.domain.OrgMng; +import cokr.xit.ens.modules.common.ctgy.sys.mng.service.OrgMngService; +import cokr.xit.ens.modules.common.domain.SendMast; +import cokr.xit.ens.modules.common.domain.repository.SendMastRepository; +import cokr.xit.ens.modules.common.domain.support.FieldError; +import cokr.xit.ens.modules.common.event.SendMastStatUpdateEvent; +import cokr.xit.ens.modules.kkomydoc.code.KkoMydocStatusCd; +import cokr.xit.ens.modules.kkomydoc.domain.SendDetailKkoMydoc; +import cokr.xit.ens.modules.kkomydoc.domain.SendDetailKkoMydocStatHist; +import cokr.xit.ens.modules.kkomydoc.domain.repository.SendDetailKkoMydocRepository; +import cokr.xit.ens.modules.kkomydoc.domain.repository.SendDetailKkoMydocStatHistRepository; +import cokr.xit.ens.modules.kkomydoc.model.KkoMydocApiRespVO; +import cokr.xit.ens.modules.kkomydoc.service.support.KkoMydocApiSpec; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +// FIXME: 카카오톡 신규 추가 +@Slf4j +@Component +@RequiredArgsConstructor +public class KkoTalkRsltFetcher extends ResultProcTemplate { + + private final ApplicationEventPublisher applicationEventPublisher; + + private final SendMastRepository sendMastRepository; + private final SendDetailKkoMydocRepository sendDetailKkoMydocRepository; + private final SendDetailKkoMydocStatHistRepository sendDetailKkoMydocStatHistRepository; + private final OrgMngService orgMngService; + private final KkoMydocApiSpec kkoMydocApi; + @Value("${contract.kakao.pay.mydoc.api.bulksend-batch-unit}") + private int SEND_BATCH_UNIT; + + @Override + @Transactional(propagation = Propagation.REQUIRES_NEW) + public EnsResponseVO execute(Long sendMastId) { + + + SendMast sendMast = null; + List sendDetails = null; + EnsResponseVO respVO = null; + Map resultInfo = new HashMap<>(); + resultInfo.put("sendMastId", sendMastId); + try { + if (CmmnUtil.isEmpty(sendMastId)) + throw new EnsException(EnsErrCd.RSLT410, "발송마스터ID(은)는 필수조건 입니다."); + + sendMast = sendMastRepository.findById(sendMastId).orElseThrow(() -> new EnsException(EnsErrCd.RSLT404, String.format("일치하는 발송마스터 자료가 없습니다. [ sendMastId %s ]", sendMastId))); + sendDetails = sendDetailKkoMydocRepository.findAllBySendMastAndDocumentBinderUuidIsNotNull(sendMast); + OrgMng orgMng = orgMngService.find(sendMast.getOrgCd()).getResultInfo(); + + Lists.partition(sendDetails, SEND_BATCH_UNIT).stream() + .forEach(list -> { + + String sendRespBody = null; + String jsonStr = null; + try { + + EnsResponseVO message = this.makeMessage(list); + if (!(EnsErrCd.OK.equals(message.getErrCode()) || null == message.getErrCode())) + throw new EnsException(message.getErrCode(), message.getErrMsg()); + jsonStr = String.valueOf(message.getResultInfo()); + + + + ResponseEntity resp = kkoMydocApi.bulkStatus(orgMng.getKkoMdAccessToken(), orgMng.getKkoMdContractUuid(), jsonStr); + if (log.isDebugEnabled()) { + StringBuffer sb = new StringBuffer(); + sb.append("\n==============================================================================") + .append("\n[ Kakao Mydoc - StatFetch ]") + .append("\n### Request Info...") + .append("\n" + jsonStr) + .append("\n### Response Info...") + .append("\n" + resp.getBody()) + .append("\n=============================================================================="); + log.debug(sb.toString()); + } + + sendRespBody = resp.getBody(); + if (resp.getStatusCode() != HttpStatus.OK) + throw new EnsException(EnsErrCd.RSLT620, String.format("문서상태조회 중.. %s %s", resp.getStatusCode().toString(), resp.getBody())); + EnsResponseVO mResponse = this.respMsgToMap(resp.getBody()); + if (!EnsErrCd.OK.equals(mResponse.getErrCode())) + throw new EnsException(mResponse.getErrCode(), mResponse.getErrMsg()); + List> documents = (List>) ((Map) mResponse.getResultInfo()).get("documents"); + + + + Map mApiRespVOByDocumentBinderUuid = documents.stream() + .map(row -> this.toApiRespVOMap(row)) + .collect(Collectors.toMap(m -> String.valueOf(m.get("key")), m -> (KkoMydocApiRespVO) m.get("value"), (k1, k2) -> k1)); + + list.stream().forEach(row -> this.modifyStatInfoByDocumentBinderUuid(row, mApiRespVOByDocumentBinderUuid)); + + } catch (EnsException e) { + + list.stream() + .forEach(row -> row.setError(FieldError.initBuilder() + .errorCode(e.getErrCd().getCode()) + .errorMessage(e.getMessage()) + .build()) + ); + } catch (Exception e) { + + list.stream() + .forEach(row -> row.setError(FieldError.initBuilder() + .errorCode(EnsErrCd.RSLT500.getCode()) + .errorMessage(e.getMessage()) + .build()) + ); + } finally { + if (!CmmnUtil.isEmpty(jsonStr)) + sendDetailKkoMydocStatHistRepository.saveAll(this.toSendDetailStatHist(list, sendRespBody)); + } + + }); + + + respVO = EnsResponseVO.okBuilder().resultInfo(resultInfo).build(); + } catch (EnsException e) { + + respVO = EnsResponseVO.errRsltBuilder().errCode(e.getErrCd()).errMsg(e.getMessage()).resultInfo(resultInfo).build(); + } catch (Exception e) { + + respVO = EnsResponseVO.errRsltBuilder().errCode(EnsErrCd.RSLT500).errMsg(e.getMessage()).resultInfo(resultInfo).build(); + } finally { + if (!CmmnUtil.isEmpty(sendMast)) + if (EnsErrCd.OK.equals(respVO.getErrCode())) { + sendMast.setError(FieldError.initBuilder().build()); + + + Integer readCnt = sendDetails.stream() + .map(row -> KkoMydocStatusCd.READ.equals(row.getKkoDocStat()) ? 1 : 0) + .reduce(Integer::sum) + .orElseGet(() -> 0); + sendMast.setReadCnt(readCnt); + + if (StatCd.sendok.equals(sendMast.getStatCd())) { + if (readCnt > 0) + sendMast.setStatCd(StatCd.open); + else + sendMast.setStatCd(StatCd.sendcmplt); + } else if (StatCd.sendcmplt.equals(sendMast.getStatCd())) { + if (readCnt > 0) + sendMast.setStatCd(StatCd.open); + } + } else { + sendMast.setError(FieldError.initBuilder() + .errorCode(respVO.getErrCode().getCode()) + .errorMessage(respVO.getErrMsg()) + .build()); + } + + + applicationEventPublisher.publishEvent(SendMastStatUpdateEvent.builder().sendMastId(sendMastId).build()); + } + + + return respVO; + } + + + /** + * 문서상태조회API body Message로 변환하여 반환 한다. + * + * @param list + * @return + */ + private EnsResponseVO makeMessage(List list) { + String result = null; + Gson gson = new GsonBuilder().disableHtmlEscaping().create(); + EnsResponseVO resp = null; + try { + + List documentBinderUuids = list.stream() + .map(row -> row.getDocumentBinderUuid()) + .collect(Collectors.toList()); + Map mJson = new HashMap<>(); + mJson.put("document_binder_uuids", documentBinderUuids); + + + result = gson.toJson(mJson); + + resp = EnsResponseVO.okBuilder() + .resultInfo(result) + .build(); + } catch (EnsException e) { + resp = EnsResponseVO.errBuilder() + .errCode(e.getErrCd()) + .errMsg(e.getMessage()) + .build(); + } catch (Exception e) { + resp = EnsResponseVO.errBuilder() + .errCode(EnsErrCd.RSLT502) + .errMsg(String.format("요청 메시지 생성 중 오류 발생. %s", e.getMessage())) + .build(); + } + + return resp; + } + + + /** + * 문서상태조회 응답결과를 API응답VO 객체에 매핑하여 반환 + * + * @param row + * @return + */ + private Map toApiRespVOMap(Map row) { + KkoMydocApiRespVO kkoMydocApiRespVO = KkoMydocApiRespVO.builder() + .error_code(row.containsKey("error_code") ? String.valueOf(row.get("error_code")) : null) + .error_message(row.containsKey("error_message") ? String.valueOf(row.get("error_message")) : null) + .data(row.containsKey("status_data") ? row.get("status_data") : new HashMap<>()) + .build(); + + Map m = new HashMap<>(); + m.put("key", row.get("document_binder_uuid")); + m.put("value", kkoMydocApiRespVO); + return m; + } + + + /** + * 문서상태조회결과 갱신(update) + * -. 성공/실패 여부에 따라 상태정보 및 Error 필드 갱신 + * + * @param row + * @param mMydocApiRespVOByDocumentBinderUuid + */ + private void modifyStatInfoByDocumentBinderUuid(SendDetailKkoMydoc row, Map mMydocApiRespVOByDocumentBinderUuid) { + KkoMydocApiRespVO apiRespVO = mMydocApiRespVOByDocumentBinderUuid.get(row.getDocumentBinderUuid()); + if (CmmnUtil.isEmpty(apiRespVO.getError_code())) { + Map statusData = (Map) apiRespVO.getData(); + + String docBoxStatus = statusData.containsKey("doc_box_status") ? (String) statusData.get("doc_box_status") : null; + Long docBoxSentAt = statusData.containsKey("doc_box_sent_at") ? (Long) statusData.get("doc_box_sent_at") : null; + Long docBoxReceivedAt = statusData.containsKey("doc_box_received_at") ? (Long) statusData.get("doc_box_received_at") : null; + Long authenticatedAt = statusData.containsKey("authenticated_at") ? (Long) statusData.get("authenticated_at") : null; + Long tokenUsedAt = statusData.containsKey("token_used_at") ? (Long) statusData.get("token_used_at") : null; + Long docBoxReadAt = statusData.containsKey("doc_box_read_at") ? (Long) statusData.get("doc_box_read_at") : null; + Long userNotifiedAt = statusData.containsKey("user_notified_at") ? (Long) statusData.get("user_notified_at") : null; + + row.setKkoDocStat(CmmnUtil.isEmpty(docBoxStatus) ? row.getKkoDocStat() : KkoMydocStatusCd.valueOfEnum(docBoxStatus)); + row.setKkoDocSentDt(CmmnUtil.isEmpty(row.getKkoDocSentDt()) ? DateUtil.absTimeSecToDate(docBoxSentAt, "yyyyMMddHHmmss") : row.getKkoDocSentDt()); + row.setKkoDocReceivedDt(CmmnUtil.isEmpty(row.getKkoDocReceivedDt()) ? DateUtil.absTimeSecToDate(docBoxReceivedAt, "yyyyMMddHHmmss") : row.getKkoDocReceivedDt()); + row.setKkoDocAuthFrstDt(CmmnUtil.isEmpty(row.getKkoDocAuthFrstDt()) ? DateUtil.absTimeSecToDate(authenticatedAt, "yyyyMMddHHmmss") : row.getKkoDocAuthFrstDt()); + row.setKkoDocTokenVrfyFrstDt(CmmnUtil.isEmpty(row.getKkoDocTokenVrfyFrstDt()) ? DateUtil.absTimeSecToDate(tokenUsedAt, "yyyyMMddHHmmss") : row.getKkoDocTokenVrfyFrstDt()); + row.setKkoDocReadFrstDt(CmmnUtil.isEmpty(row.getKkoDocReadFrstDt()) ? DateUtil.absTimeSecToDate(docBoxReadAt, "yyyyMMddHHmmss") : row.getKkoDocReadFrstDt()); + row.setKkoDocUserNotiedDt(CmmnUtil.isEmpty(row.getKkoDocUserNotiedDt()) ? DateUtil.absTimeSecToDate(userNotifiedAt, "yyyyMMddHHmmss") : row.getKkoDocUserNotiedDt()); + + row.setError(FieldError.initBuilder().build()); + } else { + if ("NOT_FOUND".equals(apiRespVO.getError_code()) && "documentBinder를 찾을 수 없습니다.".equals(apiRespVO.getError_message())) + row.setKkoDocStat(KkoMydocStatusCd.INTERNAL_SENT_ERR); + + row.setError(FieldError.initBuilder() + .errorCode(EnsErrCd.RSLT630.getCode()) + .errorMessage(String.format("%s %s", apiRespVO.getError_code(), apiRespVO.getError_message())) + .build()); + } + } + + /** + * 문서상태조회 응답을 전송요청이력 Entity 데이터로 변환하여 반환 한다. + * + * @param list + * @param respMsg + * @return + */ + private List toSendDetailStatHist(List list, String respMsg) { + +// Map mMydocApiRespVOByExtDocUuid = + + EnsResponseVO respVO = this.respMsgToMap(respMsg); + + + if (EnsErrCd.OK.equals(respVO.getErrCode())) { + + List> documents = (List>) ((Map) respVO.getResultInfo()).get("documents"); + Map mRespVOByExtDocUuid = documents.stream() + .map(row -> this.toApiRespVOMap(row)) + .collect(Collectors.toMap(m -> String.valueOf(m.get("key")), m -> (KkoMydocApiRespVO) m.get("value"), (k1, k2) -> k1)); + + Gson gson = new GsonBuilder().disableHtmlEscaping().create(); + + return list.stream() + .map(row -> { + KkoMydocApiRespVO apiRespVO = mRespVOByExtDocUuid.get(row.getDocumentBinderUuid()); + Map statusData = (Map) apiRespVO.getData(); + return SendDetailKkoMydocStatHist.builder() + .sendDetailId(row.getSendDetailId()) + .documentBinderUuid(row.getDocumentBinderUuid()) + .externalDocumentUuid(row.getPropExternalDocumentUuid()) +// .respRawMsg(gson.toJson(statusData)) + .docBoxStatus(statusData.containsKey("doc_box_status") ? (String) statusData.get("doc_box_status") : null) + .docBoxSentAt(statusData.containsKey("doc_box_sent_at") ? (Long) statusData.get("doc_box_sent_at") : null) + .docBoxReceivedAt(statusData.containsKey("doc_box_received_at") ? (Long) statusData.get("doc_box_received_at") : null) + .authenticatedAt(statusData.containsKey("authenticated_at") ? (Long) statusData.get("authenticated_at") : null) + .tokenUsedAt(statusData.containsKey("token_used_at") ? (Long) statusData.get("token_used_at") : null) + .docBoxReadAt(statusData.containsKey("doc_box_read_at") ? (Long) statusData.get("doc_box_read_at") : null) + .userNotifiedAt(statusData.containsKey("user_notified_at") ? (Long) statusData.get("user_notified_at") : null) + .docDistributionReceivedAt(statusData.containsKey("doc_distribution_received_at") ? (Long) statusData.get("doc_distribution_received_at") : null) + .payload(statusData.containsKey("payload") ? (String) statusData.get("payload") : null) + .error(FieldError.initBuilder() + .errorCode(apiRespVO.getError_code()) + .errorMessage(apiRespVO.getError_message()) + .build()) + .build(); + }) + .collect(Collectors.toList()); + + } else { + return list.stream() + .map(row -> { + return SendDetailKkoMydocStatHist.builder() + .sendDetailId(row.getSendDetailId()) + .documentBinderUuid(row.getDocumentBinderUuid()) + .externalDocumentUuid(row.getPropExternalDocumentUuid()) + .error(FieldError.initBuilder() + .errorCode(respVO.getErrCode().getCode()) + .errorMessage(respVO.getErrMsg()) + .build()) + .build(); + }) + .collect(Collectors.toList()); + } + } + + + private EnsResponseVO respMsgToMap(String respMsg) { + Gson gson = new GsonBuilder().registerTypeAdapter(Map.class, new MapDeserailizer()).serializeNulls().create(); + EnsErrCd errCode = EnsErrCd.OK; + String errMsg = EnsErrCd.OK.getCodeNm(); + Map mResp = null; + try { + mResp = gson.fromJson(respMsg, Map.class); + if (CmmnUtil.isEmpty(mResp.get("documents"))) + throw new EnsException(EnsErrCd.RSLT620, String.format("문서상태조회API 오류. documents 키값이 없습니다. %s", respMsg)); + } catch (EnsException e) { + errCode = e.getErrCd(); + errMsg = e.getMessage(); + } catch (Exception e) { + errCode = EnsErrCd.RSLT511; + errMsg = String.format("문서상태조회API 응답 데이터 JSON 객체 변환 실패. %s", respMsg); + } + return EnsErrCd.OK.equals(errCode) ? + EnsResponseVO.okBuilder().resultInfo(mResp).build() + : EnsResponseVO.errBuilder() + .errCode(errCode) + .errMsg(errMsg) + .build(); + } + +} diff --git a/src/main/java/cokr/xit/ens/modules/kkotalk/service/support/KkoTalkRsltProvider.java b/src/main/java/cokr/xit/ens/modules/kkotalk/service/support/KkoTalkRsltProvider.java new file mode 100644 index 0000000..5483e79 --- /dev/null +++ b/src/main/java/cokr/xit/ens/modules/kkotalk/service/support/KkoTalkRsltProvider.java @@ -0,0 +1,114 @@ +package cokr.xit.ens.modules.kkotalk.service.support; + +import java.time.format.DateTimeFormatter; +import java.util.List; +import java.util.stream.Collectors; + +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +import cokr.xit.ens.core.aop.EnsResponseVO; +import cokr.xit.ens.core.exception.EnsException; +import cokr.xit.ens.core.exception.code.EnsErrCd; +import cokr.xit.ens.core.utils.CmmnUtil; +import cokr.xit.ens.modules.common.biztmplt.EnsPhaseProcSupport; +import cokr.xit.ens.modules.common.code.StatCd; +import cokr.xit.ens.modules.common.domain.SendMast; +import cokr.xit.ens.modules.common.domain.repository.SendMastRepository; +import cokr.xit.ens.modules.kkomydoc.domain.SendDetailKkoMydoc; +import cokr.xit.ens.modules.kkomydoc.domain.repository.SendDetailKkoMydocRepository; +import cokr.xit.ens.modules.kkomydoc.model.KkoMydocRsltRespDTO; +import cokr.xit.ens.modules.kkomydoc.model.config.KkoMydocStat; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +// FIXME: 카카오톡 신규 추가 +@Slf4j +@Component +@RequiredArgsConstructor +public class KkoTalkRsltProvider implements EnsPhaseProcSupport { + + private final SendMastRepository sendMastRepository; + private final SendDetailKkoMydocRepository sendDetailKkoMydocRepository; + + @Override + @Transactional(propagation = Propagation.REQUIRES_NEW) + public EnsResponseVO statReady(List args) { + log.info("no process"); + return null; + } + + @Override + @Transactional(propagation = Propagation.REQUIRES_NEW) + public EnsResponseVO statBegin(Long arg) { + log.info("no process"); + return null; + } + + @Override + @Transactional(propagation = Propagation.REQUIRES_NEW) + public EnsResponseVO execute(Long sendMastId) { + try { + SendMast sendMast = sendMastRepository.findById(sendMastId) + .orElseThrow(() -> new EnsException(EnsErrCd.PRVD404, String.format("일치하는 발송마스터 자료가 없습니다. [ sendMastId %s ]", sendMastId))); + List sendDetailKkoMydocs = sendDetailKkoMydocRepository.findAllBySendMast(sendMast); + + + KkoMydocRsltRespDTO respDTO = KkoMydocRsltRespDTO.builder() + .sendMastId(sendMast.getSendMastId()) + .statCd(sendMast.getStatCd()) + .orgCd(sendMast.getOrgCd()) + .tmpltCd(sendMast.getTmpltCd()) + .postBundleTitle(sendMast.getPostBundleTitle()) + .sendDt(sendMast.getSendDt().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"))) + .closeDt(sendMast.getCloseDt().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"))) + .closeAt(this.getCloseAt(sendMast)) + .documents(sendDetailKkoMydocs.stream() + .map(row -> KkoMydocStat.builder() + .sendDetailId(row.getSendDetailId()) + .externalDocumentUuid(row.getPropExternalDocumentUuid()) + .documentBinderUuid(row.getDocumentBinderUuid()) + .kkoDocStat(row.getKkoDocStat()) + .kkoDocSentDt(row.getKkoDocSentDt()) + .kkoDocReceivedDt(row.getKkoDocReceivedDt()) + .kkoDocAuthFrstDt(row.getKkoDocAuthFrstDt()) + .kkoDocTokenVrfyFrstDt(row.getKkoDocTokenVrfyFrstDt()) + .kkoDocReadFrstDt(row.getKkoDocReadFrstDt()) + .kkoDocUserNotiedDt(row.getKkoDocUserNotiedDt()) + .errorCode(CmmnUtil.isEmpty(row.getError()) ? null : row.getError().getErrorCode()) + .errorMessage(CmmnUtil.isEmpty(row.getError()) ? null : row.getError().getErrorMessage()) + .build()) + .collect(Collectors.toList())) + .build(); + + return EnsResponseVO.okBuilder().resultInfo(respDTO).build(); + } catch (EnsException e) { + return EnsResponseVO.errRsltBuilder() + .errCode(e.getErrCd()) + .errMsg(e.getMessage()) + .resultInfo(KkoMydocRsltRespDTO.builder() + .sendMastId(sendMastId) + .build()) + .build(); + } catch (Exception e) { + return EnsResponseVO.errRsltBuilder() + .errCode(EnsErrCd.ERR999) + .errMsg(String.format("전송결과 응답 처리 실패. 실패사유: %s", e.getMessage())) + .resultInfo(KkoMydocRsltRespDTO.builder() + .sendMastId(sendMastId) + .build()) + .build(); + } + + } + + private boolean getCloseAt(SendMast sendMast) { + if (StatCd.close.equals(sendMast.getStatCd())) + return true; + else if (StatCd.sendfail.equals(sendMast.getStatCd())) + return true; + else + return false; + } +} diff --git a/src/main/java/cokr/xit/ens/modules/kkotalk/service/support/KkoTalkSender.java b/src/main/java/cokr/xit/ens/modules/kkotalk/service/support/KkoTalkSender.java new file mode 100644 index 0000000..15cecd6 --- /dev/null +++ b/src/main/java/cokr/xit/ens/modules/kkotalk/service/support/KkoTalkSender.java @@ -0,0 +1,427 @@ +package cokr.xit.ens.modules.kkotalk.service.support; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +import com.google.common.collect.Lists; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +import cokr.xit.ens.core.aop.EnsResponseVO; +import cokr.xit.ens.core.exception.EnsException; +import cokr.xit.ens.core.exception.code.EnsErrCd; +import cokr.xit.ens.core.monitor.slack.event.MonitorEvent; +import cokr.xit.ens.core.utils.CmmnUtil; +import cokr.xit.ens.core.utils.MapDeserailizer; +import cokr.xit.ens.modules.common.biztmplt.SendProcTemplate; +import cokr.xit.ens.modules.common.code.PostSeCd; +import cokr.xit.ens.modules.common.code.StatCd; +import cokr.xit.ens.modules.common.ctgy.sys.mng.domain.OrgMng; +import cokr.xit.ens.modules.common.ctgy.sys.mng.service.OrgMngService; +import cokr.xit.ens.modules.common.domain.SendMast; +import cokr.xit.ens.modules.common.domain.repository.SendMastRepository; +import cokr.xit.ens.modules.common.domain.support.FieldError; +import cokr.xit.ens.modules.common.event.SendMastStatUpdateEvent; +import cokr.xit.ens.modules.common.monitor.MessageByPhase; +import cokr.xit.ens.modules.kkomydoc.code.KkoMydocStatusCd; +import cokr.xit.ens.modules.kkomydoc.domain.SendDetailKkoMydoc; +import cokr.xit.ens.modules.kkomydoc.domain.SendDetailKkoMydocReqHist; +import cokr.xit.ens.modules.kkomydoc.domain.repository.SendDetailKkoMydocRepository; +import cokr.xit.ens.modules.kkomydoc.domain.repository.SendDetailKkoMydocReqHistRepository; +import cokr.xit.ens.modules.kkomydoc.model.KkoMydocApiRespVO; +import cokr.xit.ens.modules.kkomydoc.service.support.KkoMydocApiSpec; +import cokr.xit.ens.modules.kkotalk.mapper.IKkoTalkMapper; +import cokr.xit.ens.modules.kkotalk.model.KkotalkApiDTO; +import cokr.xit.ens.modules.kkotalk.model.KkotalkDTO; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +// FIXME: 카카오톡 신규 추가 +@Slf4j +@Component +@RequiredArgsConstructor +public class KkoTalkSender extends SendProcTemplate { + + private final ApplicationEventPublisher applicationEventPublisher; + private final SendMastRepository sendMastRepository; + private final SendDetailKkoMydocRepository sendDetailKkoMydocRepository; + private final IKkoTalkMapper talkMapper; + private final SendDetailKkoMydocReqHistRepository sendDetailKkoMydocReqHistRepository; + private final OrgMngService orgMngService; + private final KkoMydocApiSpec kkoMydocApi; + private final IKkoTalkApiService kkoTalkApi; + @Value("${contract.kakao.pay.mydoc.api.bulksend-batch-unit}") + private int SEND_BATCH_UNIT; + + private Gson gson = new GsonBuilder().registerTypeAdapter(Map.class, new MapDeserailizer()).disableHtmlEscaping().create(); + + @Override + @Transactional(propagation = Propagation.REQUIRES_NEW) + public EnsResponseVO execute(Long sendMastId) { + + SendMast sendMast = null; + List sendDetails = null; + EnsResponseVO respVO = null; + try { + if (CmmnUtil.isEmpty(sendMastId)) + throw new EnsException(EnsErrCd.SEND410, "발송마스터ID(은)는 필수조건 입니다."); + + sendMast = sendMastRepository.findById(sendMastId).orElseThrow(() -> new EnsException(EnsErrCd.SEND404, String.format("일치하는 발송마스터 자료가 없습니다. [ sendMastId %s ]", sendMastId))); + sendDetails = talkMapper.findAllFetchBySendMastId(sendMast.getSendMastId()); + //sendDetails = sendDetailKkoMydocRepository.findAllFetchBySendMastId(sendMast.getSendMastId()); + OrgMng orgMng = orgMngService.find(sendMast.getOrgCd()).getResultInfo(); + + + Lists.partition(sendDetails, SEND_BATCH_UNIT).stream() + .forEach(list -> { + + String sendRespBody = null; + String jsonStr = null; + try { + + List envelopes = this.makeMessage(list); + KkotalkDTO.BulkSendResponse resp = kkoTalkApi.requestSendBulk( + KkotalkDTO.BulkSendRequest.builder() + .envelopes(envelopes) + .signguCode(orgMng.getOrgCd()) + .ffnlgCode("11") + + .build() + ); + +/* + EnsResponseVO message = this.makeMessage(list); + if (!(EnsErrCd.OK.equals(message.getErrCode()) || null == message.getErrCode())) + throw new EnsException(message.getErrCode(), message.getErrMsg()); + jsonStr = String.valueOf(message.getResultInfo()); + + ResponseEntity resp = kkoMydocApi.bulkSend(orgMng.getKkoMdAccessToken(), orgMng.getKkoMdContractUuid(), jsonStr); + if (log.isDebugEnabled()) { + StringBuffer sb = new StringBuffer(); + sb.append("\n==============================================================================") + .append("\n[ Kakao Mydoc - Sending ]") + .append("\n### Request Info...") + .append("\n" + jsonStr) + .append("\n### Response Info...") + .append("\n" + resp.getBody()) + .append("\n=============================================================================="); + log.debug(sb.toString()); + } + + sendRespBody = resp.getBody(); + if (resp.getStatusCode() != HttpStatus.OK) + throw new EnsException(EnsErrCd.SEND620, String.format("전송요청 중.. %s %s", resp.getStatusCode().toString(), resp.getBody())); + EnsResponseVO mResponse = this.respMsgToMap(resp.getBody()); + if (!EnsErrCd.OK.equals(mResponse.getErrCode())) + throw new EnsException(mResponse.getErrCode(), mResponse.getErrMsg()); + List> documents = (List>) ((Map) mResponse.getResultInfo()).get("documents"); + + + + + Map mApiRespVOByExtDocUuid = documents.stream() + .map(row -> this.toApiRespVOMap(row)) + .collect(Collectors.toMap(m -> String.valueOf(m.get("key")), m -> (KkoMydocApiRespVO) m.get("value"), (k1, k2) -> k1)); + + list.stream() + .forEach(row -> this.modifyDocSendRsltByExtDocUuid(row, mApiRespVOByExtDocUuid)); + + + + } catch (EnsException e) { + + list.stream() + .forEach(row -> { + row.set(KkoMydocStatusCd.SENT_FAIL); + row.setError(FieldError.initBuilder() + .errorCode(e.getErrCd().getCode()) + .errorMessage(e.getMessage()) + .build()); + }); + } catch (Exception e) { + + list.stream() + .forEach(row -> { + row.setKkoDocStat(KkoMydocStatusCd.SENT_FAIL); + row.setError(FieldError.initBuilder() + .errorCode(EnsErrCd.SEND500.getCode()) + .errorMessage(e.getMessage()) + .build()); + }); +*/ + } finally { +// if (!CmmnUtil.isEmpty(jsonStr)) +// sendDetailKkoMydocReqHistRepository.saveAll(this.toSendDetailReqHist(list, jsonStr, sendRespBody)); + } + + }); + + + Optional cntSuccess = sendDetails.stream() + .map(row -> CmmnUtil.isEmpty(row.getErrorCode()) ? 1 : 0) + .reduce(Integer::sum); + if (cntSuccess.get() > 0) + respVO = EnsResponseVO.okBuilder() + .resultInfo(sendDetails.stream() +// .map(row -> this.toResultInfo(row)) + .collect(Collectors.toList())) + .build(); + else + throw new EnsException(EnsErrCd.SEND500, String.format("전체건수 전송 실패(총 %d 건)", sendDetails.size())); + + } catch (EnsException e) { + + respVO = EnsResponseVO.errBuilder().errCode(e.getErrCd()).errMsg(e.getMessage()).build(); + } catch (Exception e) { + + respVO = EnsResponseVO.errBuilder().errCode(EnsErrCd.SEND500).errMsg(e.getMessage()).build(); + } finally { + if (!CmmnUtil.isEmpty(sendMast)) { + if (EnsErrCd.OK.equals(respVO.getErrCode())) { + sendMast.setStatCd(StatCd.sendok); + sendMast.setError(FieldError.initBuilder().build()); + } else { + sendMast.setStatCd(StatCd.sendfail); + sendMast.setError(FieldError.initBuilder() + .errorCode(respVO.getErrCode().getCode()) + .errorMessage(respVO.getErrMsg()) + .build()); + + applicationEventPublisher.publishEvent(MonitorEvent.builder() + .message(MessageByPhase.builder() + .oClass(getClass().getSimpleName() + "." + new Throwable().getStackTrace()[0].getMethodName()) + .postSeCd(PostSeCd.kkoMydoc) + .statCd(StatCd.sendfail) + .errCd(respVO.getErrCode()) + .message("-.SendMastId: " + sendMastId + "\n" + respVO.getErrMsg()) + .build()) + .build()); + } + } + +// if (respVO != null && EnsErrCd.OK.equals(respVO.getErrCode())) { +// applicationEventPublisher.publishEvent(KkoMydocSentEvent.builder().sendMastId(sendMastId).build()); +// } else { + applicationEventPublisher.publishEvent(SendMastStatUpdateEvent.builder().sendMastId(sendMastId).build()); +// } + + } + + + return respVO; + } + + + /** + * 전송요청API body Message로 변환하여 반환 한다. + * + * @param list + * @return + */ + private List makeMessage(List list) { + final List bulkList = new ArrayList<>(); + + for (KkotalkDTO.SendDetailKkoTalkDTO sendTgtDTO : list) { + + KkotalkApiDTO.Envelope bulkReqDTO = null; + if (StringUtils.isNotEmpty(sendTgtDTO.getCi())) { + bulkReqDTO = KkotalkApiDTO.Envelope.builder() + .ci(sendTgtDTO.getCi()) + .build(); + } else { + bulkReqDTO = KkotalkApiDTO.Envelope.builder() + .build(); + } + + final KkotalkApiDTO.Content content = KkotalkApiDTO.Content.builder() + .link(sendTgtDTO.getLink()) + .build(); + + bulkReqDTO.setExternalId(sendTgtDTO.getSendDetailId().toString()); + bulkReqDTO.setTitle(sendTgtDTO.getTitle()); + bulkReqDTO.setPayload(sendTgtDTO.getPayload()); + bulkReqDTO.setReadExpiresAt(sendTgtDTO.getReadExpiresAt()); + bulkReqDTO.setReviewExpiresAt(sendTgtDTO.getReviewExpiresAt()); + bulkReqDTO.setHash(sendTgtDTO.getHash()); + bulkReqDTO.setGuide(sendTgtDTO.getGuide()); + bulkReqDTO.setContent(content); + + bulkList.add(bulkReqDTO); + } + return bulkList; + } + + + /** + * 전송요청 응답결과를 API응답VO 객체에 매핑하여 반환 + * + * @param row + * @return + */ + private Map toApiRespVOMap(Map row) { + KkoMydocApiRespVO apiRespVO = KkoMydocApiRespVO.builder() + .error_code(row.containsKey("error_code") ? String.valueOf(row.get("error_code")) : null) + .error_message(row.containsKey("error_message") ? String.valueOf(row.get("error_message")) : null) + .data(row.containsKey("document_binder_uuid") ? String.valueOf(row.get("document_binder_uuid")) : null) + .build(); + + Map m = new HashMap<>(); + m.put("key", row.get("external_document_uuid")); + m.put("value", apiRespVO); + return m; + } + + /** + * 전송요청결과 갱신(update) + * -. 성공/실패 여부에 따라 문서식별번호(document_binder_uuid) 및 Error 필드 갱신 + * + * @param row + * @param mApiRespVOByExtDocUuid + */ + private void modifyDocSendRsltByExtDocUuid(SendDetailKkoMydoc row, Map mApiRespVOByExtDocUuid) { + KkoMydocApiRespVO apiRespVO = mApiRespVOByExtDocUuid.get(row.getPropExternalDocumentUuid()); + if (CmmnUtil.isEmpty(apiRespVO.getError_code())) { + row.setKkoDocStat(KkoMydocStatusCd.SENT); + row.setDocumentBinderUuid(String.valueOf(apiRespVO.getData())); + row.setError(FieldError.initBuilder().build()); + } else { + row.setKkoDocStat(KkoMydocStatusCd.valueOfEnum(apiRespVO.getError_code())); + row.setError(FieldError.initBuilder() + .errorCode(EnsErrCd.SEND630.getCode()) + .errorMessage(String.format("%s %s", apiRespVO.getError_code(), apiRespVO.getError_message())) + .build()); + } + + } + + + private Map toResultInfo(SendDetailKkoMydoc row) { + Map m = new HashMap<>(); +// m.put("msgIdx", row.getMsgIdx()); + m.put("documentBinderUuid", row.getDocumentBinderUuid()); + m.put("externalDocumentUuid", row.getPropExternalDocumentUuid()); + if (!CmmnUtil.isEmpty(row.getError())) { + m.put("errCd", row.getError().getErrorCode()); + m.put("errMsg", row.getError().getErrorMessage()); + } + return m; + } + + + /** + * 전송요청응답을 전송요청이력 Entity 데이터로 변환하여 반환 한다. + * + * @param sendMsg + * @param respMsg + * @return + */ + private List toSendDetailReqHist(List list, String sendMsg, String respMsg) { + + + List> sendMsgList = (List>) gson.fromJson(sendMsg, Map.class).get("documents"); + Map mSendDetailIds = list.stream() + .map(row -> { + Map m = new HashMap<>(); + m.put("key", row.getPropExternalDocumentUuid()); + m.put("value", row.getSendDetailId()); + return m; + }) + .collect(Collectors.toMap(m -> String.valueOf(m.get("key")), m -> (Long) m.get("value"), (k1, k2) -> k1)); + + EnsResponseVO respVO = this.respMsgToMap(respMsg); + + + if (EnsErrCd.OK.equals(respVO.getErrCode())) { + Map mRespBody = (Map) respVO.getResultInfo(); + List> documents = (List>) mRespBody.get("documents"); + Map> mRespMsg = documents.stream() + .map(row -> { + Map m = new HashMap<>(); + m.put("key", String.valueOf(row.get("external_document_uuid"))); + Map mValue = new HashMap<>(); + mValue.put("document_binder_uuid", row.get("document_binder_uuid")); + mValue.put("error_code", row.get("error_code")); + mValue.put("error_message", row.get("error_message")); + mValue.put("rawMsg", gson.toJson(row)); + m.put("value", mValue); + return m; + }) + .collect(Collectors.toMap(m -> String.valueOf(m.get("key")), m -> (Map) m.get("value"), (k1, k2) -> k1)); + + return sendMsgList.stream() + .map(row -> { + Map property = (Map) row.get("property"); + String externalDocumentUuid = property.get("external_document_uuid"); + boolean isSuccess = CmmnUtil.isEmpty(mRespMsg.get(externalDocumentUuid).get("error_code")); + + return SendDetailKkoMydocReqHist.builder() + .sendDetailId(mSendDetailIds.get(externalDocumentUuid)) + .sendRawMsg(gson.toJson(row)) + .externalDocumentUuid(externalDocumentUuid) + .documentBinderUuid(mRespMsg.get(externalDocumentUuid).get("document_binder_uuid")) + .respRawMsg(mRespMsg.get(externalDocumentUuid).get("rawMsg")) + .error(FieldError.initBuilder() + .errorCode(isSuccess ? null : EnsErrCd.SEND620.getCode()) + .errorMessage(isSuccess ? null : String.format("%s %s" + , mRespMsg.get(externalDocumentUuid).get("error_code") + , mRespMsg.get(externalDocumentUuid).get("error_message") + )) + .build()) + .build(); + }) + .collect(Collectors.toList()); + + } else { + return sendMsgList.stream() + .map(row -> { + Map property = (Map) row.get("property"); + String externalDocumentUuid = property.get("external_document_uuid"); + return SendDetailKkoMydocReqHist.builder() + .sendDetailId(mSendDetailIds.get(externalDocumentUuid)) + .sendRawMsg(gson.toJson(row)) + .externalDocumentUuid(externalDocumentUuid) + .respRawMsg(respMsg) + .error(FieldError.initBuilder() + .errorCode(respVO.getErrCode().getCode()) + .errorMessage(respVO.getErrMsg()) + .build()) + .build(); + }) + .collect(Collectors.toList()); + } + } + + private EnsResponseVO respMsgToMap(String respMsg) { + EnsErrCd errCode = EnsErrCd.OK; + String errMsg = EnsErrCd.OK.getCodeNm(); + Map mResp = null; + try { + mResp = gson.fromJson(respMsg, Map.class); + if (CmmnUtil.isEmpty(mResp.get("documents"))) + throw new EnsException(EnsErrCd.SEND620, String.format("전송요청API 오류. documents 키값이 없습니다. %s", respMsg)); + } catch (EnsException e) { + errCode = e.getErrCd(); + errMsg = e.getMessage(); + } catch (Exception e) { + errCode = EnsErrCd.SEND511; + errMsg = String.format("전송요청API 응답데이터 JSON 객체 변환 실패. %s", respMsg); + } + return EnsErrCd.OK.equals(errCode) ? + EnsResponseVO.okBuilder().resultInfo(mResp).build() + : EnsResponseVO.errBuilder() + .errCode(errCode) + .errMsg(errMsg) + .build(); + } +} diff --git a/src/main/java/cokr/xit/ens/modules/kkotalk/web/KkotalkEltrcDocController.java b/src/main/java/cokr/xit/ens/modules/kkotalk/web/KkotalkApiController.java similarity index 98% rename from src/main/java/cokr/xit/ens/modules/kkotalk/web/KkotalkEltrcDocController.java rename to src/main/java/cokr/xit/ens/modules/kkotalk/web/KkotalkApiController.java index 94bca13..d2f53bd 100644 --- a/src/main/java/cokr/xit/ens/modules/kkotalk/web/KkotalkEltrcDocController.java +++ b/src/main/java/cokr/xit/ens/modules/kkotalk/web/KkotalkApiController.java @@ -10,7 +10,7 @@ import cokr.xit.ens.core.aop.ApiResponseDTO; import cokr.xit.ens.core.aop.IApiResponse; import cokr.xit.ens.modules.kkotalk.model.KkotalkApiDTO; import cokr.xit.ens.modules.kkotalk.model.KkotalkDTO; -import cokr.xit.ens.modules.kkotalk.service.IKkotalkEltrcDocService; +import cokr.xit.ens.modules.kkotalk.service.support.IKkoTalkApiService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.ExampleObject; @@ -32,13 +32,13 @@ import lombok.extern.slf4j.Slf4j; * * */ -@Tag(name = "KkotalkEltrcDocController", description = "카카오톡 전자문서 API") +@Tag(name = "KkotalkController", description = "카카오톡 전자문서 API") @Slf4j @RequiredArgsConstructor @RestController @RequestMapping(value = "/api/ens/kakao/v2") -public class KkotalkEltrcDocController { - private final IKkotalkEltrcDocService service; +public class KkotalkApiController { + private final IKkoTalkApiService service; /** *
diff --git a/src/main/resources/mybatis-mapper/modules/iup-kkotalk-mapper.xml b/src/main/resources/mybatis-mapper/modules/iup-kkotalk-mapper.xml
new file mode 100644
index 0000000..2db7390
--- /dev/null
+++ b/src/main/resources/mybatis-mapper/modules/iup-kkotalk-mapper.xml
@@ -0,0 +1,79 @@
+
+
+
+    
+	
+	
+	
+	
+		/** iup-kkotalk-mapper|saveSndDtlKkoTalk-카카오톡발송상세생성|julim  */
+		INSERT INTO ens_snd_dtl_kko_talk (
+		    send_detail_id,
+		    title,
+		    link,
+		    hash,
+		    guide,
+		    payload,
+		    read_expires_at,
+		    review_expires_at,
+		    use_non_personalized_noti,
+		    ci,
+		    phone_number,
+		    name,
+		    birthday,
+		    external_id,
+		    error_code,
+		    error_message,
+		    bill_uid,
+		    send_mast_id,
+		    regist_dt
+		) VALUES (
+		    #{sendDetailId},
+			#{title},
+		    #{link},
+		    #{hash},
+		    #{guide},
+			#{payload},
+		    #{readExpiresAt},
+			#{reviewExpiresAt},
+			#{useNonPersonalizedNoti},
+			#{ci},
+			#{phoneNumber},
+			#{name},
+			#{birthday},
+			#{externalId},
+			#{errorCode},
+			#{errorMessage},
+		    #{billUid},
+		    #{sendMastId},
+		    sysdate
+		)
+	
+
+	
+
+
diff --git a/src/main/resources/mybatis-mapper/mybatis-config.xml b/src/main/resources/mybatis-mapper/mybatis-config.xml
new file mode 100644
index 0000000..54a6663
--- /dev/null
+++ b/src/main/resources/mybatis-mapper/mybatis-config.xml
@@ -0,0 +1,56 @@
+
+
+
+
+		
+	
+		
+		
+		
+		
+		 
+
+		
+		
+		
+		
+		
+		
+		
+		
+		
+		
+		
+	
+
+	
+
+
+
+
+
+    
+