package com.bizcare.agent; import jakarta.persistence.EntityManager; import lombok.RequiredArgsConstructor; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.*; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.util.*; import java.util.stream.Collectors; /** * 영업점관리 — billing_react BranchController / BranchGroupController / BranchFee entries 이식. */ @RestController @RequestMapping("/api/agent/homes") @RequiredArgsConstructor class CareBranchController { private static final ZoneId KST = ZoneId.of("Asia/Seoul"); private static final DateTimeFormatter DT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); private final EntityManager em; private final PasswordEncoder encoder; // ---- 영업점 그룹 ---- @GetMapping("/branch-groups") @Transactional(readOnly = true) ApiResponse branchGroups(@AuthenticationPrincipal JwtUser u) { List list = em.createQuery( "select g from HomesBranchGroup g where g.groupId=:gid order by g.seq asc, g.id asc", HomesBranchGroup.class) .setParameter("gid", u.getGroupId()) .getResultList(); return ApiResponse.ok(list.stream().map(this::groupMap).toList()); } @PostMapping("/branch-groups") @Transactional ApiResponse createBranchGroup(@RequestBody Map body, @AuthenticationPrincipal JwtUser u) { String name = str(body.get("name")); if (blank(name)) return ApiResponse.fail("그룹명을 입력하세요."); HomesBranchGroup g = new HomesBranchGroup(); g.setGroupId(u.getGroupId()); g.setName(name.trim()); g.setSeq(body.get("seq") instanceof Number n ? n.intValue() : 0); em.persist(g); return ApiResponse.ok(groupMap(g)); } // ---- 영업점 목록 / CRUD ---- @GetMapping("/branches") @Transactional(readOnly = true) ApiResponse branches(@AuthenticationPrincipal JwtUser u) { List list = em.createQuery( "select b from HomesBranch b where b.groupId=:gid order by b.id desc", HomesBranch.class) .setParameter("gid", u.getGroupId()) .getResultList(); Map groupNames = groupNameMap(u.getGroupId()); return ApiResponse.ok(list.stream().map(b -> listMap(b, groupNames)).toList()); } @PostMapping("/branches") @Transactional ApiResponse createBranch(@RequestBody Map body, @RequestParam(defaultValue = "1") int level, @RequestParam(required = false) Long branchGroupId, @AuthenticationPrincipal JwtUser u) { String userid = str(body.get("userid")); String password = str(body.get("password")); String officeName = str(body.get("officeName")); String name = str(body.get("name")); String phone = str(body.get("phone")); String email = str(body.get("email")); if (blank(userid) || blank(password)) return ApiResponse.fail("아이디와 패스워드를 입력하세요."); if (blank(officeName) || blank(name)) return ApiResponse.fail("영업점명과 대표자명을 입력하세요."); if (blank(phone) || blank(email)) return ApiResponse.fail("휴대폰과 이메일을 입력하세요."); if (existsUserid(u.getGroupId(), userid, null)) return ApiResponse.fail("다른사람이 사용중인 아이디입니다."); if (existsOfficeName(u.getGroupId(), officeName, null)) return ApiResponse.fail("다른사람이 사용중인 영업점명입니다."); if (existsEmail(u.getGroupId(), email, null)) return ApiResponse.fail("다른 사람이 사용중인 이메일입니다."); HomesBranch b = new HomesBranch(); b.setGroupId(u.getGroupId()); b.setUserid(userid.trim()); b.setPasswordHash(encoder.encode(password)); b.setName(name.trim()); b.setOfficeName(officeName.trim()); b.setTel(str(body.get("tel"))); b.setPhone(phone.trim()); b.setFax(str(body.get("fax"))); b.setEmail(email.trim()); b.setZipcode(str(body.get("zipcode"))); b.setAddress(str(body.get("address"))); b.setAddressDetail(str(body.get("addressDetail"))); b.setSido(deriveSido(str(body.get("zipcode")), str(body.get("sido")), str(body.get("address")))); b.setLevel(level); b.setGrantFlag(1); // 관리자 등록 시 승인 b.setBranchGroupId(branchGroupId != null ? branchGroupId : longOrNull(body.get("branchGroupId"))); b.setIsTax(body.get("isTax") instanceof Number n ? n.intValue() : 1); b.setMemo(str(body.get("memo"))); em.persist(b); return ApiResponse.ok(listMap(b, groupNameMap(u.getGroupId()))); } @GetMapping("/branches/{id}/detail") @Transactional(readOnly = true) ApiResponse branchDetail(@PathVariable Long id, @AuthenticationPrincipal JwtUser u) { HomesBranch b = findBranch(id, u); if (b == null) return ApiResponse.fail("영업점을 찾을 수 없습니다."); return ApiResponse.ok(detailMap(b)); } @PutMapping("/branches/{id}/detail") @Transactional ApiResponse updateBranchDetail(@PathVariable Long id, @RequestBody Map body, @AuthenticationPrincipal JwtUser u) { HomesBranch b = findBranch(id, u); if (b == null) return ApiResponse.fail("영업점을 찾을 수 없습니다."); String officeName = str(body.get("officeName")); String email = str(body.get("email")); String name = str(body.get("name")); if (blank(officeName) || blank(name)) return ApiResponse.fail("영업점명과 이름을 입력하세요."); if (existsOfficeName(u.getGroupId(), officeName, id)) return ApiResponse.fail("다른사람이 사용중인 영업점명입니다."); if (!blank(email) && existsEmail(u.getGroupId(), email, id)) return ApiResponse.fail("다른 사람이 사용중인 이메일입니다."); String password = str(body.get("password")); String passwordConfirmation = str(body.get("passwordConfirmation")); if (!blank(password)) { if (!password.equals(passwordConfirmation)) return ApiResponse.fail("패스워드 확인이 일치하지 않습니다."); b.setPasswordHash(encoder.encode(password)); } Long oldGroupId = b.getBranchGroupId(); Long newGroupId = longOrNull(body.get("branchGroupId")); boolean keepFee = Boolean.TRUE.equals(body.get("keepFee")) || "true".equalsIgnoreCase(String.valueOf(body.get("keepFee"))); b.setName(name.trim()); b.setOfficeName(officeName.trim()); b.setTel(str(body.get("tel"))); b.setPhone(str(body.get("phone"))); b.setFax(str(body.get("fax"))); b.setEmail(email); b.setZipcode(str(body.get("zipcode"))); b.setAddress(str(body.get("address"))); b.setAddressDetail(str(body.get("addressDetail"))); b.setSido(deriveSido(str(body.get("zipcode")), str(body.get("sido")), str(body.get("address")))); if (body.get("level") instanceof Number n) b.setLevel(n.intValue()); b.setBranchGroupId(newGroupId); b.setCenterName(str(body.get("centerName"))); if (blank(b.getCenterName()) && body.get("centerId") != null) { // centerId 호환: 숫자면 무시, 문자열이면 이름 b.setCenterName(str(body.get("centerId"))); } boolean isPersonal = Boolean.TRUE.equals(body.get("isPersonal")) || "true".equalsIgnoreCase(String.valueOf(body.get("isPersonal"))); b.setHasBusinessLicense(!isPersonal); b.setLicenseNumber(isPersonal ? null : str(body.get("licenseNumber"))); b.setLicenseType(isPersonal ? null : str(body.get("licenseType"))); b.setLicenseItem(isPersonal ? null : str(body.get("licenseItem"))); b.setAccountBank(str(body.get("accountBank"))); b.setAccountNumber(str(body.get("accountNumber"))); boolean accountNameIsName = Boolean.TRUE.equals(body.get("accountNameIsName")) || "true".equalsIgnoreCase(String.valueOf(body.get("accountNameIsName"))); b.setAccountName(accountNameIsName ? b.getName() : str(body.get("accountName"))); b.setContactPhone(str(body.get("contactPhone"))); b.setContactNateon(str(body.get("contactNateon"))); if (body.get("acceptPhone") instanceof Number n) b.setAcceptPhone(n.intValue()); if (body.get("acceptNateon") instanceof Number n) b.setAcceptNateon(n.intValue()); if (body.get("isTax") instanceof Number n) b.setIsTax(n.intValue()); b.setMemo(str(body.get("memo"))); b.setAdminMemo(str(body.get("adminMemo"))); b.setNotifyPhonesJson(toNotifyJson(body.get("notifyPhones"))); b.setPermissionOrderDateUpdate(bool(body.get("permissionOrderDateUpdate"))); b.setPermissionCalculate(bool(body.get("permissionCalculate"))); b.setPermissionConfig(bool(body.get("permissionConfig"))); b.setPermissionExcelDownload(bool(body.get("permissionExcelDownload"))); b.setPermissionChart(bool(body.get("permissionChart"))); // keepFee=false 이고 그룹이 바뀌면 개별 수수료는 유지, 그룹 매핑은 이름만 바뀌므로 별도 처리 없음 if (!keepFee && !Objects.equals(oldGroupId, newGroupId) && newGroupId != null) { HomesBranchGroup ng = em.find(HomesBranchGroup.class, newGroupId); if (ng != null && Objects.equals(ng.getGroupId(), u.getGroupId())) { // 그룹 수수료는 branchGroupName 문자열 키 — 접수의 branchGroup 과 맞추는 용도만 // (실제 접수 일괄 갱신은 범위 외) } } return ApiResponse.ok(detailMap(b)); } @PostMapping("/branches/{id}/grant/{grant}") @Transactional ApiResponse setGrant(@PathVariable Long id, @PathVariable int grant, @AuthenticationPrincipal JwtUser u) { HomesBranch b = findBranch(id, u); if (b == null) return ApiResponse.fail("영업점을 찾을 수 없습니다."); if (grant != 0 && grant != 1) return ApiResponse.fail("승인값은 0 또는 1이어야 합니다."); b.setGrantFlag(grant); return ApiResponse.ok(null); } @DeleteMapping("/branches/{id}") @Transactional ApiResponse deleteBranch(@PathVariable Long id, @AuthenticationPrincipal JwtUser u) { HomesBranch b = findBranch(id, u); if (b == null) return ApiResponse.fail("영업점을 찾을 수 없습니다."); em.remove(b); return ApiResponse.ok(null); } /** 영업점 수수료 목록 (개별 + 소속 그룹) — billing GET /branch-fee/branch/{id}/entries */ @GetMapping("/branches/{id}/fees") @Transactional(readOnly = true) ApiResponse branchFees(@PathVariable Long id, @AuthenticationPrincipal JwtUser u) { HomesBranch b = findBranch(id, u); if (b == null) return ApiResponse.fail("영업점을 찾을 수 없습니다."); List> entries = new ArrayList<>(); List alone = em.createQuery( "select f from HomesBranchFee f where f.groupId=:gid and f.feeType='alone' and f.branchName=:name", HomesBranchFee.class) .setParameter("gid", u.getGroupId()) .setParameter("name", b.getOfficeName()) .getResultList(); for (HomesBranchFee f : alone) { entries.add(feeEntryMap(f, "alone")); } if (b.getBranchGroupId() != null) { HomesBranchGroup g = em.find(HomesBranchGroup.class, b.getBranchGroupId()); if (g != null && Objects.equals(g.getGroupId(), u.getGroupId())) { List groupFees = em.createQuery( "select f from HomesBranchFee f where f.groupId=:gid and f.feeType='group' and f.branchGroupName=:name", HomesBranchFee.class) .setParameter("gid", u.getGroupId()) .setParameter("name", g.getName()) .getResultList(); for (HomesBranchFee f : groupFees) { entries.add(feeEntryMap(f, "group")); } } } // 적용일 내림차순, 동일일이면 개별(alone) 우선 entries.sort((a, b2) -> { String sa = String.valueOf(a.get("startDate") == null ? "" : a.get("startDate")); String sb = String.valueOf(b2.get("startDate") == null ? "" : b2.get("startDate")); int c = sb.compareTo(sa); if (c != 0) return c; int pa = "alone".equals(a.get("type")) ? 0 : 1; int pb = "alone".equals(b2.get("type")) ? 0 : 1; return Integer.compare(pa, pb); }); return ApiResponse.ok(entries); } /** 수수료 상세 — billing branch-fee/{id}/read/{type} */ @GetMapping("/branch-fees/{feeId}") @Transactional(readOnly = true) ApiResponse feeDetail(@PathVariable Long feeId, @RequestParam(defaultValue = "alone") String type, @AuthenticationPrincipal JwtUser u) { HomesBranchFee f = em.find(HomesBranchFee.class, feeId); if (f == null || !Objects.equals(f.getGroupId(), u.getGroupId())) { return ApiResponse.fail("수수료를 찾을 수 없습니다."); } String want = "group".equalsIgnoreCase(type) ? "group" : "alone"; if (!want.equals(f.getFeeType())) { return ApiResponse.fail("수수료 종류가 일치하지 않습니다."); } return ApiResponse.ok(feeDetailMap(f)); } /** 개별 수수료 등록 */ @PostMapping("/branches/{id}/fees") @Transactional ApiResponse createAloneFee(@PathVariable Long id, @RequestBody Map body, @AuthenticationPrincipal JwtUser u) { HomesBranch b = findBranch(id, u); if (b == null) return ApiResponse.fail("영업점을 찾을 수 없습니다."); String carrier = str(body.get("carrier")); if (blank(carrier)) return ApiResponse.fail("통신사를 선택하세요."); LocalDateTime start = parseDateTime(body.get("startDate")); if (start == null) return ApiResponse.fail("적용일을 입력하세요."); if (existsAloneFee(u.getGroupId(), b.getOfficeName(), carrier, start, null)) { return ApiResponse.fail("이미 등록되어 있는 적용일입니다."); } HomesBranchFee fee = new HomesBranchFee(); fee.setGroupId(u.getGroupId()); fee.setFeeType("alone"); fee.setBranchName(b.getOfficeName()); if (b.getBranchGroupId() != null) { HomesBranchGroup g = em.find(HomesBranchGroup.class, b.getBranchGroupId()); if (g != null) fee.setBranchGroupName(g.getName()); } fee.setCarrier(carrier); fee.setStartDate(start); fee.setMemo(str(body.get("memo"))); em.persist(fee); em.flush(); replaceProducts(fee.getId(), u.getGroupId(), body.get("products")); return ApiResponse.ok(feeDetailMap(fee)); } /** 개별 수수료 수정 — 그룹은 거부 */ @PutMapping("/branch-fees/{feeId}") @Transactional ApiResponse updateAloneFee(@PathVariable Long feeId, @RequestBody Map body, @AuthenticationPrincipal JwtUser u) { HomesBranchFee f = em.find(HomesBranchFee.class, feeId); if (f == null || !Objects.equals(f.getGroupId(), u.getGroupId())) { return ApiResponse.fail("수수료를 찾을 수 없습니다."); } if (!"alone".equals(f.getFeeType())) { return ApiResponse.fail("그룹 수수료는 여기서 수정할 수 없습니다."); } LocalDateTime start = parseDateTime(body.get("startDate")); if (start == null) return ApiResponse.fail("적용일을 입력하세요."); String carrier = blank(str(body.get("carrier"))) ? f.getCarrier() : str(body.get("carrier")); if (existsAloneFee(u.getGroupId(), f.getBranchName(), carrier, start, feeId)) { return ApiResponse.fail("이미 등록되어 있는 적용일입니다."); } f.setCarrier(carrier); f.setStartDate(start); f.setMemo(str(body.get("memo"))); if (body.containsKey("file1")) f.setFile1(str(body.get("file1"))); if (body.containsKey("file2")) f.setFile2(str(body.get("file2"))); if (body.containsKey("file3")) f.setFile3(str(body.get("file3"))); if (body.containsKey("file4")) f.setFile4(str(body.get("file4"))); if (body.containsKey("file5")) f.setFile5(str(body.get("file5"))); replaceProducts(feeId, u.getGroupId(), body.get("products")); return ApiResponse.ok(feeDetailMap(f)); } @DeleteMapping("/branch-fees/{feeId}") @Transactional ApiResponse deleteAloneFee(@PathVariable Long feeId, @AuthenticationPrincipal JwtUser u) { HomesBranchFee f = em.find(HomesBranchFee.class, feeId); if (f == null || !Objects.equals(f.getGroupId(), u.getGroupId())) { return ApiResponse.fail("수수료를 찾을 수 없습니다."); } if (!"alone".equals(f.getFeeType())) { return ApiResponse.fail("그룹 수수료는 여기서 삭제할 수 없습니다."); } em.createQuery("delete from HomesBranchFeeProduct p where p.feeId=:fid and p.groupId=:gid") .setParameter("fid", feeId) .setParameter("gid", u.getGroupId()) .executeUpdate(); em.remove(f); return ApiResponse.ok(null); } /** 등록 폼용 기본 상품 템플릿 (시드/기존 수수료 상품명 재사용) */ @GetMapping("/branch-fee-product-templates") @Transactional(readOnly = true) ApiResponse productTemplates(@RequestParam(required = false) String carrier, @AuthenticationPrincipal JwtUser u) { List samples = em.createQuery( "select p from HomesBranchFeeProduct p where p.groupId=:gid order by p.id asc", HomesBranchFeeProduct.class) .setParameter("gid", u.getGroupId()) .setMaxResults(40) .getResultList(); LinkedHashMap> uniq = new LinkedHashMap<>(); for (HomesBranchFeeProduct p : samples) { String key = p.getProductType() + "|" + p.getProductName(); if (uniq.containsKey(key)) continue; Map m = new LinkedHashMap<>(); m.put("productType", p.getProductType()); m.put("productName", p.getProductName()); m.put("fee", 0); uniq.put(key, m); } if (uniq.isEmpty()) { Object[][] defaults = { {"e01", "기가인터넷 1G"}, {"e01", "안심 인터넷"}, {"e03", "B tv 프리미엄"}, {"e02", "집전화 스마트"}, {"e04", "기타"}, }; for (Object[] d : defaults) { Map m = new LinkedHashMap<>(); m.put("productType", d[0]); m.put("productName", d[1]); m.put("fee", 0); uniq.put(d[0] + "|" + d[1], m); } } return ApiResponse.ok(new ArrayList<>(uniq.values())); } /** 입력처(센터) 후보 — 레벨5 영업점 + 접수 processor distinct */ @GetMapping("/branch-centers") @Transactional(readOnly = true) ApiResponse centers(@AuthenticationPrincipal JwtUser u) { LinkedHashSet names = new LinkedHashSet<>(); em.createQuery( "select b.officeName from HomesBranch b where b.groupId=:gid and b.level=5 and b.officeName is not null", String.class) .setParameter("gid", u.getGroupId()) .getResultList() .forEach(names::add); em.createQuery( "select distinct r.processor from HomesReception r where r.groupId=:gid and r.processor is not null and r.processor<>''", String.class) .setParameter("gid", u.getGroupId()) .getResultList() .forEach(names::add); List> out = new ArrayList<>(); int i = 1; for (String n : names) { Map m = new LinkedHashMap<>(); m.put("id", i++); m.put("name", n); out.add(m); } return ApiResponse.ok(out); } // ---- helpers ---- private void replaceProducts(Long feeId, String groupId, Object productsRaw) { em.createQuery("delete from HomesBranchFeeProduct p where p.feeId=:fid and p.groupId=:gid") .setParameter("fid", feeId) .setParameter("gid", groupId) .executeUpdate(); if (!(productsRaw instanceof List list)) return; for (Object o : list) { if (!(o instanceof Map m)) continue; String productType = str(m.get("productType")); String productName = str(m.get("productName")); if (blank(productName)) continue; HomesBranchFeeProduct p = new HomesBranchFeeProduct(); p.setGroupId(groupId); p.setFeeId(feeId); p.setProductType(blank(productType) ? "e01" : productType); p.setProductName(productName); Object feeObj = m.get("fee"); int fee = feeObj instanceof Number n ? n.intValue() : 0; try { if (!(feeObj instanceof Number) && feeObj != null && !String.valueOf(feeObj).isBlank()) { fee = Integer.parseInt(String.valueOf(feeObj).replace(",", "").replace("원", "").trim()); } } catch (Exception ignored) { /* keep 0 */ } p.setFee(fee); em.persist(p); } } private boolean existsAloneFee(String gid, String branchName, String carrier, LocalDateTime start, Long excludeId) { String jpql = "select count(f) from HomesBranchFee f where f.groupId=:gid and f.feeType='alone'" + " and f.branchName=:name and f.carrier=:c and f.startDate=:sd" + (excludeId != null ? " and f.id<>:id" : ""); var q = em.createQuery(jpql, Long.class) .setParameter("gid", gid) .setParameter("name", branchName) .setParameter("c", carrier) .setParameter("sd", start); if (excludeId != null) q.setParameter("id", excludeId); return q.getSingleResult() > 0; } private Map feeDetailMap(HomesBranchFee f) { List products = em.createQuery( "select p from HomesBranchFeeProduct p where p.feeId=:fid and p.groupId=:gid order by p.productType, p.id", HomesBranchFeeProduct.class) .setParameter("fid", f.getId()) .setParameter("gid", f.getGroupId()) .getResultList(); Map fee = new LinkedHashMap<>(); fee.put("id", f.getId()); fee.put("type", f.getFeeType()); fee.put("branchName", f.getBranchName()); fee.put("branchGroupName", f.getBranchGroupName()); fee.put("carrier", f.getCarrier()); fee.put("ispName", f.getCarrier()); fee.put("startDate", f.getStartDate() == null ? null : DT.format(f.getStartDate())); fee.put("memo", f.getMemo()); fee.put("file1", f.getFile1()); fee.put("file2", f.getFile2()); fee.put("file3", f.getFile3()); fee.put("file4", f.getFile4()); fee.put("file5", f.getFile5()); fee.put("source", String.valueOf(f.getId())); List> productMaps = new ArrayList<>(); for (HomesBranchFeeProduct p : products) { Map pm = new LinkedHashMap<>(); pm.put("id", p.getId()); pm.put("productType", p.getProductType()); pm.put("productName", p.getProductName()); pm.put("fee", p.getFee() == null ? 0 : p.getFee()); productMaps.add(pm); } Map out = new LinkedHashMap<>(); out.put("fee", fee); out.put("products", productMaps); return out; } private static LocalDateTime parseDateTime(Object raw) { if (raw == null) return null; String s = String.valueOf(raw).trim().replace('T', ' '); if (s.isEmpty()) return null; try { if (s.length() == 10) return LocalDateTime.parse(s + " 00:00:00", DT); if (s.length() >= 19) return LocalDateTime.parse(s.substring(0, 19), DT); return LocalDateTime.parse(s, DT); } catch (Exception e) { try { return LocalDateTime.parse(s.substring(0, 10) + "T00:00:00"); } catch (Exception e2) { return null; } } } private HomesBranch findBranch(Long id, JwtUser u) { HomesBranch b = em.find(HomesBranch.class, id); if (b == null || !Objects.equals(b.getGroupId(), u.getGroupId())) return null; return b; } private Map groupNameMap(String groupId) { return em.createQuery( "select g from HomesBranchGroup g where g.groupId=:gid", HomesBranchGroup.class) .setParameter("gid", groupId) .getResultList() .stream() .collect(Collectors.toMap(HomesBranchGroup::getId, HomesBranchGroup::getName, (a, b) -> a)); } private boolean existsUserid(String gid, String userid, Long excludeId) { String jpql = "select count(b) from HomesBranch b where b.groupId=:gid and b.userid=:uid" + (excludeId != null ? " and b.id<>:id" : ""); var q = em.createQuery(jpql, Long.class).setParameter("gid", gid).setParameter("uid", userid.trim()); if (excludeId != null) q.setParameter("id", excludeId); return q.getSingleResult() > 0; } private boolean existsOfficeName(String gid, String officeName, Long excludeId) { String jpql = "select count(b) from HomesBranch b where b.groupId=:gid and b.officeName=:n" + (excludeId != null ? " and b.id<>:id" : ""); var q = em.createQuery(jpql, Long.class).setParameter("gid", gid).setParameter("n", officeName.trim()); if (excludeId != null) q.setParameter("id", excludeId); return q.getSingleResult() > 0; } private boolean existsEmail(String gid, String email, Long excludeId) { String jpql = "select count(b) from HomesBranch b where b.groupId=:gid and b.email=:e" + (excludeId != null ? " and b.id<>:id" : ""); var q = em.createQuery(jpql, Long.class).setParameter("gid", gid).setParameter("e", email.trim()); if (excludeId != null) q.setParameter("id", excludeId); return q.getSingleResult() > 0; } private Map groupMap(HomesBranchGroup g) { Map m = new LinkedHashMap<>(); m.put("id", g.getId()); m.put("name", g.getName()); m.put("seq", g.getSeq()); return m; } private Map listMap(HomesBranch b, Map groupNames) { Map m = new LinkedHashMap<>(); m.put("id", b.getId()); m.put("name", b.getName()); m.put("officeName", b.getOfficeName()); m.put("userid", b.getUserid()); m.put("tel", b.getTel()); m.put("phone", b.getPhone()); m.put("email", b.getEmail()); m.put("sido", b.getSido()); m.put("level", b.getLevel() == null ? 1 : b.getLevel()); m.put("grant", b.getGrantFlag() == null ? 0 : b.getGrantFlag()); m.put("branchGroupId", b.getBranchGroupId()); m.put("branchGroupName", b.getBranchGroupId() == null ? null : groupNames.get(b.getBranchGroupId())); m.put("centerName", b.getCenterName()); m.put("hasBusinessLicense", b.isHasBusinessLicense()); m.put("isTax", b.getIsTax() == null ? 1 : b.getIsTax()); m.put("createdAt", b.getCreatedAt() == null ? null : DT.format(b.getCreatedAt().atZone(KST).toLocalDateTime())); return m; } private Map detailMap(HomesBranch b) { Map m = listMap(b, groupNameMap(b.getGroupId())); m.put("fax", b.getFax()); m.put("zipcode", b.getZipcode()); m.put("address", b.getAddress()); m.put("addressDetail", b.getAddressDetail()); m.put("isPersonal", !b.isHasBusinessLicense()); m.put("accountBank", b.getAccountBank()); m.put("accountNumber", b.getAccountNumber()); m.put("accountName", b.getAccountName()); m.put("contactPhone", b.getContactPhone()); m.put("contactNateon", b.getContactNateon()); m.put("acceptPhone", b.getAcceptPhone() == null ? 0 : b.getAcceptPhone()); m.put("acceptNateon", b.getAcceptNateon() == null ? 0 : b.getAcceptNateon()); m.put("memo", b.getMemo()); m.put("adminMemo", b.getAdminMemo()); m.put("licenseNumber", b.getLicenseNumber()); m.put("licenseType", b.getLicenseType()); m.put("licenseItem", b.getLicenseItem()); m.put("notifyPhones", parseNotify(b.getNotifyPhonesJson())); m.put("permissionOrderDateUpdate", b.isPermissionOrderDateUpdate()); m.put("permissionCalculate", b.isPermissionCalculate()); m.put("permissionConfig", b.isPermissionConfig()); m.put("permissionExcelDownload", b.isPermissionExcelDownload()); m.put("permissionChart", b.isPermissionChart()); return m; } private Map feeEntryMap(HomesBranchFee f, String type) { Map m = new LinkedHashMap<>(); m.put("id", f.getId()); m.put("type", type); m.put("ispName", f.getCarrier()); m.put("carrier", f.getCarrier()); m.put("category", "공통"); m.put("startDate", f.getStartDate() == null ? null : DT.format(f.getStartDate())); return m; } private static List parseNotify(String json) { List phones = new ArrayList<>(); if (json != null && json.startsWith("[")) { String inner = json.substring(1, json.endsWith("]") ? json.length() - 1 : json.length()).trim(); if (!inner.isEmpty()) { for (String part : inner.split(",")) { phones.add(part.trim().replace("\"", "")); } } } while (phones.size() < 6) phones.add(""); return phones.subList(0, 6); } private static String toNotifyJson(Object raw) { List phones = new ArrayList<>(); if (raw instanceof List list) { for (Object o : list) phones.add(o == null ? "" : String.valueOf(o)); } while (phones.size() < 6) phones.add(""); StringBuilder sb = new StringBuilder("["); for (int i = 0; i < 6; i++) { if (i > 0) sb.append(','); sb.append('"').append(phones.get(i).replace("\"", "")).append('"'); } sb.append(']'); return sb.toString(); } private static String deriveSido(String zipcode, String sido, String address) { if (!blank(sido)) return sido.trim(); if (!blank(address)) { String[] parts = address.trim().split("\\s+"); if (parts.length > 0) return parts[0]; } if (!blank(zipcode) && zipcode.length() >= 2) return zipcode.substring(0, 2); return ""; } private static boolean blank(String s) { return s == null || s.isBlank(); } private static String str(Object o) { return o == null ? "" : String.valueOf(o).trim(); } private static boolean bool(Object o) { return Boolean.TRUE.equals(o) || "true".equalsIgnoreCase(String.valueOf(o)); } private static Long longOrNull(Object o) { if (o == null || "".equals(o) || "null".equals(String.valueOf(o))) return null; if (o instanceof Number n) return n.longValue(); try { return Long.parseLong(String.valueOf(o)); } catch (Exception e) { return null; } } }