package cokr.xit.fims.nxrp.service.bean;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import javax.annotation.Resource;

import org.springframework.stereotype.Component;

import cokr.xit.base.user.service.bean.UserBean;
import cokr.xit.fims.FimsConf;
import cokr.xit.fims.cmmn.CrdnSttsHstry;
import cokr.xit.fims.cmmn.service.bean.CrdnSttsHstryBean;
import cokr.xit.fims.crdn.Crdn;
import cokr.xit.fims.crdn.dao.CrdnInfoMapper;
import cokr.xit.fims.crdn.dao.CrdnUpdtMapper;
import cokr.xit.fims.excl.LevyExcl;
import cokr.xit.fims.excl.service.bean.LevyExclBean;
import cokr.xit.fims.levy.Levy;
import cokr.xit.fims.levy.LevyQuery;
import cokr.xit.fims.levy.dao.LevyMapper;
import cokr.xit.fims.mngt.Vltn;
import cokr.xit.fims.nxrp.NisIndivQuery;
import cokr.xit.fims.nxrp.dao.NisA01Mapper;
import cokr.xit.fims.rcvm.Rcvmt;
import cokr.xit.fims.rcvm.service.bean.RcvmtBean;
import cokr.xit.fims.rdca.Rdamt;
import cokr.xit.fims.rdca.service.bean.RdamtBean;
import cokr.xit.fims.sndb.SndbQuery;
import cokr.xit.fims.sndb.SndngDtl;
import cokr.xit.fims.sndb.dao.SndngDtlMapper;
import cokr.xit.foundation.Downloadable;
import cokr.xit.foundation.UserInfo;
import cokr.xit.foundation.component.ScheduledBean;
import cokr.xit.foundation.data.DataObject;
import cokr.xit.foundation.data.JSON;
import cokr.xit.interfaces.sntris.buga.Bu04BugaWSDTO;
import cokr.xit.interfaces.sntris.fileoffer.Ye22InputDTO;
import cokr.xit.interfaces.sntris.fileoffer.Ye22NoticeInfoDTO;
import cokr.xit.interfaces.sntris.prenotice.Bu18WebPreNoticeDTO;
import cokr.xit.interfaces.sntris.prenotice.Bu18WebReturnInfoDTO;
import cokr.xit.interfaces.sntris.service.bean.SntrisBean;

/**과태료 대장 단속 정보 등록 정보 관리 Bean
 *
 * <p>상세 설명:
 *
 * <pre>
 * ============ 변경 이력 ============
 * 2024-02-16	JoJH 최초 작성
 * ================================
 * </pre>
 */
@Component("snisBean")
public class SnisBean extends ScheduledBean {
	@Resource(name = "sntrisBean")
	private SntrisBean sntrisBean;
	/** 발송 상세 정보 DAO */
	@Resource(name = "sndngDtlMapper")
	private SndngDtlMapper sndngDtlMapper;
	/** 과태료 대장 단속 정보 등록 정보 DAO */
	@Resource(name = "nisA01Mapper")
	private NisA01Mapper nisA01Mapper;
	@Resource(name = "crdnInfoMapper")
	private CrdnInfoMapper crdnInfoMapper;
	/** 단속 대장 수정 정보 DAO */
	@Resource(name = "crdnUpdtMapper")
	private CrdnUpdtMapper crdnUpdtMapper;
	@Resource(name = "crdnSttsHstryBean")
	private CrdnSttsHstryBean crdnSttsHstryBean;
	/** 사용자 정보 DAO */
	@Resource(name="userBean")
	private UserBean userBean;
	@Resource(name = "levyMapper")
	private LevyMapper levyMapper;
	@Resource(name = "rcvmtBean")
	private RcvmtBean rcvmtBean;
	@Resource(name = "rdamtBean")
	private RdamtBean rdamtBean;
	@Resource(name = "levyExclBean")
	private LevyExclBean levyExclBean;

	private JSON json = new JSON();

	/**발송대상 사전통보를 서울 세외수입 시스템의 과태료대장에 등록한다.
	 * @param req 발송대상 사전통보 조회조건
	 */
	public String registerPreNotices(SndbQuery req) {
		// 발송대상 사전통보 과태료대장 등록
		Map<String, Bu18WebPreNoticeDTO> prenotices = getPreNotices(req); // 과태료대장 등록을 위한 사전통보 추출
		Map<String, Bu18WebReturnInfoDTO> results = sntrisBean.registerPreNotices(prenotices); // 발송대상 ID별 과태료 등록 응답
		return update(results);
	}

	private String update(Map<String, Bu18WebReturnInfoDTO> results) {
		log().debug("{} Bu18WebReturnInfoDTOs received", results.size());
		// 로컬 발송대상 상세 업데이트
		Set<String> ids = results.keySet(); // 발송대상 상세ID
		SndbQuery req = new SndbQuery().setSndngDtlIds(ids.toArray(new String[ids.size()]));
		List<SndngDtl> details = sndngDtlMapper.selectSndngDetails(req).stream()
			.map(detail -> update(
				results.get(
					detail.getSndngDtlId()),
					detail
				)
			)
			.filter(detail -> detail != null)
			.toList();
		sndngDtlMapper.updateSndngDtls(details);

		// 부과정보 업데이트
		Map<String, Bu18WebReturnInfoDTO> byCrdnIds = details.stream().collect(Collectors.toMap( // 단속 ID별 과태료 등록 응답
				detail -> detail.getCrdnId(),
				detail -> {
					String sndngDtlId = detail.getSndngDtlId();
					return results.get(sndngDtlId);
				}
			));
		ids = details.stream()  // 발송대상의 단속 ID
			.map(SndngDtl::getCrdnId)
			.collect(Collectors.toSet());
		List<Levy> levies = levyMapper.selectLevys(new LevyQuery().setCrdnIds(ids.toArray(new String[ids.size()]))).stream() // 업데이트를 위한 부과정보 가져오기
			.map(levy -> {
				Bu18WebReturnInfoDTO returned = byCrdnIds.get(levy.getCrdnId());
				return update(returned, levy);
			})
			.filter(levy -> levy != null)
			.toList();
		levyMapper.updateLevies(levies);

		// 단속정보 업데이트
		byCrdnIds.forEach((crdnId, returned) -> {
			Crdn crdn = new Crdn();
			crdn.setCrdnId(crdnId);
			crdn.setNxrpAcbKey(returned.getNoticeAk());

			crdnUpdtMapper.updateNxrpCrdnLinkYn(crdn); // 연계상태
			sndngDtlMapper.updateCrdnNxrpAcbKey(crdn); // 과태료대장키
		});

		return "[S] 작업이 정상 처리 되었습니다.";
	}

	private SndngDtl update(Bu18WebReturnInfoDTO returned, SndngDtl sndngDtl) {
		if (returned == null) return null;

		log().debug("returned\n{}", json.stringify(returned, true));

		sndngDtl.setLinkMngKey(returned.getNoticeAk());             // 연계 키 <- 세외수입 과태료대장키
//		sndngDtl.setLinkMngKey(returned.getBuAfk());             // 연계 키 <- 세외수입 부과키
		sndngDtl.setEpayno(returned.getEnapbuNo());                 // 전자납부번호
		setBankAccounts(returned.accountGetters(), sndngDtl.bankAccountSetters()); // 은행 / 계좌번호
		sndngDtl.setSndngDtlSttsCd("01");

		return sndngDtl;
	}

	private void setBankAccounts(Map<String, Supplier<String>> getters, List<BiConsumer<String, String>> setters) {
		getters.forEach((bank, account) -> {
			if (setters.isEmpty()) return;

			setters.remove(0).accept(bank, account.get().replace("-", ""));
		});
	}

	private Levy update(Bu18WebReturnInfoDTO returned, Levy levy) {
		if (returned == null) return null;

		levy.setNxrpLevyKey(returned.getBuAfk());    // 세외수입부과 키 <- 세외수입시스템 일련번호
		levy.setEpayno(returned.getEnapbuNo());      // 전자납부번호
		setBankAccounts(returned.accountGetters(), levy.bankAccountSetters()); // 은행 / 계좌번호

		return levy;
	}

	/**발송대상 상세ID별 사전통지를 반환한다.
	 * @param req 발송대상 사전통지 조회조건
	 * @return 발송대상 ID별 사전통지
	 */
	private Map<String, Bu18WebPreNoticeDTO> getPreNotices(SndbQuery req) {
		// 발송대상 ID 추출
		List<DataObject> targets = sndngDtlMapper.selectSndngTrgtDtlIds(req).stream()
			.filter(row -> "00".equals(row.get("SNDNG_DTL_STTS_CD")))
			.toList();
		if (targets.isEmpty()) return Collections.emptyMap();

		List<String> crdnIds = targets.stream()
			.map(target -> target.string("CRDN_ID"))
			.toList();
		Map<String, DataObject> crdnMap = nisA01Mapper.selectCrdnList(crdnIds).stream().collect(
			Collectors.toMap(info -> info.string("CRDN_ID"), info -> info)
		);
		// 발송대상 ID별 단속정보 병합
		targets.forEach(target -> {
			DataObject found = crdnMap.get(target.string("CRDN_ID"));
			if (found == null) return;

			target.putAll(found);
		});

		// 발송대상 ID별 사전통지 생성
		String now = dateFormats.now();
		UserInfo currentUser = currentUser();
		DataObject userInfo = userBean.getUserInfo(currentUser.getId());
		return targets.stream().collect(Collectors.toMap(
			target -> target.string("SNDNG_DTL_ID"),
			target -> createPreNotice(currentUser, now, userInfo, target)
		));
	}

	public Downloadable downloadPreNotices(SndbQuery req) {
		Map<String, Bu18WebPreNoticeDTO> prenotices = getPreNotices(req);
		return sntrisBean.getDownloadable(prenotices.values().stream().toList());
	}

	private Bu18WebPreNoticeDTO createPreNotice(UserInfo currentUser, String now, DataObject userInfo, DataObject crdnInfo) {
		Bu18WebPreNoticeDTO prenotice = new Bu18WebPreNoticeDTO();

		prenotice.setBigo(crdnInfo.string("ETC_CN"));                // 비고
		prenotice.setBookNo(crdnInfo.string("SNDNG_DTL_ID"));        // 자료키 <- 발송상세 아이디
		prenotice.setSidoCd("11");                                   // 시도코드
		prenotice.setSiguCd(currentUser.info("instCd"));             // 시구코드 <- 기관코드
//		prenotice.setBuseoCd(currentUser.getDeptCode());             // 서울 세외수입 시스템에서 시도, 시구, 작업자ID로 조회해서 설정

		prenotice.setDisposalContent("부과");                          // 처분내용
		prenotice.setRawBasis(Vltn.legalBasis(crdnInfo));            // 부과근거
		prenotice.setSemokCd(crdnInfo.string("ACNTG_SE_CD") + crdnInfo.string("TXITM_CD")); // 세목코드
		prenotice.setTaxAmt(toLong(crdnInfo.get("FFNLG_AMT")));      // 부과예정금액(감경전)
		prenotice.setPreTaxAmt(toLong(crdnInfo.get("ADVNTCE_AMT"))); // 자진납부금액(감경후)

		prenotice.setJukbalDtlAddr(Crdn.location(crdnInfo));         // 위반장소
		prenotice.setJukbalMgrNm(currentUser.getName());             // 적발담당자
		prenotice.setJukbalTarget(String.format("%s(%s)", crdnInfo.string("VLTN_ARTCL"), crdnInfo.string("VHRNO"))); // 적발내용
		prenotice.setJukbalTelNo(userInfo.string("TELNO"));          // 적발담당자 전화번호
		String crdnYmdhms = crdnInfo.string("CRDN_YMD") + crdnInfo.string("CRDN_TM");
		prenotice.setJukbalYm(crdnYmdhms.substring(0, 6));           // 적발년월
		prenotice.setJukbalYmdHs(crdnYmdhms.substring(0, 12));       // 위반일시

		prenotice.setNapId(blankIfEmpty(crdnInfo.string("RTPYR_NO")).replace("-", ""));// 납세자 주민번호
		prenotice.setNapNm(crdnInfo.string("RTPYR_NM"));             // 납세자 이름
		prenotice.setNapMobilNo(crdnInfo.string("RTPYR_MBL_TELNO")); // 납세자 휴대폰 번호
		prenotice.setNapTelNo(crdnInfo.string("RTPYR_TELNO"));       // 납세자 전화번호

		prenotice.setNapDzipAddr(crdnInfo.string("ADDR"));           // 도로명 우편주소
		prenotice.setNapDdtlAddr(crdnInfo.string("DTL_ADDR"));       // 도로명 상세주소
		prenotice.setNapDzipCd(crdnInfo.string("ZIP"));              // 도로명 우편번호
/*
		prenotice.setNapDrefAddr("");	// 도로명 참고항목

		prenotice.setNapZipAddr("");	// 지번 우편주소
		prenotice.setNapDtlAddr("");	// 지번 상세주소
		prenotice.setNapZipCd("");	// 지번 우편번호
*/
		prenotice.setSendYmd(crdnInfo.string("ADVNTCE_BGNG_YMD"));   // 사전통지일
		prenotice.setSubmitYmd(crdnInfo.string("ADVNTCE_DUDT_YMD")); // 의견제출기한

		prenotice.setSysGubun(FimsConf.get().getSystemGubun());      // 시스템 구분
		prenotice.setLastWorkId(notEmpty(userInfo.string("NTRIS_KEY"), "세외수입 인사대체키")); // 작업자 ID <- 세외수입 사용자키
		prenotice.setLastWorkNm(currentUser.getName());              // 작업자 이름
		prenotice.setWorkYmd(now);                                   // 작업일자

		return prenotice;
	}

	/**주어진 조건으로 서울 세외수입에 연계하여 과태료대장을 조회한다.
	 * @param input 조회조건
	 * @return 과태료대장 조회결과
	 */
	public List<Ye22NoticeInfoDTO> lookupPreNotices(SndngDtl sndngDtl) {
		Ye22InputDTO input = new Ye22InputDTO();
		String buAk = sndngDtl.getLinkMngKey(); // 연계관리키 <- 세외수입 과태료대장키
		input.setBuAk(buAk);
		input.setSystemGubun(FimsConf.get().getSystemGubun());
		List<Ye22NoticeInfoDTO> notices = sntrisBean.getPreNoticeList(input);
		update(notices);
		return notices;
	}
	
	public List<Ye22NoticeInfoDTO> lookupPreNotices(String buAk) {
		Ye22InputDTO input = new Ye22InputDTO();
		input.setBuAk(buAk);
		input.setSystemGubun(FimsConf.get().getSystemGubun());
		List<Ye22NoticeInfoDTO> notices = sntrisBean.getPreNoticeList(input);
		update(notices);
		return notices;
	}	

	private void update(Ye22NoticeInfoDTO notice, Levy levy) {
		if (notice == null || levy == null) return;

		boolean chenap = "체납".equals(notice.getBuStatusNm());

//		levy.setSggCd(notice.getSiguCd());            // 시군구 코드
		levy.setDeptCd(notice.getBuseoCd());          // 부서코드

		String semokCd = notice.getSemokCd();
		if (!semokCd.contains(levy.getTxitmCd()))
			levy.setTxitmCd(semokCd);                 // 세목코드
		levy.setLevyNo(notice.getTaxNo());            // 부과번호
		levy.setLevyYmd(notice.getTaxYmd());          // 부과일자
		levy.setDudtYmd(!chenap ? notice.getNapgiYmd() : notice.getCheNapgiYmd());        // 납기일자

		int taxAmt = !chenap ? (int)notice.getTaxAmt() : (int)notice.getCheAmt();
		levy.setLevyPcptax(taxAmt);  // 부과본세 <- 과세금액
		int gasanAmt = !chenap ? (int)notice.gasanAmt() : (int)notice.chenapGasanAmt();
		levy.setLevyAdamt(gasanAmt);    // 부과가산금
		levy.setDudtAftrAmt(null);

		levy.setDudtAftrYmd(notice.getNapgiAftYmd()); // 납기후일자
		levy.setTxtnThing(notice.getMulNm());         // 물건이름

		levy.setSzrYmd(notice.getApYmd());            // 압류일자
		levy.setEpayno(notice.getEnapbuNo());         // 전자납부번호
//		levy.setNxrpLevyKey(Long.toString(notice.getBuAk())); // 세외수입 부과키

		setBankAccounts(notice.accountGetters(), levy.bankAccountSetters()); // 은행 / 계좌번호
		if (chenap)
			levy.setNpmntRsnCd("09"); // 체납사유: 기타
	}

	private void update(Ye22NoticeInfoDTO notice, SndngDtl sndngDtl) {
		if (notice == null || sndngDtl == null) return;

		String status = notice.getBuStatusNm();
		boolean chenap = "체납".equals(status);

		sndngDtl.setSggCd(notice.getSiguCd());            // 시군구 코드
		sndngDtl.setTaxNo(notice.getNapseNo());           // 납세번호
		sndngDtl.setEpayno(notice.getEnapbuNo());         // 전자납부번호
		sndngDtl.setVhrno(notice.getMulNm());             // 차량번호

//		detail.setLevyNo(notice.getTaxNo());            // 부과번호
		sndngDtl.setLevyYmd(notice.getTaxYmd());          // 부과일자

		sndngDtl.setDudtYmd(!chenap ? notice.getNapgiYmd() : notice.getCheNapgiYmd());        // 납기일자
		sndngDtl.setDudtAftrYmd(notice.getNapgiAftYmd()); // 납기후일자

		sndngDtl.setPcptax(!chenap ? (int)notice.getTaxAmt() : (int)notice.getCheAmt()); // 본세
		sndngDtl.setAdamt(!chenap ? (int)notice.gasanAmt() : (int)notice.chenapGasanAmt());// 가산금
		sndngDtl.setLastAmt((int)notice.totalAmt());      // 최종금액
		sndngDtl.setDudtAftrAmt(null);                    // 납기후 금액

		setBankAccounts(notice.accountGetters(), sndngDtl.bankAccountSetters()); // 은행 / 계좌번호
		int sttsCd = toInt(sndngDtl.getSndngDtlSttsCd());
		if (sttsCd < 2)
    		sndngDtl.setSndngDtlSttsCd("02");	// 발송 상태 코드(FIM050) 02:가상계좌취득
		if ("완납".equals(status) && sttsCd != 9)
			sndngDtl.setSndngDtlSttsCd("09");   // 발송 상태 코드(FIM050) 09:수납
	}

	private void update(List<Ye22NoticeInfoDTO> notices) {
		log().debug("Ye22NoticeInfoDTO(s) received: {}", !isEmpty(notices) ? notices.size() : 0);
		// 상태가 부과,완납,감면,전액감액,신고종료인 것들만 처리
		notices = !isEmpty(notices) ?
			notices.stream().filter(notice -> "부과,완납,체납,감면,전액감액,신고종료".contains(notice.getBuStatusNm())).toList() :
			Collections.emptyList();
		if (isEmpty(notices)) return;

		// 세외수입 연계키별 부과정보
		Map<String, Levy> levies = levyMapper.selectLevys(new LevyQuery().setNxrpLevyKeys(notices.stream().map(notice -> Long.toString(notice.getBuAk())).toList()))
			.stream().collect(Collectors.toMap(
				Levy::getNxrpLevyKey,
				levy -> levy
			));
		// 단속ID별 단속정보 <- 부과정보
		List<String> crdnIDs = levies.values().stream().map(Levy::getCrdnId).toList();
		Map<String, Crdn> crdns = crdnInfoMapper.selectCrdns(crdnIDs).stream().collect(Collectors.toMap(
			Crdn::getCrdnId,
			crdn -> crdn
		));
		// 단속ID별 발송상세 정보
		List<SndngDtl> sndngDtls = sndngDtlMapper.selectSndngDetails(new SndbQuery().setCrdnIds(crdnIDs.toArray(new String[crdnIDs.size()])));
		Map<String, SndngDtl> sndngDetails = sndngDtls.stream().collect(Collectors.toMap(
				SndngDtl::getCrdnId,
				sndngDtl -> sndngDtl
			));

		for (Ye22NoticeInfoDTO notice: notices) {
			log().debug("Ye22NoticeInfoDTO of\n{}", json.stringify(notice, true));

			String linkMngKey = Long.toString(notice.getBuAk()); // 세외수입 연계키
			Levy levy = levies.get(linkMngKey);
			if (levy == null) continue;
			String crdnId = levy.getCrdnId();
			Crdn crdn = crdns.get(crdnId);
			if (crdn == null) continue;

			String statusCode = notice.getBuStatusCd(),          // 과태료 상태
				   status = notice.getBuStatusNm();              // 상태이름
			int rcvmtAmt = (int)notice.totalAmt();
			log().debug("세외수입 연계키: {}, 상태: {}({})", linkMngKey, status, statusCode);

			update(notice, levy);          // 부과정보에 적용
			levyMapper.updateLevy(levy);

			if ("부과".equals(status)) {
				// 상태 == 부과 && 구분 == 신고  -> 사전부과(23)
				// 상태 == 부과 && 구분 == 수시분 -> 본부과(51)
				String noticeStatus = "신고".equals(notice.getTaxGubunNm()) ? "23" : "51",
					   crdnStatus = crdn.getCrdnSttsCd();
				switch (noticeStatus) {
				case "23":
					if (crdnStatus.compareTo(noticeStatus) < 0) // 현재상태가 사전부과보다 작을 경우
						crdnStatus = noticeStatus;
					break;
				case "51":
					if (crdnStatus.compareTo(noticeStatus) < 0) // 현재상태가 본부과보다 작을 경우
						crdnStatus = noticeStatus;
					break;
				default: break;
				}
				if (!equals(crdnStatus, crdn.getCrdnSttsCd())) {
					CrdnSttsHstry statusHistory = CrdnSttsHstry.create(crdnStatus, null, crdn);
					crdnSttsHstryBean.create(statusHistory);
					crdnSttsHstryBean.updateCrdn(statusHistory);
				}
			}

			if ("완납".equals(status)) {
				Rcvmt rcvmt = Rcvmt.create(levy); // 수납등록
				rcvmt.setRcvmtAmt(rcvmtAmt);             // 수납금액
				if ("1".equals(statusCode)) { // 완납
					String rcvmtSttsCd = switch (crdn.getCrdnSttsCd()) {
					case "23", "31", "42", "43", "44", "45" -> "1"; // 사전부과, 의견제출, 사전통보발송, 사전통보완료 -> 사전통보수납
					case "51" -> "2";       // 부과 -> 부과수납
					case "55" -> "3";       // 압류 -> 압류수납
					default -> null;
					};
					if (rcvmtSttsCd != null)
						rcvmt.setRcvmtSttsCd(rcvmtSttsCd);
				}
				rcvmtBean.createRcvmt(rcvmt);

				crdn.setRcvmtAmt(rcvmtAmt); // 수납금액
				crdnUpdtMapper.updateEditCrdn(crdn);
			}

			SndngDtl sndngDtl = sndngDetails.get(crdnId);
			update(notice, sndngDtl); // 발송상세에 적용
			if (sndngDtl != null) {
				sndngDtl.setLastAmt(levy.getLastAmt());
				sndngDtlMapper.updateSndngDtl(sndngDtl);
			}

			if ("감면".equals(status)) { // 감면 -> 감액 등록
				Rdamt rdamt = Rdamt.create(levy);
				rdamtBean.createRdamt(rdamt);
			}

			if ("전액감액,신고종료".contains(status)) { // 전액감액,  -> 부과취소
				LevyExcl levyExcl = new LevyExcl();
				levyExcl.setCrdnId(crdnId);
				levyExcl.setLevyExclSeCd("3");    // 구분: 부과취소
				levyExcl.setLevyExclRsnCd("150"); // 사유: 부과취소
				levyExcl.setLevyExclYmd(dateFormats.today());
				String msg = levyExclBean.createLevyExcl(levyExcl);
				log().debug(msg);
			}
		}
	}

	public void lookupPreNotices(SndbQuery req) {
		if (req == null)
			req = new SndbQuery();
		log().debug("Starting getPreNotices()...");
		for (SndngDtl sndngDtl: sndngDtlMapper.selectSndngDtlsToSync(req))
			lookupPreNotices(sndngDtl);
	}

	/**과태료 대장 단속 정보 등록 정보를 등록한다.
	 * @param nisIndivA01 과태료 대장 단속 정보 등록
	 * @return 저장 여부
	 * <ul><li>저장됐으면 true</li>
	 *     <li>그렇지 않으면 false</li>
	 * </ul>
	 */
	public String createBu04Buga(NisIndivQuery req) {
		String userId = currentUser().getId();
		DataObject userInfo = userBean.getUserInfo(userId); // 1. 사용자 정보
		String userDept = userInfo.string("DEPT_CD");
		DataObject crdnInfo = nisA01Mapper.selectCrdnInfo(req.getCrdnId()); // 3. 세외수입 과태료대장에 연계할 단속 정보
		String
			sggCd = crdnInfo.string("SGG_CD"),
			crdnId = crdnInfo.string("CRDN_ID"),
			vhrno = crdnInfo.string("VHRNO"),
			sndngYmd = req.getSndngYmd(),
			taskSeCd = crdnInfo.string("TASK_SE_CD"); // 업무 구분 코드

		Bu04BugaWSDTO buga = new Bu04BugaWSDTO();
		buga.setTaxGubun("3"); //1: 정기, 2: 수시, 3: 신고
		buga.setTaxNo("");
		buga.setTaxYm(sndngYmd.substring(0, 6));
		buga.setTaxYmd(sndngYmd);

		buga.setBigo(crdnInfo.string("ETC_CN"));
		buga.setBookNo(crdnId);
		buga.setBunhalCnt(0);
		buga.setBuseoCd(userDept);
		buga.setEtc1("");
		buga.setGasanAmtSkipGubun("");
		buga.setGasanRateGubun("");
		buga.setGigum(0);
		buga.setGigumGasanAmt(0);
		buga.setGigumIja(0);
		buga.setGukse(0);
		buga.setGukseGasanAmt(0);
		buga.setGukseIja(0);
		buga.setGuse(0);
		buga.setGuseGasanAmt(0);
		buga.setGuseIja(0);

		if ("PES".equals(taskSeCd)) {
			buga.setHangmok1("차고지 외 밤샘주차");
			buga.setHangmok2("위반일시 : " + crdnInfo.string("CRDN_YMD_TM_MASK"));
			buga.setHangmok3("위반장소 : " + crdnInfo.string("CRDN_PLC"));
			buga.setHangmok4("화물자동차운수사업법");

			switch (blankIfEmpty(crdnInfo.string("CRDN_INPT_SE_CD"))) {
			case "06": buga.setHangmok5("관내단속"); break;
			case "08": buga.setHangmok5(crdnInfo.string("ETC_CN")); break;
			default: buga.setHangmok5(""); break;
			}
		} else if ("DPV,ECA".contains(taskSeCd)) {
			buga.setHangmok1("위반일시 : " + crdnInfo.string("CRDN_YMD_TM_MASK"));		// 관리항목1
			buga.setHangmok2("");													// 관리항목2
			buga.setHangmok3("위반장소 : " + crdnInfo.string("CRDN_PLC"));				// 관리항목3
			buga.setHangmok4("");													// 관리항목4
			buga.setHangmok5("위반항목 : " + crdnInfo.string("VLTN_ARTCL"));			// 관리항목5
		} else
			throw new RuntimeException("지정되지 않은 업무 구분코드가 입력되었습니다.");		// 예외를 발생시켜서 DB Rollback
		buga.setHangmok6(sggCd + crdnId);	// 관리6항목

		buga.setHdongCd(crdnInfo.string("DONG_CD"));
		buga.setLastWorkDate(sndngYmd);
		buga.setLastWorkId(userId);

		buga.setMulGubun("02"); // 차량
		buga.setMulNm(vhrno);
		buga.setMulSpcCd("9"); // 무번지
/*
		buga.setMulAptNm("");
		buga.setMulBan("");
		buga.setMulBdongriCd("");
		buga.setMulBon("");
		buga.setMulBu("");
		buga.setMulDong("");
		buga.setMulDtlAddr("");
		buga.setMulHosu("");
		buga.setMulOcrSiguCd("");
		buga.setMulTong("");
		buga.setMulZipAddr("");
		buga.setMulZipCd("");
*/
		String pyrSeCd = Map.of(
				"01", "01", // 주민(01)  -> 개인(01)
				"02", "12", // 외국인(02) -> 외국인(12)
				"03", "30", // 법인(03)  -> 법인(30)
				"04", "32"  // 사업자(04) -> 사업자(32)
			).getOrDefault(crdnInfo.string("RTPYR_SE_CD"), "99"); // 기타(05) -> 기타(99)
		buga.setNapGubun(pyrSeCd);
		String rtpyrNo = crdnInfo.string("RTPYR_NO");
		buga.setNapId(rtpyrNo);
		String rtpyrNm = crdnInfo.string("RTPYR_NM");
		buga.setNapNm(rtpyrNm);
		buga.setNapMobilNo(crdnInfo.string("RTPYR_MBL_TELNO"));
		buga.setNapTelNo(crdnInfo.string("RTPYR_TELNO"));
		buga.setNapEmail(crdnInfo.string("RTPYR_EML"));

		buga.setNapBldBon(crdnInfo.string("BMNO"));
		buga.setNapBldBu(crdnInfo.string("BSNO"));

		buga.setNapDzipCd(crdnInfo.string("ZIP"));
		buga.setNapDzipAddr(crdnInfo.string("ADDR"));
		buga.setNapDdtlAddr(crdnInfo.string("DTL_ADDR"));

		buga.setNapDoroAddr("");
		buga.setNapDoroCd("");
		buga.setNapDrefAddr("");
		buga.setNapDtlAddr("");

		buga.setNapUndYn("1".equals(crdnInfo.string("UDGD_SE_CD")) ? "Y" : "N");
		buga.setNapZipAddr("");
		buga.setNapZipCd(crdnInfo.string("ZIP"));

		buga.setOcrBuseoCd("");
		buga.setOcrSiguCd("");
		buga.setResideStatus("");
		buga.setRowStatus("");
		buga.setSemokCd(crdnInfo.string("TXITM_CD"));
		buga.setSendYmd(sndngYmd);
		buga.setSidoCd("11"); // 고정
		buga.setSiguCd(sggCd);
		buga.setSise(0);
		buga.setSiseGasanAmt(0);
		buga.setSiseIja(0);
		buga.setSokwanBuseoCd("");
		buga.setSokwanMgrNm("");
		buga.setSokwanTelNo("");
		buga.setSpecialRate(0);
		buga.setSpecialRateApplyCnt(0);
		buga.setSpecialRateApplySayu("");
		buga.setSpecialRateHighRate(0);
		buga.setSuBuseoCd("");
		buga.setSysGubun("");

		buga.setTaxAmt(0);
		buga.setVatAmt(0);
		buga.setNapbuYmd("");
		buga.setNapgiAftYmd("");
		buga.setNapgiYmd("");

		try {
			sntrisBean.register(userId, buga);
			return "";
		} catch (Exception e) {
			throw runtimeException(e);
		}
	}
}