Files
BizCare/backend/src/main/java/com/bizcare/agent/CareReceptionController.java
T
2026-07-12 15:55:08 +09:00

1344 lines
71 KiB
Java

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<String> PROGRESS_STATUSES = ReceptionStatuses.progressCodes();
static final List<String> SETTLE_STATUSES = ReceptionStatuses.centerCodes();
static final List<String> GIFT_STATUSES = ReceptionStatuses.giftCodes();
static final List<String> CARRIERS = List.of("KT", "SKB", "LGU+", "SKT", "기타");
static final List<String> REGIONS = List.of(
"서울", "경기", "인천", "강원", "충북", "충남", "대전", "전북", "전남", "광주",
"경북", "대구", "경남", "울산", "부산", "제주");
@GetMapping("/receptions")
ApiResponse list(@AuthenticationPrincipal JwtUser u,
@RequestParam Map<String, String> f,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "50") int size) {
List<HomesReception> filtered = filterReceptions(u, f);
Map<String, Long> 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<Map<String, Object>> list = filtered.stream().skip(fromIdx).limit(limit)
.map(this::toMap).toList();
Map<String, Object> 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<String> excel(@AuthenticationPrincipal JwtUser u,
@RequestParam Map<String, String> f) {
List<HomesReception> 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<String, Object> 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<String> 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<String, Object> 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<String, Object> 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<String, Object> body, @AuthenticationPrincipal JwtUser u) {
List<Long> 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<String, Object> body, @AuthenticationPrincipal JwtUser u) {
List<Long> 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<HomesReception> 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<String, Object> body, @AuthenticationPrincipal JwtUser u) {
List<Long> 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<HomesReception> 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<String, Object> body, @AuthenticationPrincipal JwtUser u) {
List<Long> ids = toIds(body.get("ids"));
if (ids.isEmpty()) return ApiResponse.fail("리스트를 선택하세요.");
List<HomesReception> 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<HomesReceptionIsp> 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<String, Object> 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<HomesReceptionIspProduct> 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<HomesReceptionIspAgreement> 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<String, Object> 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<String, Object> customer = body.get("customer") instanceof Map<?, ?> m ? (Map<String, Object>) m : Map.of();
@SuppressWarnings("unchecked")
Map<String, Object> payment = body.get("payment") instanceof Map<?, ?> m ? (Map<String, Object>) m : Map.of();
@SuppressWarnings("unchecked")
Map<String, Object> product = body.get("product") instanceof Map<?, ?> m ? (Map<String, Object>) m : Map.of();
@SuppressWarnings("unchecked")
Map<String, Object> gift = body.get("gift") instanceof Map<?, ?> m ? (Map<String, Object>) 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<Resource> 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<Resource> 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<String, Object> 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<Resource> 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<HomesReception> filterReceptions(JwtUser u, Map<String, String> f) {
List<HomesReception> 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<HomesReception> 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<String, Object> 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<String, Object> toProgressDetail(HomesReception r, JwtUser u) {
Map<String, Object> data = new LinkedHashMap<>();
Map<String, Object> reception = toMap(r);
data.put("reception", reception);
data.put("order", reception); // billing_react 호환 별칭
data.put("id", r.getId());
Map<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> toMap(HomesReception r) {
Map<String, Object> 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<Map<String, Object>> 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<String, Object> 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<Map<String, Object>> 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<String, Object> 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<Member> 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<String> distinct(List<HomesReception> list,
java.util.function.Function<HomesReception, String> getter,
List<String> fallback) {
List<String> 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<Long> toIds(Object raw) {
if (!(raw instanceof List<?> list)) return List.of();
List<Long> 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<String, String> 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;
}
}