package com.bizcare.agent; import jakarta.persistence.EntityManager; import lombok.RequiredArgsConstructor; import org.springframework.core.io.Resource; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import java.math.BigDecimal; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.time.*; import java.time.format.DateTimeFormatter; import java.util.*; import java.util.stream.Collectors; /** * 접수관리 — billing_react /order(전체 리스트) 기능을 Care Homes 에 이식. */ @RestController @RequestMapping("/api/agent/homes") @RequiredArgsConstructor class CareReceptionController { private final EntityManager em; private final FileStorageService fileStorage; static final List PROGRESS_STATUSES = ReceptionStatuses.progressCodes(); static final List SETTLE_STATUSES = ReceptionStatuses.centerCodes(); static final List GIFT_STATUSES = ReceptionStatuses.giftCodes(); static final List CARRIERS = List.of("KT", "SKB", "LGU+", "SKT", "기타"); static final List REGIONS = List.of( "서울", "경기", "인천", "강원", "충북", "충남", "대전", "전북", "전남", "광주", "경북", "대구", "경남", "울산", "부산", "제주"); @GetMapping("/receptions") ApiResponse list(@AuthenticationPrincipal JwtUser u, @RequestParam Map f, @RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "50") int size) { List filtered = filterReceptions(u, f); Map counts = new LinkedHashMap<>(); counts.put("ALL", (long) filtered.size()); for (String st : PROGRESS_STATUSES) { counts.put(st, filtered.stream() .filter(r -> st.equals(ReceptionStatuses.normalizeProgress(r.getProgressStatus()))) .count()); } int fromIdx = Math.max(0, page) * Math.max(1, size); int limit = Math.min(Math.max(size, 1), 200); List> list = filtered.stream().skip(fromIdx).limit(limit) .map(this::toMap).toList(); Map data = new LinkedHashMap<>(); data.put("total", filtered.size()); data.put("list", list); data.put("counts", counts); data.put("progressStatuses", PROGRESS_STATUSES); data.put("settleStatuses", SETTLE_STATUSES); data.put("giftStatuses", GIFT_STATUSES); data.put("progressStatusLabels", ReceptionStatuses.PROGRESS); data.put("giftStatusLabels", ReceptionStatuses.GIFT); data.put("settleStatusLabels", ReceptionStatuses.CENTER); data.put("giftPlaceLabels", ReceptionStatuses.GIFT_PLACE); data.put("carriers", distinct(filtered, HomesReception::getCarrier, CARRIERS)); data.put("branches", distinct(filtered, HomesReception::getBranch, List.of())); data.put("counselors", distinct(filtered, HomesReception::getCounselor, List.of())); data.put("processors", distinct(filtered, HomesReception::getProcessor, List.of())); data.put("regions", REGIONS); data.put("giftCategories", distinct(filtered, HomesReception::getGiftCategory, List.of())); data.put("productCategories", distinct(filtered, HomesReception::getProductCategory, List.of())); return ApiResponse.ok(data); } @GetMapping(value = "/receptions/excel", produces = "text/csv; charset=UTF-8") org.springframework.http.ResponseEntity excel(@AuthenticationPrincipal JwtUser u, @RequestParam Map f) { List filtered = filterReceptions(u, f); StringBuilder csv = new StringBuilder("\uFEFF"); csv.append("접수번호,접수일,상담자,지점,통신사,사은품분류,상품분류,상품명,고객명,연락처,지역,진행상황,주문번호,입고정보,처리자,세부진행,지급구분,금액,정산상태,개통일,사은품일,사은품진행,지급처,약정,개통번호,메모\n"); for (HomesReception r : filtered) { csv.append(csvCell(r.getId())).append(',') .append(csvCell(fmtDt(r.getOrderDate()))).append(',') .append(csvCell(r.getCounselor())).append(',') .append(csvCell(r.getBranch())).append(',') .append(csvCell(r.getCarrier())).append(',') .append(csvCell(r.getGiftCategory())).append(',') .append(csvCell(r.getProductCategory())).append(',') .append(csvCell(r.getProductName())).append(',') .append(csvCell(r.getCustomerName())).append(',') .append(csvCell(r.getPhone())).append(',') .append(csvCell(r.getRegion())).append(',') .append(csvCell(r.getProgressStatus())).append(',') .append(csvCell(r.getOrderNumber())).append(',') .append(csvCell(r.getInboundInfo())).append(',') .append(csvCell(r.getProcessor())).append(',') .append(csvCell(r.getDetailProgress())).append(',') .append(csvCell(r.getPaymentType())).append(',') .append(csvCell(r.getAmount())).append(',') .append(csvCell(r.getSettleStatus())).append(',') .append(csvCell(r.getOpenDate())).append(',') .append(csvCell(r.getGiftDate())).append(',') .append(csvCell(r.getGiftStatus())).append(',') .append(csvCell(r.getGiftPlace())).append(',') .append(csvCell(r.getAgreementName())).append(',') .append(csvCell(r.getContractNumber())).append(',') .append(csvCell(r.getMemo())).append('\n'); } return org.springframework.http.ResponseEntity.ok() .header(org.springframework.http.HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=receptions.csv") .body(csv.toString()); } @GetMapping("/receptions/{id}") ApiResponse get(@PathVariable Long id, @AuthenticationPrincipal JwtUser u) { HomesReception r = findOwned(id, u); if (r == null) return ApiResponse.fail("접수를 찾을 수 없습니다."); return ApiResponse.ok(toProgressDetail(r, u)); } /** 업무 진행내역 저장 — billing_react POST /order/{id}/progress */ @PostMapping("/receptions/{id}/progress") @Transactional ApiResponse progress(@PathVariable Long id, @RequestBody Map body, @AuthenticationPrincipal JwtUser u) { HomesReception r = findOwned(id, u); if (r == null) return ApiResponse.fail("접수를 찾을 수 없습니다."); if (body.containsKey("postIt")) r.setPostIt(str(body.get("postIt"))); if (body.containsKey("postAdd")) r.setPostAdd(str(body.get("postAdd"))); if (body.containsKey("contractNumber")) r.setContractNumber(str(body.get("contractNumber"))); if (body.containsKey("installSchedule")) r.setInstallSchedule(str(body.get("installSchedule"))); if (body.containsKey("customerLoginId") || body.containsKey("customerid")) { Object v = body.containsKey("customerLoginId") ? body.get("customerLoginId") : body.get("customerid"); r.setCustomerLoginId(str(v)); } if (body.containsKey("newTel")) r.setNewTel(str(body.get("newTel"))); if (body.containsKey("multiLine")) r.setMultiLine(Boolean.TRUE.equals(body.get("multiLine")) || "true".equalsIgnoreCase(String.valueOf(body.get("multiLine")))); if (body.containsKey("addPayer")) r.setAddPayer(Boolean.TRUE.equals(body.get("addPayer")) || "true".equalsIgnoreCase(String.valueOf(body.get("addPayer")))); if (body.containsKey("record") || body.containsKey("recordFlag")) { Object v = body.containsKey("recordFlag") ? body.get("recordFlag") : body.get("record"); r.setRecordFlag(Boolean.TRUE.equals(v) || "true".equalsIgnoreCase(String.valueOf(v))); } if (body.containsKey("adminMemo")) r.setAdminMemo(str(body.get("adminMemo"))); if (body.containsKey("openDate") || body.containsKey("opended")) { Object v = body.containsKey("openDate") ? body.get("openDate") : body.get("opended"); r.setOpenDate(parseDate(str(v))); } if (body.containsKey("cancelDate") || body.containsKey("canceled")) { Object v = body.containsKey("cancelDate") ? body.get("cancelDate") : body.get("canceled"); r.setCancelDate(parseDate(str(v))); } if (body.containsKey("giftPlace") || body.containsKey("place")) { Object v = body.containsKey("giftPlace") ? body.get("giftPlace") : body.get("place"); r.setGiftPlace(str(v)); } if (body.containsKey("giftRequested") || body.containsKey("requested")) { Object v = body.containsKey("giftRequested") ? body.get("giftRequested") : body.get("requested"); r.setGiftRequested(parseDate(str(v))); } if (body.containsKey("giftSended") || body.containsKey("sended")) { Object v = body.containsKey("giftSended") ? body.get("giftSended") : body.get("sended"); r.setGiftSended(parseDate(str(v))); } if (Boolean.TRUE.equals(body.get("changeOrderDate")) && body.get("orderDate") != null) { r.setOrderDate(parseDateTime(str(body.get("orderDate")))); } String newStatus = str(body.get("status")); if (blank(newStatus)) newStatus = str(body.get("progressStatus")); String progressMemo = str(body.get("memo")); if (blank(progressMemo)) progressMemo = str(body.get("progressLog")); boolean adminOnly = Boolean.TRUE.equals(body.get("adminOnly")) || "true".equalsIgnoreCase(String.valueOf(body.get("adminOnly"))); boolean statusChanged = false; String prevProgress = ReceptionStatuses.normalizeProgress(r.getProgressStatus()); if (!blank(newStatus)) { newStatus = ReceptionStatuses.normalizeProgress(newStatus); if (!Objects.equals(newStatus, prevProgress)) { Set allowed = ReceptionStatuses.orderStatusTransitions(prevProgress); if (("f23".equals(prevProgress) || "f24".equals(prevProgress) || "f25".equals(prevProgress)) && !allowed.isEmpty() && !allowed.contains(newStatus)) { return ApiResponse.fail("정산 관련 상태는 정산현황 메뉴에서 변경하세요. (현재: " + ReceptionStatuses.progressLabel(prevProgress) + ")"); } r.setProgressStatus(newStatus); statusChanged = true; applyOpenSideEffects(r, newStatus, u); } } String giftStatus = str(body.get("giftStatus")); if (body.containsKey("giftStatus")) { giftStatus = ReceptionStatuses.normalizeGift(giftStatus); String prevGift = ReceptionStatuses.normalizeGift(r.getGiftStatus()); if (!Objects.equals(giftStatus, prevGift)) { r.setGiftStatus(blank(giftStatus) ? null : giftStatus); if ("g01".equals(giftStatus) && r.getGiftRequested() == null) { r.setGiftRequested(LocalDate.now(ZoneId.of("Asia/Seoul"))); } if ("g07".equals(giftStatus)) { if (r.getGiftSended() == null) { LocalDate today = LocalDate.now(ZoneId.of("Asia/Seoul")); r.setGiftSended(today); r.setGiftDate(today); } String cur = ReceptionStatuses.normalizeProgress(r.getProgressStatus()); if ("f14".equals(cur)) { r.setProgressStatus("f23"); r.setSettleStatus("h01"); statusChanged = true; appendLog(r, "f23", "사은품 지급완료 후 정산대기 자동변경", u, false); } } appendGiftLog(r, blank(giftStatus) ? prevGift : giftStatus, str(body.get("giftProgressLog")), u); } else if (!blank(str(body.get("giftProgressLog")))) { appendGiftLog(r, r.getGiftStatus(), str(body.get("giftProgressLog")), u); } } else if (!blank(str(body.get("giftProgressLog")))) { appendGiftLog(r, r.getGiftStatus(), str(body.get("giftProgressLog")), u); } String centerStatus = str(body.containsKey("centerStatus") ? body.get("centerStatus") : body.get("settleStatus")); if (!blank(centerStatus)) { centerStatus = ReceptionStatuses.normalizeCenter(centerStatus); if (!Objects.equals(centerStatus, ReceptionStatuses.normalizeCenter(r.getSettleStatus()))) { r.setSettleStatus(centerStatus); } } if (statusChanged || !blank(progressMemo)) { appendLog(r, blank(newStatus) ? r.getProgressStatus() : newStatus, progressMemo, u, adminOnly); } if (body.containsKey("processor") && !blank(str(body.get("processor")))) { r.setProcessor(str(body.get("processor"))); } else if (statusChanged) { r.setProcessor(resolveMemberName(u)); } writeLog(u, "RECEPTION", "접수-진행", "접수 #" + r.getId() + " [" + nullToEmpty(r.getCustomerName()) + "] 진행내역 저장"); return ApiResponse.ok(toProgressDetail(r, u)); } /** f14 개통완료 시 사은품 g01 + 입력처 h01 + 개통일 */ private void applyOpenSideEffects(HomesReception r, String newStatus, JwtUser u) { if ("f22".equals(newStatus) && r.getCancelDate() == null) { r.setCancelDate(LocalDate.now(ZoneId.of("Asia/Seoul"))); } if (ReceptionStatuses.isPreCalc(newStatus)) { r.setReceptionCostId(null); } if (!"f14".equals(newStatus)) return; if (r.getOpenDate() == null) { r.setOpenDate(LocalDate.now(ZoneId.of("Asia/Seoul"))); } String gift = ReceptionStatuses.normalizeGift(r.getGiftStatus()); if (blank(gift)) { r.setGiftStatus("g01"); if (r.getGiftRequested() == null) { r.setGiftRequested(LocalDate.now(ZoneId.of("Asia/Seoul"))); } appendGiftLog(r, "g01", "개통완료 후 자동변경", u); } String center = ReceptionStatuses.normalizeCenter(r.getSettleStatus()); r.setSettleStatus(blank(center) ? "h01" : center); } @DeleteMapping("/receptions/progress-log/{logId}") @Transactional ApiResponse deleteProgressLog(@PathVariable Long logId, @AuthenticationPrincipal JwtUser u) { HomesReceptionProgressLog log = em.find(HomesReceptionProgressLog.class, logId); if (log == null || !Objects.equals(log.getGroupId(), u.getGroupId())) return ApiResponse.fail("로그를 찾을 수 없습니다."); em.remove(log); return ApiResponse.ok(null); } @DeleteMapping("/receptions/gift-progress-log/{logId}") @Transactional ApiResponse deleteGiftProgressLog(@PathVariable Long logId, @AuthenticationPrincipal JwtUser u) { HomesReceptionGiftProgressLog log = em.find(HomesReceptionGiftProgressLog.class, logId); if (log == null || !Objects.equals(log.getGroupId(), u.getGroupId())) return ApiResponse.fail("로그를 찾을 수 없습니다."); em.remove(log); return ApiResponse.ok(null); } @PostMapping("/receptions") @Transactional ApiResponse create(@RequestBody Map body, @AuthenticationPrincipal JwtUser u) { HomesReception r = new HomesReception(); r.setGroupId(u.getGroupId()); apply(r, body); if (r.getOrderDate() == null) r.setOrderDate(LocalDateTime.now(ZoneId.of("Asia/Seoul"))); if (blank(r.getProgressStatus())) r.setProgressStatus("f01"); else r.setProgressStatus(ReceptionStatuses.normalizeProgress(r.getProgressStatus())); if (blank(r.getSettleStatus())) r.setSettleStatus("h01"); else r.setSettleStatus(ReceptionStatuses.normalizeCenter(r.getSettleStatus())); if (blank(r.getGiftStatus()) || "미지급".equals(r.getGiftStatus())) r.setGiftStatus(null); else r.setGiftStatus(ReceptionStatuses.normalizeGift(r.getGiftStatus())); if (blank(r.getOrderNumber())) r.setOrderNumber(genOrderNumber()); em.persist(r); em.flush(); appendLog(r, r.getProgressStatus(), "접수 등록", u); writeLog(u, "RECEPTION", "접수-등록", "접수 [" + nullToEmpty(r.getCustomerName()) + "] 등록하였습니다."); return ApiResponse.ok(toMap(r)); } @PutMapping("/receptions/{id}") @Transactional ApiResponse update(@PathVariable Long id, @RequestBody Map body, @AuthenticationPrincipal JwtUser u) { HomesReception r = findOwned(id, u); if (r == null) return ApiResponse.fail("접수를 찾을 수 없습니다."); String prev = r.getProgressStatus(); apply(r, body); if (!Objects.equals(prev, r.getProgressStatus())) { appendLog(r, r.getProgressStatus(), str(body.get("progressMemo")), u); } writeLog(u, "RECEPTION", "접수-수정", "접수 [" + nullToEmpty(r.getCustomerName()) + "] 수정하였습니다."); return ApiResponse.ok(toMap(r)); } @DeleteMapping("/receptions/{id}") @Transactional ApiResponse delete(@PathVariable Long id, @AuthenticationPrincipal JwtUser u) { HomesReception r = findOwned(id, u); if (r == null) return ApiResponse.fail("접수를 찾을 수 없습니다."); writeLog(u, "RECEPTION", "접수-삭제", "접수 [" + nullToEmpty(r.getCustomerName()) + "] 삭제하였습니다."); em.remove(r); return ApiResponse.ok(null); } @PostMapping("/receptions/delete") @Transactional ApiResponse bulkDelete(@RequestBody Map body, @AuthenticationPrincipal JwtUser u) { List ids = toIds(body.get("ids")); int n = 0; for (Long id : ids) { HomesReception r = findOwned(id, u); if (r == null) continue; writeLog(u, "RECEPTION", "접수-삭제", "접수 [" + nullToEmpty(r.getCustomerName()) + "] 삭제하였습니다."); em.remove(r); n++; } return ApiResponse.ok(Map.of("count", n)); } @PostMapping("/receptions/bulk-progress") @Transactional ApiResponse bulkProgress(@RequestBody Map body, @AuthenticationPrincipal JwtUser u) { List ids = toIds(body.get("ids")); if (ids.isEmpty()) return ApiResponse.fail("리스트를 선택하세요."); String status = str(body.get("status")); if (blank(status)) return ApiResponse.fail("진행상황을 선택하세요."); String memo = "[일괄처리] " + nullToEmpty(str(body.get("memo"))); boolean adminOnly = Boolean.TRUE.equals(body.get("adminOnly")); String processor = firstNonBlank(str(body.get("centerId")), str(body.get("processor")), str(body.get("centerName"))); LocalDate opened = parseDate(str(body.containsKey("opended") ? body.get("opended") : body.containsKey("opened") ? body.get("opened") : body.get("openDate"))); LocalDate canceled = parseDate(str(body.containsKey("canceled") ? body.get("canceled") : body.get("cancelDate"))); List rows = new ArrayList<>(); String baseStatus = null; for (Long id : ids) { HomesReception r = findOwned(id, u); if (r == null) return ApiResponse.fail("접수 리스트가 올바르지 않습니다."); if (baseStatus != null && !Objects.equals(baseStatus, r.getProgressStatus())) { return ApiResponse.fail("진행상황이 다른 접수가 포함되어 있습니다."); } baseStatus = r.getProgressStatus(); rows.add(r); } int n = 0; for (HomesReception r : rows) { String normalized = ReceptionStatuses.normalizeProgress(status); r.setProgressStatus(normalized); if (!blank(processor)) r.setProcessor(processor); if (opened != null) r.setOpenDate(opened); if (canceled != null) r.setCancelDate(canceled); applyOpenSideEffects(r, normalized, u); appendLog(r, normalized, memo, u, adminOnly); n++; } writeLog(u, "RECEPTION", "접수-일괄진행", "접수 " + n + "건 진행상황을 [" + status + "]로 변경하였습니다."); return ApiResponse.ok(Map.of("count", n)); } @PostMapping("/receptions/bulk-gift") @Transactional ApiResponse bulkGift(@RequestBody Map body, @AuthenticationPrincipal JwtUser u) { List ids = toIds(body.get("ids")); if (ids.isEmpty()) return ApiResponse.fail("리스트를 선택하세요."); String status = str(body.get("status")); if (blank(status)) return ApiResponse.fail("사은품진행상황을 선택하세요."); String place = str(body.get("place")); String giftMemo = "[일괄처리] " + nullToEmpty(str( body.containsKey("giftProgressLog") ? body.get("giftProgressLog") : body.get("memo"))); String deliveryCompany = str(body.get("deliveryCompany")); String trackingNumber = str(body.get("trackingNumber")); LocalDate requested = parseDate(str(body.containsKey("requested") ? body.get("requested") : body.get("giftRequested"))); LocalDate sended = parseDate(str(body.containsKey("sended") ? body.get("sended") : body.containsKey("giftSended") ? body.get("giftSended") : body.get("giftDate"))); List rows = new ArrayList<>(); String basePlace = null; for (Long id : ids) { HomesReception r = findOwned(id, u); if (r == null) return ApiResponse.fail("선택한 접수가 올바르지 않습니다."); String p = r.getGiftPlace(); if (basePlace == null) { basePlace = p; } else { boolean baseSelf = "b02".equals(basePlace) || "영업점".equals(basePlace); boolean curSelf = "b02".equals(p) || "영업점".equals(p); if (baseSelf != curSelf) return ApiResponse.fail("사은품 지급처가 다른 접수가 포함되어 있습니다."); } rows.add(r); } int n = 0; for (HomesReception r : rows) { String normalized = ReceptionStatuses.normalizeGift(status); r.setGiftStatus(blank(normalized) ? null : normalized); if (!blank(place)) r.setGiftPlace(place); if (!blank(deliveryCompany)) r.setDeliveryCompany(deliveryCompany); if (!blank(trackingNumber)) r.setTrackingNumber(trackingNumber); if (requested != null) r.setGiftRequested(requested); if (sended != null) { r.setGiftSended(sended); r.setGiftDate(sended); } if ("g01".equals(normalized) && r.getGiftRequested() == null) { r.setGiftRequested(LocalDate.now(ZoneId.of("Asia/Seoul"))); } if ("g07".equals(normalized)) { if (r.getGiftSended() == null) { LocalDate today = LocalDate.now(ZoneId.of("Asia/Seoul")); r.setGiftSended(today); r.setGiftDate(today); } if ("f14".equals(ReceptionStatuses.normalizeProgress(r.getProgressStatus()))) { r.setProgressStatus("f23"); r.setSettleStatus("h01"); appendLog(r, "f23", "사은품 일괄지급완료 후 정산대기", u, false); } } appendGiftLog(r, blank(normalized) ? status : normalized, giftMemo, u); n++; } writeLog(u, "RECEPTION", "사은품-일괄", "접수 " + n + "건 사은품진행을 [" + status + "]로 변경하였습니다."); return ApiResponse.ok(Map.of("count", n)); } @PostMapping("/receptions/bulk-center-progress") @Transactional ApiResponse bulkCenterProgress(@RequestBody Map body, @AuthenticationPrincipal JwtUser u) { List ids = toIds(body.get("ids")); if (ids.isEmpty()) return ApiResponse.fail("리스트를 선택하세요."); List rows = new ArrayList<>(); for (Long id : ids) { HomesReception r = findOwned(id, u); if (r == null) return ApiResponse.fail("접수 리스트가 올바르지 않습니다."); // 입력처 정산이 비어있거나 정산대기(h01)인 건만 대상 String st = ReceptionStatuses.normalizeCenter(r.getSettleStatus()); if (!blank(st) && !"h01".equals(st)) { return ApiResponse.fail("접수 리스트가 올바르지 않습니다."); } rows.add(r); } int n = 0; for (HomesReception r : rows) { r.setSettleStatus("h01"); appendLog(r, r.getProgressStatus(), "[일괄변경] 입력처 정산대기", u, false); n++; } writeLog(u, "RECEPTION", "입력처-일괄", "접수 " + n + "건을 입력처 정산대기로 변경하였습니다."); return ApiResponse.ok(Map.of("count", n)); } /** 신규신청 통신사 목록 (billing_react GET /isp?onlyOn=true) */ @GetMapping("/reception-isps") ApiResponse isps(@AuthenticationPrincipal JwtUser u) { List list = em.createQuery( "select e from HomesReceptionIsp e where e.groupId=:g and (e.active is null or e.active=true) order by e.seq asc, e.id asc", HomesReceptionIsp.class) .setParameter("g", u.getGroupId()).getResultList(); return ApiResponse.ok(list.stream().map(i -> { Map m = new LinkedHashMap<>(); m.put("id", i.getId()); m.put("name", i.getName()); m.put("telecom", i.getTelecom()); m.put("seq", i.getSeq()); m.put("isSktb", Boolean.TRUE.equals(i.getIsSktb())); return m; }).toList()); } @GetMapping("/reception-isps/{ispId}/products") ApiResponse ispProducts(@PathVariable Long ispId, @AuthenticationPrincipal JwtUser u) { HomesReceptionIsp isp = findIsp(ispId, u); if (isp == null) return ApiResponse.fail("통신사를 찾을 수 없습니다."); List list = em.createQuery( "select e from HomesReceptionIspProduct e where e.groupId=:g and e.ispId=:isp and (e.active is null or e.active=true) order by e.id", HomesReceptionIspProduct.class) .setParameter("g", u.getGroupId()).setParameter("isp", ispId).getResultList(); return ApiResponse.ok(list.stream().map(p -> Map.of( "id", p.getId(), "type", nullToEmpty(p.getProductType()), "name", nullToEmpty(p.getName()) )).toList()); } @GetMapping("/reception-isps/{ispId}/agreements") ApiResponse ispAgreements(@PathVariable Long ispId, @AuthenticationPrincipal JwtUser u) { HomesReceptionIsp isp = findIsp(ispId, u); if (isp == null) return ApiResponse.fail("통신사를 찾을 수 없습니다."); List list = em.createQuery( "select e from HomesReceptionIspAgreement e where e.groupId=:g and e.ispId=:isp and (e.active is null or e.active=true) order by e.id", HomesReceptionIspAgreement.class) .setParameter("g", u.getGroupId()).setParameter("isp", ispId).getResultList(); return ApiResponse.ok(list.stream().map(a -> Map.of( "id", a.getId(), "name", nullToEmpty(a.getName()) )).toList()); } /** * 신규신청 등록 — billing_react POST /order 를 Care Homes reception 으로 매핑. * body: { ispId, counselor, branch, customer:{type,name,phone,region,address,ssid,postIt}, payment:{method,name,...}, * product:{productType,productId,agreementId,price,installDate,discount}, gift:{type,place,cashAmount,goods,giftDate}, memo } */ @PostMapping("/receptions/apply") @Transactional ApiResponse apply(@RequestBody Map body, @AuthenticationPrincipal JwtUser u) { Long ispId = toLong(body.get("ispId")); HomesReceptionIsp isp = ispId == null ? null : findIsp(ispId, u); if (isp == null) return ApiResponse.fail("통신사를 선택하세요."); @SuppressWarnings("unchecked") Map customer = body.get("customer") instanceof Map m ? (Map) m : Map.of(); @SuppressWarnings("unchecked") Map payment = body.get("payment") instanceof Map m ? (Map) m : Map.of(); @SuppressWarnings("unchecked") Map product = body.get("product") instanceof Map m ? (Map) m : Map.of(); @SuppressWarnings("unchecked") Map gift = body.get("gift") instanceof Map m ? (Map) m : Map.of(); String customerName = str(customer.get("name")); if (blank(customerName)) return ApiResponse.fail("고객명을 입력하세요."); Long productId = toLong(product.get("productId")); if (productId == null) return ApiResponse.fail("상품을 선택하세요."); HomesReceptionIspProduct prod = em.find(HomesReceptionIspProduct.class, productId); if (prod == null || !Objects.equals(prod.getGroupId(), u.getGroupId()) || !Objects.equals(prod.getIspId(), ispId)) { return ApiResponse.fail("상품이 올바르지 않습니다."); } String agreementName = null; Long agreementId = toLong(product.get("agreementId")); if (agreementId != null) { HomesReceptionIspAgreement ag = em.find(HomesReceptionIspAgreement.class, agreementId); if (ag != null && Objects.equals(ag.getGroupId(), u.getGroupId())) agreementName = ag.getName(); } String productTypeLabel = switch (nullToEmpty(prod.getProductType())) { case "e02" -> "전화"; case "e03" -> "TV"; case "e04" -> "렌탈"; default -> "인터넷"; }; String giftType = str(gift.get("type")); String giftCategory = switch (nullToEmpty(giftType)) { case "a02" -> "현금"; case "a03" -> "상품"; case "a04" -> "현금+상품"; case "a01" -> "없음"; default -> blank(giftType) ? "상품권" : giftType; }; String giftPlaceCode = str(gift.get("place")); String payMethod = str(payment.get("method")); String paymentType = switch (nullToEmpty(payMethod)) { case "card" -> "카드"; case "paper" -> "지로"; case "telephone" -> "전화납부"; default -> "계좌"; }; HomesReception r = new HomesReception(); r.setGroupId(u.getGroupId()); r.setOrderDate(LocalDateTime.now(ZoneId.of("Asia/Seoul"))); r.setIspId(isp.getId()); r.setIspName(isp.getName()); r.setCarrier(isp.getTelecom()); r.setCounselor(blank(str(body.get("counselor"))) ? resolveMemberName(u) : str(body.get("counselor"))); r.setBranch(str(body.get("branch"))); r.setBranchGroup(str(body.get("branchGroup"))); r.setCustomerType(blank(str(customer.get("type"))) ? "public" : str(customer.get("type"))); r.setCustomerName(customerName); r.setPhone(str(customer.get("phone"))); r.setTel(str(customer.get("tel"))); r.setEmail(str(customer.get("email"))); r.setZipcode(str(customer.get("zipcode"))); r.setAddress(str(customer.get("address"))); r.setAddressDetail(str(customer.get("addressDetail"))); r.setRegion(str(customer.get("region"))); r.setIdentifyNumber(str(customer.get("ssid"))); r.setPostIt(str(customer.get("postIt"))); r.setPostAdd(str(customer.get("postAdd"))); r.setProductType(prod.getProductType()); r.setProductCategory(productTypeLabel); r.setProductName(prod.getName()); r.setAgreementName(agreementName); r.setAmount(toDecimal(product.get("price"))); r.setOpenDate(parseDate(str(product.get("installDate")))); r.setGiftCategory(giftCategory); r.setGiftPlace(giftPlaceCode); r.setGiftDate(parseDate(str(gift.get("giftDate")))); r.setGiftStatus(blank(giftType) || "a01".equals(giftType) ? null : "g01"); if ("g01".equals(r.getGiftStatus())) { r.setGiftRequested(LocalDate.now(ZoneId.of("Asia/Seoul"))); } r.setGiftCashAmount(toDecimal(gift.get("cashAmount"))); r.setGiftGoods(str(gift.get("goods"))); r.setGiftGoodsPrice(toDecimal(gift.get("goodsPrice"))); r.setGiftBank(str(gift.get("accountBank"))); r.setGiftAccountNumber(str(gift.get("accountNumber"))); r.setGiftAccountName(str(gift.get("accountName"))); r.setGiftSsid(str(gift.get("ssid"))); r.setPaymentMethod(blank(payMethod) ? "transfer" : payMethod); r.setPaymentType(paymentType); r.setPaymentName(str(payment.get("name"))); r.setPaymentSsid(str(payment.get("ssid"))); r.setPaymentTel(str(payment.get("tel"))); r.setPaymentBank(str(payment.get("bank"))); r.setPaymentAccountNumber(str(payment.get("accountNumber"))); r.setPaymentCardCompany(str(payment.get("cardCompany"))); r.setPaymentCardYear(str(payment.get("cardYear"))); r.setPaymentCardMonth(str(payment.get("cardMonth"))); r.setPaymentCardNumber(str(payment.get("cardNumber"))); r.setProgressStatus("f01"); r.setSettleStatus("h01"); r.setOrderNumber(genOrderNumber()); r.setProcessor(r.getCounselor()); r.setDetailProgress("신규신청"); r.setMemo(str(body.get("memo"))); em.persist(r); em.flush(); appendLog(r, r.getProgressStatus(), "신규신청 [" + isp.getName() + "]", u, false); writeLog(u, "RECEPTION", "신규신청", "신규신청 [" + isp.getName() + "] " + customerName + " 등록하였습니다."); return ApiResponse.ok(toProgressDetail(r, u)); } // ---- 첨부파일 (일반 file1~5) ---- @PostMapping("/receptions/{id}/file/{slot}") @Transactional ApiResponse uploadFile(@PathVariable Long id, @PathVariable int slot, @RequestParam("file") MultipartFile file, @AuthenticationPrincipal JwtUser u) { return uploadAttachment(id, slot, false, file, u); } @GetMapping("/receptions/{id}/file/{slot}") ResponseEntity downloadFile(@PathVariable Long id, @PathVariable int slot, @AuthenticationPrincipal JwtUser u) { return downloadAttachment(id, slot, false, u); } @DeleteMapping("/receptions/{id}/file/{slot}") @Transactional ApiResponse deleteFile(@PathVariable Long id, @PathVariable int slot, @AuthenticationPrincipal JwtUser u) { return deleteAttachment(id, slot, false, u); } // ---- 관리자 첨부파일 (admin_file1~3) ---- @PostMapping("/receptions/{id}/admin-file/{slot}") @Transactional ApiResponse uploadAdminFile(@PathVariable Long id, @PathVariable int slot, @RequestParam("file") MultipartFile file, @AuthenticationPrincipal JwtUser u) { return uploadAttachment(id, slot, true, file, u); } @GetMapping("/receptions/{id}/admin-file/{slot}") ResponseEntity downloadAdminFile(@PathVariable Long id, @PathVariable int slot, @AuthenticationPrincipal JwtUser u) { return downloadAttachment(id, slot, true, u); } @DeleteMapping("/receptions/{id}/admin-file/{slot}") @Transactional ApiResponse deleteAdminFile(@PathVariable Long id, @PathVariable int slot, @AuthenticationPrincipal JwtUser u) { return deleteAttachment(id, slot, true, u); } private ApiResponse uploadAttachment(Long id, int slot, boolean admin, MultipartFile file, JwtUser u) { HomesReception r = findOwned(id, u); int max = admin ? 3 : 5; if (slot < 1 || slot > max) return ApiResponse.fail("첨부 슬롯이 올바르지 않습니다."); if (file == null || file.isEmpty()) return ApiResponse.fail("업로드할 파일을 선택하세요."); String old = getFile(r, slot, admin); if (old != null && !old.isBlank()) fileStorage.delete(old); String path = fileStorage.store("reception/" + id, file); setFile(r, slot, admin, path); writeLog(u, "RECEPTION", admin ? "관리자첨부" : "첨부", "접수 #" + id + " " + (admin ? "관리자첨부" : "첨부") + slot + " 업로드"); Map data = new LinkedHashMap<>(); data.put("id", r.getId()); data.put("slot", slot); data.put("admin", admin); data.put("path", path); data.put("name", FileStorageService.displayName(path)); return ApiResponse.ok(data); } private ResponseEntity downloadAttachment(Long id, int slot, boolean admin, JwtUser u) { HomesReception r = findOwned(id, u); String rel = getFile(r, slot, admin); if (rel == null || rel.isBlank()) { return ResponseEntity.notFound().build(); } Resource resource = fileStorage.loadAsResource(rel); String filename = URLEncoder.encode(FileStorageService.displayName(rel), StandardCharsets.UTF_8) .replace("+", "%20"); return ResponseEntity.ok() .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename*=UTF-8''" + filename) .contentType(MediaType.APPLICATION_OCTET_STREAM) .body(resource); } private ApiResponse deleteAttachment(Long id, int slot, boolean admin, JwtUser u) { HomesReception r = findOwned(id, u); int max = admin ? 3 : 5; if (slot < 1 || slot > max) return ApiResponse.fail("첨부 슬롯이 올바르지 않습니다."); String old = getFile(r, slot, admin); if (old != null && !old.isBlank()) fileStorage.delete(old); setFile(r, slot, admin, null); writeLog(u, "RECEPTION", admin ? "관리자첨부삭제" : "첨부삭제", "접수 #" + id + " " + (admin ? "관리자첨부" : "첨부") + slot + " 삭제"); return ApiResponse.ok(Map.of("id", id, "slot", slot, "admin", admin)); } private static String getFile(HomesReception r, int slot, boolean admin) { if (admin) { return switch (slot) { case 1 -> r.getAdminFile1(); case 2 -> r.getAdminFile2(); case 3 -> r.getAdminFile3(); default -> null; }; } return switch (slot) { case 1 -> r.getFile1(); case 2 -> r.getFile2(); case 3 -> r.getFile3(); case 4 -> r.getFile4(); case 5 -> r.getFile5(); default -> null; }; } private static void setFile(HomesReception r, int slot, boolean admin, String path) { if (admin) { switch (slot) { case 1 -> r.setAdminFile1(path); case 2 -> r.setAdminFile2(path); case 3 -> r.setAdminFile3(path); default -> { } } return; } switch (slot) { case 1 -> r.setFile1(path); case 2 -> r.setFile2(path); case 3 -> r.setFile3(path); case 4 -> r.setFile4(path); case 5 -> r.setFile5(path); default -> { } } } private HomesReceptionIsp findIsp(Long id, JwtUser u) { HomesReceptionIsp isp = em.find(HomesReceptionIsp.class, id); if (isp == null || !Objects.equals(isp.getGroupId(), u.getGroupId())) return null; return isp; } private static Long toLong(Object v) { if (v == null || String.valueOf(v).isBlank()) return null; try { return Long.valueOf(String.valueOf(v)); } catch (Exception e) { return null; } } private List filterReceptions(JwtUser u, Map f) { List all = em.createQuery( "select e from HomesReception e where e.groupId=:g order by e.orderDate desc, e.id desc", HomesReception.class) .setParameter("g", u.getGroupId()).getResultList(); LocalDate from = parseDate(first(f, "dateFrom", "orderDateStart")); LocalDate to = parseDate(first(f, "dateTo", "orderDateEnd")); LocalDate openFrom = parseDate(f.get("openDateFrom")); LocalDate openTo = parseDate(f.get("openDateTo")); LocalDate opened = parseDate(first(f, "opended", "opened", "openDate")); LocalDate giftSended = parseDate(first(f, "giftSended", "giftDateExact")); String progress = blankToNull(first(f, "progressStatus", "tab")); if ("ALL".equalsIgnoreCase(progress)) progress = null; String settle = blankToNull(first(f, "settleStatus", "centerStatus")); String giftStatus = blankToNull(f.get("giftStatus")); String carrier = blankToNull(f.get("carrier")); String branch = blankToNull(f.get("branch")); String counselor = blankToNull(f.get("counselor")); String processor = blankToNull(f.get("processor")); String region = blankToNull(first(f, "region", "customerSido")); String giftCategory = blankToNull(first(f, "giftCategory", "giftType")); String productCategory = blankToNull(first(f, "productCategory", "productType")); String customerName = blankToNull(f.get("customerName")); String phone = blankToNull(f.get("customerPhone") != null ? f.get("customerPhone") : f.get("phone")); String orderNumber = blankToNull(f.get("orderNumber")); String q = blankToNull(first(f, "q", "keyword")); String postIt = blankToNull(f.get("postIt")); String agreementName = blankToNull(f.get("agreementName")); String customerSsid = blankToNull(first(f, "customerSsid", "identifyNumber")); String customerEmail = blankToNull(first(f, "customerEmail", "email")); String productName = blankToNull(f.get("productName")); String ispName = blankToNull(f.get("ispName")); String contractNumber = blankToNull(f.get("contractNumber")); String giftPlace = blankToNull(f.get("giftPlace")); String branchEmployee = blankToNull(first(f, "branchEmployee")); String branchName = blankToNull(f.get("branchName")); String sorting = blankToNull(f.get("sorting")); String progressF = progress; List result = all.stream().filter(r -> { LocalDate od = r.getOrderDate() == null ? null : r.getOrderDate().toLocalDate(); if (from != null && (od == null || od.isBefore(from))) return false; if (to != null && (od == null || od.isAfter(to))) return false; if (openFrom != null && (r.getOpenDate() == null || r.getOpenDate().isBefore(openFrom))) return false; if (openTo != null && (r.getOpenDate() == null || r.getOpenDate().isAfter(openTo))) return false; if (opened != null && (r.getOpenDate() == null || !r.getOpenDate().equals(opened))) return false; if (giftSended != null) { LocalDate gd = r.getGiftSended() != null ? r.getGiftSended() : r.getGiftDate(); if (gd == null || !gd.equals(giftSended)) return false; } if (progressF != null) { String rp = ReceptionStatuses.normalizeProgress(r.getProgressStatus()); String fp = ReceptionStatuses.normalizeProgress(progressF); if (!fp.equals(rp) && !progressF.equals(r.getProgressStatus())) return false; } if (settle != null) { String rs = ReceptionStatuses.normalizeCenter(r.getSettleStatus()); String fs = ReceptionStatuses.normalizeCenter(settle); if (!fs.equals(rs) && !settle.equals(r.getSettleStatus())) return false; } if (giftStatus != null) { String rg = ReceptionStatuses.normalizeGift(r.getGiftStatus()); String fg = ReceptionStatuses.normalizeGift(giftStatus); if (!fg.equals(rg) && !giftStatus.equals(r.getGiftStatus())) return false; } if (carrier != null && !carrier.equals(r.getCarrier()) && !carrier.equals(r.getIspName())) return false; if (ispName != null) { String hay = (nullToEmpty(r.getIspName()) + " " + nullToEmpty(r.getCarrier())).toLowerCase(); if (!hay.contains(ispName.toLowerCase())) return false; } if (branch != null && !branch.equals(r.getBranch())) return false; if (branchName != null && !nullToEmpty(r.getBranch()).contains(branchName)) return false; if (counselor != null && !counselor.equals(r.getCounselor())) return false; if (branchEmployee != null && !nullToEmpty(r.getCounselor()).contains(branchEmployee)) return false; if (processor != null && !processor.equals(r.getProcessor())) return false; if (region != null && !region.equals(r.getRegion())) return false; if (giftCategory != null && !nullToEmpty(r.getGiftCategory()).contains(giftCategory)) return false; if (productCategory != null && !productCategory.equals(r.getProductCategory()) && !productCategory.equals(r.getProductType())) return false; if (giftPlace != null && !giftPlace.equals(r.getGiftPlace())) return false; if (customerName != null && !nullToEmpty(r.getCustomerName()).contains(customerName)) return false; if (phone != null) { String digits = nullToEmpty(r.getPhone()).replaceAll("\\D", ""); if (!digits.contains(phone.replaceAll("\\D", "")) && !nullToEmpty(r.getPhone()).contains(phone)) return false; } if (orderNumber != null && !nullToEmpty(r.getOrderNumber()).contains(orderNumber)) return false; if (postIt != null && !nullToEmpty(r.getPostIt()).contains(postIt) && !nullToEmpty(r.getPostAdd()).contains(postIt)) return false; if (agreementName != null && !nullToEmpty(r.getAgreementName()).contains(agreementName)) return false; if (customerSsid != null && !nullToEmpty(r.getIdentifyNumber()).contains(customerSsid)) return false; if (customerEmail != null && !nullToEmpty(r.getEmail()).contains(customerEmail)) return false; if (productName != null && !nullToEmpty(r.getProductName()).contains(productName)) return false; if (contractNumber != null && !nullToEmpty(r.getContractNumber()).contains(contractNumber)) return false; if (q != null) { String hay = (nullToEmpty(r.getCustomerName()) + " " + nullToEmpty(r.getPhone()) + " " + nullToEmpty(r.getProductName()) + " " + nullToEmpty(r.getOrderNumber()) + " " + nullToEmpty(r.getCounselor()) + " " + nullToEmpty(r.getBranch()) + " " + nullToEmpty(r.getMemo()) + " " + nullToEmpty(r.getPostIt()) + " " + nullToEmpty(r.getIdentifyNumber())).toLowerCase(); if (!hay.contains(q.toLowerCase())) return false; } return true; }).collect(Collectors.toCollection(ArrayList::new)); if ("opended".equals(sorting) || "opened".equals(sorting)) { result.sort(Comparator .comparing(HomesReception::getOpenDate, Comparator.nullsLast(Comparator.naturalOrder())) .reversed()); } return result; } private void apply(HomesReception r, Map d) { if (d.containsKey("orderDate")) r.setOrderDate(parseDateTime(str(d.get("orderDate")))); if (d.containsKey("counselor")) r.setCounselor(str(d.get("counselor"))); if (d.containsKey("branch")) r.setBranch(str(d.get("branch"))); if (d.containsKey("branchGroup")) r.setBranchGroup(str(d.get("branchGroup"))); if (d.containsKey("carrier")) r.setCarrier(str(d.get("carrier"))); if (d.containsKey("giftCategory")) r.setGiftCategory(str(d.get("giftCategory"))); if (d.containsKey("productCategory")) r.setProductCategory(str(d.get("productCategory"))); if (d.containsKey("productName")) r.setProductName(str(d.get("productName"))); if (d.containsKey("customerName")) r.setCustomerName(str(d.get("customerName"))); if (d.containsKey("phone")) r.setPhone(str(d.get("phone"))); if (d.containsKey("region")) r.setRegion(str(d.get("region"))); if (d.containsKey("progressStatus")) r.setProgressStatus(ReceptionStatuses.normalizeProgress(str(d.get("progressStatus")))); if (d.containsKey("orderNumber")) r.setOrderNumber(str(d.get("orderNumber"))); if (d.containsKey("ispId")) r.setIspId(toLong(d.get("ispId"))); if (d.containsKey("ispName")) r.setIspName(str(d.get("ispName"))); if (d.containsKey("inboundInfo")) r.setInboundInfo(str(d.get("inboundInfo"))); if (d.containsKey("processor")) r.setProcessor(str(d.get("processor"))); if (d.containsKey("detailProgress")) r.setDetailProgress(str(d.get("detailProgress"))); if (d.containsKey("paymentType")) r.setPaymentType(str(d.get("paymentType"))); if (d.containsKey("amount")) r.setAmount(toDecimal(d.get("amount"))); if (d.containsKey("settleStatus")) r.setSettleStatus(ReceptionStatuses.normalizeCenter(str(d.get("settleStatus")))); if (d.containsKey("openDate")) r.setOpenDate(parseDate(str(d.get("openDate")))); if (d.containsKey("giftDate")) r.setGiftDate(parseDate(str(d.get("giftDate")))); if (d.containsKey("giftStatus")) { String g = ReceptionStatuses.normalizeGift(str(d.get("giftStatus"))); r.setGiftStatus(blank(g) ? null : g); } if (d.containsKey("giftPlace")) r.setGiftPlace(str(d.get("giftPlace"))); if (d.containsKey("agreementName")) r.setAgreementName(str(d.get("agreementName"))); if (d.containsKey("contractNumber")) r.setContractNumber(str(d.get("contractNumber"))); if (d.containsKey("identifyNumber")) r.setIdentifyNumber(str(d.get("identifyNumber"))); if (d.containsKey("postIt")) r.setPostIt(str(d.get("postIt"))); if (d.containsKey("postAdd")) r.setPostAdd(str(d.get("postAdd"))); if (d.containsKey("memo")) r.setMemo(str(d.get("memo"))); if (d.containsKey("adminMemo")) r.setAdminMemo(str(d.get("adminMemo"))); if (d.containsKey("customerType")) r.setCustomerType(str(d.get("customerType"))); if (d.containsKey("tel")) r.setTel(str(d.get("tel"))); if (d.containsKey("email")) r.setEmail(str(d.get("email"))); if (d.containsKey("zipcode")) r.setZipcode(str(d.get("zipcode"))); if (d.containsKey("address")) r.setAddress(str(d.get("address"))); if (d.containsKey("addressDetail")) r.setAddressDetail(str(d.get("addressDetail"))); if (d.containsKey("paymentMethod")) r.setPaymentMethod(str(d.get("paymentMethod"))); if (d.containsKey("productType")) r.setProductType(str(d.get("productType"))); if (d.containsKey("installSchedule")) r.setInstallSchedule(str(d.get("installSchedule"))); if (d.containsKey("customerLoginId")) r.setCustomerLoginId(str(d.get("customerLoginId"))); if (d.containsKey("newTel")) r.setNewTel(str(d.get("newTel"))); if (d.containsKey("cancelDate")) r.setCancelDate(parseDate(str(d.get("cancelDate")))); if (d.containsKey("branchPhone")) r.setBranchPhone(str(d.get("branchPhone"))); } private Map toProgressDetail(HomesReception r, JwtUser u) { Map data = new LinkedHashMap<>(); Map reception = toMap(r); data.put("reception", reception); data.put("order", reception); // billing_react 호환 별칭 data.put("id", r.getId()); Map customer = new LinkedHashMap<>(); customer.put("type", blank(r.getCustomerType()) ? "public" : r.getCustomerType()); customer.put("name", r.getCustomerName()); customer.put("postIt", r.getPostIt()); customer.put("postAdd", r.getPostAdd()); customer.put("phone", r.getPhone()); customer.put("tel", r.getTel()); customer.put("email", r.getEmail()); customer.put("zipcode", r.getZipcode()); customer.put("address", r.getAddress()); customer.put("addressDetail", r.getAddressDetail()); customer.put("sido", r.getRegion()); customer.put("ssid", r.getIdentifyNumber()); data.put("customer", customer); data.put("customerPublic", Map.of("ssid", nullToEmpty(r.getIdentifyNumber()))); Map payment = new LinkedHashMap<>(); payment.put("method", blank(r.getPaymentMethod()) ? paymentMethodFromType(r.getPaymentType()) : r.getPaymentMethod()); payment.put("name", r.getPaymentName()); payment.put("ssid", r.getPaymentSsid()); payment.put("tel", r.getPaymentTel()); payment.put("bank", r.getPaymentBank()); payment.put("accountNumber", r.getPaymentAccountNumber()); payment.put("cardCompany", r.getPaymentCardCompany()); payment.put("cardYear", r.getPaymentCardYear()); payment.put("cardMonth", r.getPaymentCardMonth()); payment.put("cardNumber", r.getPaymentCardNumber()); data.put("payment", payment); if ("card".equals(payment.get("method"))) data.put("paymentCard", payment); else if ("transfer".equals(payment.get("method"))) data.put("paymentTransfer", payment); Map product = new LinkedHashMap<>(); product.put("productType", blank(r.getProductType()) ? productTypeFromCategory(r.getProductCategory()) : r.getProductType()); product.put("name", r.getProductName()); product.put("productName", r.getProductName()); product.put("price", r.getAmount() == null ? 0 : r.getAmount()); data.put("product", product); data.put("productName", r.getProductName()); data.put("agreementName", r.getAgreementName()); data.put("ispName", blank(r.getIspName()) ? r.getCarrier() : r.getIspName()); data.put("eventNames", List.of()); Map gift = new LinkedHashMap<>(); gift.put("status", r.getGiftStatus()); gift.put("place", r.getGiftPlace()); gift.put("giftDate", r.getGiftDate() == null ? null : r.getGiftDate().toString()); gift.put("requested", r.getGiftRequested() == null ? null : r.getGiftRequested().toString()); gift.put("sended", r.getGiftSended() == null ? null : r.getGiftSended().toString()); gift.put("accountBank", r.getGiftBank()); gift.put("accountNumber", r.getGiftAccountNumber()); gift.put("accountName", r.getGiftAccountName()); gift.put("ssid", r.getGiftSsid()); gift.put("goods", r.getGiftGoods()); gift.put("cashAmount", r.getGiftCashAmount()); gift.put("goodsPrice", r.getGiftGoodsPrice()); gift.put("category", r.getGiftCategory()); data.put("gift", gift); Map etc = new LinkedHashMap<>(); etc.put("memo", r.getMemo()); etc.put("adminMemo", r.getAdminMemo()); etc.put("file1", r.getFile1()); etc.put("file2", r.getFile2()); etc.put("file3", r.getFile3()); etc.put("file4", r.getFile4()); etc.put("file5", r.getFile5()); etc.put("adminFile1", r.getAdminFile1()); etc.put("adminFile2", r.getAdminFile2()); etc.put("adminFile3", r.getAdminFile3()); data.put("etc", etc); data.put("employeeName", r.getCounselor()); data.put("branchOfficeName", blank(r.getBranch()) ? "본지점" : r.getBranch()); data.put("branchPhone", blank(r.getBranchPhone()) ? "" : r.getBranchPhone()); data.put("branchTel", ""); data.put("progressLogs", progressLogs(r.getId(), u.getGroupId())); data.put("giftProgressLogs", giftProgressLogs(r.getId(), u.getGroupId())); data.put("logs", data.get("progressLogs")); data.put("progressStatuses", PROGRESS_STATUSES); data.put("giftStatuses", GIFT_STATUSES); data.put("settleStatuses", SETTLE_STATUSES); data.put("progressStatusLabels", ReceptionStatuses.PROGRESS); data.put("giftStatusLabels", ReceptionStatuses.GIFT); data.put("settleStatusLabels", ReceptionStatuses.CENTER); data.put("statusOptions", new LinkedHashMap<>(ReceptionStatuses.PROGRESS)); data.put("giftStatusOptions", new LinkedHashMap<>(ReceptionStatuses.GIFT)); data.put("centerStatusOptions", new LinkedHashMap<>(ReceptionStatuses.CENTER)); data.put("allowedProgressStatuses", new ArrayList<>( ReceptionStatuses.orderStatusTransitions(ReceptionStatuses.normalizeProgress(r.getProgressStatus())))); // flat alias for list edit form compatibility data.putAll(reception); return data; } private static String paymentMethodFromType(String paymentType) { if ("카드".equals(paymentType)) return "card"; if ("지로".equals(paymentType)) return "paper"; if ("전화납부".equals(paymentType)) return "telephone"; return "transfer"; } private static String productTypeFromCategory(String cat) { if ("전화".equals(cat)) return "e02"; if ("TV".equals(cat)) return "e03"; if ("렌탈".equals(cat)) return "e04"; return "e01"; } private Map toMap(HomesReception r) { Map m = new LinkedHashMap<>(); m.put("id", r.getId()); m.put("orderDate", fmtDt(r.getOrderDate())); m.put("today", r.getOrderDate() != null && r.getOrderDate().toLocalDate().equals(LocalDate.now(ZoneId.of("Asia/Seoul")))); m.put("counselor", r.getCounselor()); m.put("branch", r.getBranch()); m.put("branchGroup", r.getBranchGroup()); m.put("branchPhone", r.getBranchPhone()); m.put("carrier", r.getCarrier()); m.put("giftCategory", r.getGiftCategory()); m.put("productCategory", r.getProductCategory()); m.put("productType", r.getProductType()); m.put("productName", r.getProductName()); m.put("customerType", r.getCustomerType()); m.put("customerName", r.getCustomerName()); m.put("phone", r.getPhone()); m.put("tel", r.getTel()); m.put("email", r.getEmail()); m.put("zipcode", r.getZipcode()); m.put("address", r.getAddress()); m.put("addressDetail", r.getAddressDetail()); m.put("region", r.getRegion()); m.put("progressStatus", r.getProgressStatus()); m.put("progressStatusLabel", ReceptionStatuses.progressLabel(ReceptionStatuses.normalizeProgress(r.getProgressStatus()))); m.put("status", ReceptionStatuses.progressLabel(ReceptionStatuses.normalizeProgress(r.getProgressStatus()))); m.put("orderNumber", r.getOrderNumber()); m.put("ispId", r.getIspId()); m.put("ispName", r.getIspName()); m.put("inboundInfo", r.getInboundInfo()); m.put("processor", r.getProcessor()); m.put("detailProgress", r.getDetailProgress()); m.put("paymentType", r.getPaymentType()); m.put("paymentMethod", r.getPaymentMethod()); m.put("amount", r.getAmount()); m.put("settleStatus", r.getSettleStatus()); m.put("openDate", r.getOpenDate() == null ? null : r.getOpenDate().toString()); m.put("cancelDate", r.getCancelDate() == null ? null : r.getCancelDate().toString()); m.put("giftDate", r.getGiftDate() == null ? null : r.getGiftDate().toString()); m.put("giftStatus", r.getGiftStatus()); m.put("giftPlace", r.getGiftPlace()); m.put("giftRequested", r.getGiftRequested() == null ? null : r.getGiftRequested().toString()); m.put("giftSended", r.getGiftSended() == null ? null : r.getGiftSended().toString()); m.put("agreementName", r.getAgreementName()); m.put("contractNumber", r.getContractNumber()); m.put("installSchedule", r.getInstallSchedule()); m.put("customerLoginId", r.getCustomerLoginId()); m.put("newTel", r.getNewTel()); m.put("multiLine", Boolean.TRUE.equals(r.getMultiLine())); m.put("addPayer", Boolean.TRUE.equals(r.getAddPayer())); m.put("record", Boolean.TRUE.equals(r.getRecordFlag())); m.put("identifyNumber", r.getIdentifyNumber()); m.put("postIt", r.getPostIt()); m.put("postAdd", r.getPostAdd()); m.put("memo", r.getMemo()); m.put("adminMemo", r.getAdminMemo()); // billing_react OrderListRow 호환 별칭 m.put("employeeName", r.getCounselor()); m.put("sido", r.getRegion()); m.put("opened", r.getOpenDate() == null ? null : r.getOpenDate().toString()); m.put("canceled", r.getCancelDate() == null ? null : r.getCancelDate().toString()); m.put("giftType", r.getGiftCategory()); m.put("giftStatusLabel", ReceptionStatuses.giftLabel(ReceptionStatuses.normalizeGift(r.getGiftStatus()))); m.put("settleStatusLabel", ReceptionStatuses.centerLabel(ReceptionStatuses.normalizeCenter(r.getSettleStatus()))); m.put("centerStatus", r.getSettleStatus()); m.put("centerStatusLabel", ReceptionStatuses.centerLabel(ReceptionStatuses.normalizeCenter(r.getSettleStatus()))); m.put("giftSended", r.getGiftSended() == null ? (r.getGiftDate() == null ? null : r.getGiftDate().toString()) : r.getGiftSended().toString()); m.put("branchOfficeName", r.getBranch()); m.put("branchGroupName", r.getBranchGroup()); if (blank(r.getIspName())) m.put("ispName", r.getCarrier()); BigDecimal giftAmt = BigDecimal.ZERO; if (r.getGiftCashAmount() != null) giftAmt = giftAmt.add(r.getGiftCashAmount()); if (r.getGiftGoodsPrice() != null) giftAmt = giftAmt.add(r.getGiftGoodsPrice()); if (giftAmt.signum() == 0 && r.getAmount() != null && r.getGiftStatus() != null && !r.getGiftStatus().isBlank() && !"미지급".equals(r.getGiftStatus()) && !"없음".equals(r.getGiftCategory())) { giftAmt = r.getAmount(); } m.put("giftAmount", giftAmt.signum() == 0 ? null : giftAmt); m.put("centerName", blank(r.getProcessor()) ? "본사" : r.getProcessor()); m.put("centerStatus", r.getSettleStatus()); return m; } private List> progressLogs(Long receptionId, String groupId) { return em.createQuery( "select l from HomesReceptionProgressLog l where l.groupId=:g and l.receptionId=:rid order by l.loggedAt desc, l.id desc", HomesReceptionProgressLog.class) .setParameter("g", groupId).setParameter("rid", receptionId) .getResultList().stream().map(l -> { Map m = new LinkedHashMap<>(); m.put("id", l.getId()); m.put("status", l.getStatus()); m.put("memo", l.getMemo()); m.put("authorName", l.getAuthorName()); m.put("writer", l.getAuthorName()); m.put("loggedAt", fmtDt(l.getLoggedAt())); m.put("createdAt", fmtDt(l.getLoggedAt())); m.put("adminOnly", Boolean.TRUE.equals(l.getAdminOnly())); return m; }).toList(); } private List> giftProgressLogs(Long receptionId, String groupId) { return em.createQuery( "select l from HomesReceptionGiftProgressLog l where l.groupId=:g and l.receptionId=:rid order by l.loggedAt desc, l.id desc", HomesReceptionGiftProgressLog.class) .setParameter("g", groupId).setParameter("rid", receptionId) .getResultList().stream().map(l -> { Map m = new LinkedHashMap<>(); m.put("id", l.getId()); m.put("status", l.getStatus()); m.put("memo", l.getMemo()); m.put("authorName", l.getAuthorName()); m.put("writer", l.getAuthorName()); m.put("loggedAt", fmtDt(l.getLoggedAt())); m.put("createdAt", fmtDt(l.getLoggedAt())); return m; }).toList(); } private void appendLog(HomesReception r, String status, String memo, JwtUser u) { appendLog(r, status, memo, u, false); } private void appendLog(HomesReception r, String status, String memo, JwtUser u, boolean adminOnly) { HomesReceptionProgressLog log = new HomesReceptionProgressLog(); log.setGroupId(u.getGroupId()); log.setReceptionId(r.getId()); log.setStatus(status); log.setMemo(memo); log.setAuthorName(resolveMemberName(u)); log.setLoggedAt(LocalDateTime.now(ZoneId.of("Asia/Seoul"))); log.setAdminOnly(adminOnly); em.persist(log); } private void appendGiftLog(HomesReception r, String status, String memo, JwtUser u) { HomesReceptionGiftProgressLog log = new HomesReceptionGiftProgressLog(); log.setGroupId(u.getGroupId()); log.setReceptionId(r.getId()); log.setStatus(status); log.setMemo(memo); log.setAuthorName(resolveMemberName(u)); log.setLoggedAt(LocalDateTime.now(ZoneId.of("Asia/Seoul"))); em.persist(log); } private HomesReception findOwned(Long id, JwtUser u) { HomesReception r = em.find(HomesReception.class, id); if (r == null || !Objects.equals(r.getGroupId(), u.getGroupId())) return null; return r; } private void writeLog(JwtUser u, String domain, String action, String content) { HomesLog log = new HomesLog(); log.setGroupId(u.getGroupId()); log.setDomain(domain); log.setAction(action); log.setContent(content); String staff = resolveMemberName(u); log.setStaff(staff == null || staff.isBlank() ? u.getUserid() : staff); log.setProcessedAt(LocalDateTime.now()); em.persist(log); } private String resolveMemberName(JwtUser u) { List rows = em.createQuery("select m from Member m where m.groupId=:g and m.userid=:uid", Member.class) .setParameter("g", u.getGroupId()).setParameter("uid", u.getUserid()).setMaxResults(1).getResultList(); return rows.isEmpty() ? u.getUserid() : rows.getFirst().getName(); } private static List distinct(List list, java.util.function.Function getter, List fallback) { List values = list.stream().map(getter).filter(s -> s != null && !s.isBlank()) .distinct().sorted().collect(Collectors.toCollection(ArrayList::new)); if (values.isEmpty() && fallback != null) return fallback; return values; } private static String genOrderNumber() { return "RCP-" + LocalDateTime.now(ZoneId.of("Asia/Seoul")) .format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")) + "-" + (1000 + new Random().nextInt(9000)); } private static List toIds(Object raw) { if (!(raw instanceof List list)) return List.of(); List ids = new ArrayList<>(); for (Object o : list) { if (o == null) continue; try { ids.add(Long.valueOf(String.valueOf(o))); } catch (Exception ignored) { } } return ids; } private static BigDecimal toDecimal(Object v) { if (v == null || String.valueOf(v).isBlank()) return null; try { return new BigDecimal(String.valueOf(v).replace(",", "")); } catch (Exception e) { return null; } } private static LocalDate parseDate(String s) { if (s == null || s.isBlank()) return null; try { return LocalDate.parse(s.substring(0, Math.min(10, s.length()))); } catch (Exception e) { return null; } } private static LocalDateTime parseDateTime(String s) { if (s == null || s.isBlank()) return null; try { if (s.length() <= 10) return LocalDate.parse(s).atStartOfDay(); return LocalDateTime.parse(s.replace(' ', 'T').substring(0, Math.min(19, s.length()))); } catch (Exception e) { LocalDate d = parseDate(s); return d == null ? null : d.atStartOfDay(); } } private static String fmtDt(LocalDateTime dt) { if (dt == null) return null; return dt.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); } private static String first(Map f, String... keys) { for (String k : keys) { String v = f.get(k); if (v != null && !v.isBlank()) return v; } return null; } private static String blankToNull(String s) { return s == null || s.isBlank() ? null : s.trim(); } private static boolean blank(String s) { return s == null || s.isBlank(); } private static String nullToEmpty(String s) { return s == null ? "" : s; } private static String str(Object v) { return v == null ? null : String.valueOf(v).trim(); } private static String firstNonBlank(String... values) { if (values == null) return null; for (String v : values) { if (v != null && !v.isBlank()) return v.trim(); } return null; } private static String csvCell(Object value) { if (value == null) return ""; String s = String.valueOf(value).replace("\"", "\"\""); if (s.contains(",") || s.contains("\"") || s.contains("\n")) return "\"" + s + "\""; return s; } }