nation 버전 이식

This commit is contained in:
Macbook
2026-07-12 15:55:08 +09:00
parent 314a3554d3
commit 07a8114927
54 changed files with 11612 additions and 203 deletions
@@ -0,0 +1,683 @@
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<HomesBranchGroup> 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<String, Object> 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<HomesBranch> 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<Long, String> groupNames = groupNameMap(u.getGroupId());
return ApiResponse.ok(list.stream().map(b -> listMap(b, groupNames)).toList());
}
@PostMapping("/branches")
@Transactional
ApiResponse createBranch(@RequestBody Map<String, Object> 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<String, Object> 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<Map<String, Object>> entries = new ArrayList<>();
List<HomesBranchFee> 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<HomesBranchFee> 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<String, Object> 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<String, Object> 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<HomesBranchFeeProduct> 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<String, Map<String, Object>> uniq = new LinkedHashMap<>();
for (HomesBranchFeeProduct p : samples) {
String key = p.getProductType() + "|" + p.getProductName();
if (uniq.containsKey(key)) continue;
Map<String, Object> 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<String, Object> 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<String> 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<Map<String, Object>> out = new ArrayList<>();
int i = 1;
for (String n : names) {
Map<String, Object> 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<String, Object> feeDetailMap(HomesBranchFee f) {
List<HomesBranchFeeProduct> 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<String, Object> 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<Map<String, Object>> productMaps = new ArrayList<>();
for (HomesBranchFeeProduct p : products) {
Map<String, Object> 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<String, Object> 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<Long, String> 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<String, Object> groupMap(HomesBranchGroup g) {
Map<String, Object> m = new LinkedHashMap<>();
m.put("id", g.getId());
m.put("name", g.getName());
m.put("seq", g.getSeq());
return m;
}
private Map<String, Object> listMap(HomesBranch b, Map<Long, String> groupNames) {
Map<String, Object> 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<String, Object> detailMap(HomesBranch b) {
Map<String, Object> 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<String, Object> feeEntryMap(HomesBranchFee f, String type) {
Map<String, Object> 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<String> parseNotify(String json) {
List<String> 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<String> 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; }
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,182 @@
package com.bizcare.agent;
import jakarta.persistence.EntityManager;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
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.*;
/**
* 접수별 적용 수수료 현황 — billing_imeere BranchGroupFeeController@order / CenterFeeController@order.
*
* 적용가능 수수료가 없으면 fail("적용가능한 수수료가 없습니다.").
*/
@RestController
@RequestMapping("/api/agent/homes/fee")
@RequiredArgsConstructor
class CareFeeController {
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;
@GetMapping("/order/{receptionId}")
@Transactional(readOnly = true)
ApiResponse orderFee(@PathVariable Long receptionId,
@RequestParam(defaultValue = "branch") String kind,
@AuthenticationPrincipal JwtUser u) {
HomesReception r = em.find(HomesReception.class, receptionId);
if (r == null || !Objects.equals(r.getGroupId(), u.getGroupId())) {
return ApiResponse.fail("삭제되었거나 등록되지 않은 접수입니다.");
}
if ("center".equalsIgnoreCase(kind)) {
return centerOrder(r, u);
}
return branchOrder(r, u);
}
private ApiResponse branchOrder(HomesReception r, JwtUser u) {
String branch = blank(r.getBranch()) ? "본지점" : r.getBranch().trim();
String carrier = blank(r.getIspName()) ? (r.getCarrier() == null ? "" : r.getCarrier().trim()) : r.getIspName().trim();
LocalDateTime orderDate = r.getOrderDate() != null ? r.getOrderDate() : LocalDateTime.now(KST);
HomesBranchFee alone = findLatestBranchFee(u.getGroupId(), "alone", branch, carrier, orderDate);
HomesBranchFee group = findLatestBranchFee(u.getGroupId(), "group",
blank(r.getBranchGroup()) ? null : r.getBranchGroup().trim(), carrier, orderDate);
// 그룹 수수료는 branchGroupName 매칭; 없으면 동일 통신사 그룹시트 중 최신
if (group == null) {
group = findLatestBranchFeeByCarrier(u.getGroupId(), "group", carrier, orderDate);
}
if (alone == null && group == null) {
return ApiResponse.fail("적용가능한 수수료가 없습니다.");
}
String type = "group";
HomesBranchFee selected = group;
if (group == null
|| (alone != null && !alone.getStartDate().isBefore(group.getStartDate()))) {
type = "alone";
selected = alone;
}
return ApiResponse.ok(toBranchView(selected, type, branch, carrier));
}
private ApiResponse centerOrder(HomesReception r, JwtUser u) {
String center = blank(r.getProcessor()) ? "본사" : r.getProcessor().trim();
String carrier = blank(r.getIspName()) ? (r.getCarrier() == null ? "" : r.getCarrier().trim()) : r.getIspName().trim();
LocalDateTime orderDate = r.getOrderDate() != null ? r.getOrderDate() : LocalDateTime.now(KST);
HomesCenterFee fee = findLatestCenterFee(u.getGroupId(), center, carrier, orderDate);
if (fee == null) {
return ApiResponse.fail("적용가능한 수수료가 없습니다.");
}
return ApiResponse.ok(toCenterView(fee, center, carrier));
}
private HomesBranchFee findLatestBranchFee(String groupId, String feeType, String branchOrGroup,
String carrier, LocalDateTime orderDate) {
if (blank(branchOrGroup) || blank(carrier)) return null;
String jpql = "alone".equals(feeType)
? "select f from HomesBranchFee f where f.groupId=:g and f.feeType='alone' and f.branchName=:bn and f.carrier=:c and f.startDate<=:d order by f.startDate desc"
: "select f from HomesBranchFee f where f.groupId=:g and f.feeType='group' and f.branchGroupName=:bn and f.carrier=:c and f.startDate<=:d order by f.startDate desc";
List<HomesBranchFee> list = em.createQuery(jpql, HomesBranchFee.class)
.setParameter("g", groupId)
.setParameter("bn", branchOrGroup)
.setParameter("c", carrier)
.setParameter("d", orderDate)
.setMaxResults(1)
.getResultList();
return list.isEmpty() ? null : list.get(0);
}
private HomesBranchFee findLatestBranchFeeByCarrier(String groupId, String feeType, String carrier,
LocalDateTime orderDate) {
if (blank(carrier)) return null;
List<HomesBranchFee> list = em.createQuery(
"select f from HomesBranchFee f where f.groupId=:g and f.feeType=:t and f.carrier=:c and f.startDate<=:d order by f.startDate desc",
HomesBranchFee.class)
.setParameter("g", groupId)
.setParameter("t", feeType)
.setParameter("c", carrier)
.setParameter("d", orderDate)
.setMaxResults(1)
.getResultList();
return list.isEmpty() ? null : list.get(0);
}
private HomesCenterFee findLatestCenterFee(String groupId, String center, String carrier, LocalDateTime orderDate) {
if (blank(center) || blank(carrier)) return null;
List<HomesCenterFee> list = em.createQuery(
"select f from HomesCenterFee f where f.groupId=:g and f.centerName=:cn and f.carrier=:c and f.startDate<=:d order by f.startDate desc",
HomesCenterFee.class)
.setParameter("g", groupId)
.setParameter("cn", center)
.setParameter("c", carrier)
.setParameter("d", orderDate)
.setMaxResults(1)
.getResultList();
return list.isEmpty() ? null : list.get(0);
}
private Map<String, Object> toBranchView(HomesBranchFee fee, String type, String entityName, String carrier) {
Map<String, Object> m = new LinkedHashMap<>();
m.put("kind", "branch");
m.put("type", type);
m.put("feeId", fee.getId());
m.put("entityName", entityName);
m.put("ispName", carrier);
m.put("startDate", fee.getStartDate() == null ? "" : fee.getStartDate().format(DT));
m.put("memo", fee.getMemo());
m.put("products", productsOfBranch(fee.getId()));
return m;
}
private Map<String, Object> toCenterView(HomesCenterFee fee, String entityName, String carrier) {
Map<String, Object> m = new LinkedHashMap<>();
m.put("kind", "center");
m.put("type", "center");
m.put("feeId", fee.getId());
m.put("entityName", entityName);
m.put("ispName", carrier);
m.put("startDate", fee.getStartDate() == null ? "" : fee.getStartDate().format(DT));
m.put("memo", fee.getMemo());
m.put("products", productsOfCenter(fee.getId()));
return m;
}
private List<Map<String, Object>> productsOfBranch(Long feeId) {
return em.createQuery(
"select p from HomesBranchFeeProduct p where p.feeId=:id order by p.id",
HomesBranchFeeProduct.class)
.setParameter("id", feeId)
.getResultList().stream().map(p -> {
Map<String, Object> row = new LinkedHashMap<>();
row.put("productType", p.getProductType());
row.put("productName", p.getProductName());
row.put("fee", p.getFee() == null ? 0 : p.getFee());
return row;
}).toList();
}
private List<Map<String, Object>> productsOfCenter(Long feeId) {
return em.createQuery(
"select p from HomesCenterFeeProduct p where p.feeId=:id order by p.id",
HomesCenterFeeProduct.class)
.setParameter("id", feeId)
.getResultList().stream().map(p -> {
Map<String, Object> row = new LinkedHashMap<>();
row.put("productType", p.getProductType());
row.put("productName", p.getProductName());
row.put("fee", p.getFee() == null ? 0 : p.getFee());
return row;
}).toList();
}
private static boolean blank(String s) { return s == null || s.isBlank(); }
}
@@ -2845,6 +2845,7 @@ class CareHomesController {
m.put("상품관리", "product");
m.put("계약관리", "contract");
m.put("접수관리", "reception");
m.put("영업점관리", "branch");
m.put("정산관리", "invoice");
m.put("일정관리", "pending");
m.put("예약관리", "booking");
@@ -2865,7 +2866,7 @@ class CareHomesController {
Map<String, Boolean> exposure = parseMenuFlagsMap(menuFlagsRaw);
if (isOwner) {
for (String id : List.of("home", "product", "contract", "reception", "invoice", "pending", "booking",
for (String id : List.of("home", "product", "contract", "reception", "branch", "invoice", "pending", "booking",
"customer", "abooks", "report", "setting", "cs", "bbs", "sitemap")) {
if (Boolean.FALSE.equals(exposure.get(id))) continue;
allowed.add(id);
@@ -2923,7 +2924,7 @@ class CareHomesController {
private Map<String, Boolean> parseMenuFlagsMap(String raw) {
Map<String, Boolean> flags = new LinkedHashMap<>();
for (String id : List.of("home", "product", "contract", "reception", "invoice", "pending", "booking",
for (String id : List.of("home", "product", "contract", "reception", "branch", "invoice", "pending", "booking",
"customer", "abooks", "report", "cs", "bbs", "setting", "sitemap")) {
flags.put(id, true);
}
@@ -3069,7 +3070,7 @@ class CareHomesController {
private static final Map<String, List<String>> LEVEL_ROWS;
static {
Map<String, List<String>> rows = new LinkedHashMap<>();
rows.put("menu", List.of("상품관리", "계약관리", "접수관리", "정산관리", "일정관리", "예약관리", "고객관리", "장부관리", "통계관리", "환경설정"));
rows.put("menu", List.of("상품관리", "계약관리", "접수관리", "영업점관리", "정산관리", "일정관리", "예약관리", "고객관리", "장부관리", "통계관리", "환경설정"));
rows.put("product", List.of("목록조회", "상품등록", "상품수정", "상품삭제", "정책관리", "엑셀다운로드"));
rows.put("contract", List.of("목록조회", "계약등록", "계약수정", "상태변경", "엑셀다운로드"));
rows.put("reception", List.of("목록조회", "접수등록", "접수수정", "진행변경", "엑셀다운로드"));
@@ -3120,7 +3121,7 @@ class CareHomesController {
for (String menu : menus) {
boolean allow = switch (role) {
case "총괄", "팀장", "개발자" -> true;
case "사원" -> List.of("계약관리", "접수관리", "정산관리", "일정관리", "예약관리", "고객관리", "장부관리").contains(menu);
case "사원" -> List.of("계약관리", "접수관리", "영업점관리", "정산관리", "일정관리", "예약관리", "고객관리", "장부관리").contains(menu);
case "영업", "딜러" -> List.of("계약관리", "접수관리", "정산관리", "일정관리", "예약관리").contains(menu);
default -> false;
};
@@ -29,10 +29,9 @@ class CareReceptionController {
private final EntityManager em;
private final FileStorageService fileStorage;
static final List<String> PROGRESS_STATUSES = List.of(
"1.접수완료", "2.고객통화", "3.서류검토", "4.개통완료", "5.사은품지급", "6.정산완료", "9.취소");
static final List<String> SETTLE_STATUSES = List.of("정산대기", "정산완료", "회수대기", "회수완료");
static final List<String> GIFT_STATUSES = List.of("미지급", "지급대기", "지급완료", "반송");
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(
"서울", "경기", "인천", "강원", "충북", "충남", "대전", "전북", "전남", "광주",
@@ -47,7 +46,9 @@ class CareReceptionController {
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(r.getProgressStatus())).count());
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);
@@ -60,6 +61,10 @@ class CareReceptionController {
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()));
@@ -170,32 +175,61 @@ class CareReceptionController {
boolean adminOnly = Boolean.TRUE.equals(body.get("adminOnly")) || "true".equalsIgnoreCase(String.valueOf(body.get("adminOnly")));
boolean statusChanged = false;
if (!blank(newStatus) && !Objects.equals(newStatus, r.getProgressStatus())) {
r.setProgressStatus(newStatus);
statusChanged = true;
if ("4.개통완료".equals(newStatus) && blank(r.getGiftStatus())) {
r.setGiftStatus("지급대기");
}
if ("4.개통완료".equals(newStatus) && r.getOpenDate() == null) {
r.setOpenDate(LocalDate.now(ZoneId.of("Asia/Seoul")));
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"));
boolean giftChanged = false;
if (!blank(giftStatus) && !Objects.equals(giftStatus, r.getGiftStatus())) {
r.setGiftStatus(giftStatus);
giftChanged = true;
appendGiftLog(r, giftStatus, str(body.get("giftProgressLog")), u);
if ("지급완료".equals(giftStatus) && "4.개통완료".equals(r.getProgressStatus())) {
r.setProgressStatus("6.정산완료");
r.setSettleStatus("정산완료");
statusChanged = true;
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);
}
@@ -210,6 +244,30 @@ class CareReceptionController {
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) {
@@ -235,9 +293,12 @@ class CareReceptionController {
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("1.접수완료");
if (blank(r.getSettleStatus())) r.setSettleStatus("정산대기");
if (blank(r.getGiftStatus())) r.setGiftStatus("미지급");
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();
@@ -313,12 +374,13 @@ class CareReceptionController {
int n = 0;
for (HomesReception r : rows) {
r.setProgressStatus(status);
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);
if ("4.개통완료".equals(status) && blank(r.getGiftStatus())) r.setGiftStatus("지급대기");
appendLog(r, status, memo, u, adminOnly);
applyOpenSideEffects(r, normalized, u);
appendLog(r, normalized, memo, u, adminOnly);
n++;
}
writeLog(u, "RECEPTION", "접수-일괄진행", "접수 " + n + "건 진행상황을 [" + status + "]로 변경하였습니다.");
@@ -359,7 +421,8 @@ class CareReceptionController {
int n = 0;
for (HomesReception r : rows) {
r.setGiftStatus(status);
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);
@@ -368,7 +431,22 @@ class CareReceptionController {
r.setGiftSended(sended);
r.setGiftDate(sended);
}
appendGiftLog(r, status, giftMemo, u);
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 + "]로 변경하였습니다.");
@@ -385,9 +463,9 @@ class CareReceptionController {
for (Long id : ids) {
HomesReception r = findOwned(id, u);
if (r == null) return ApiResponse.fail("접수 리스트가 올바르지 않습니다.");
// 입력처 정산이 비어있거나 미지정인 건만 대상
if (!blank(r.getSettleStatus()) && !"정산대기".equals(r.getSettleStatus())
&& !"미지정".equals(r.getSettleStatus())) {
// 입력처 정산이 비어있거나 정산대기(h01)인 건만 대상
String st = ReceptionStatuses.normalizeCenter(r.getSettleStatus());
if (!blank(st) && !"h01".equals(st)) {
return ApiResponse.fail("접수 리스트가 올바르지 않습니다.");
}
rows.add(r);
@@ -395,7 +473,7 @@ class CareReceptionController {
int n = 0;
for (HomesReception r : rows) {
r.setSettleStatus("정산대기");
r.setSettleStatus("h01");
appendLog(r, r.getProgressStatus(), "[일괄변경] 입력처 정산대기", u, false);
n++;
}
@@ -540,8 +618,10 @@ class CareReceptionController {
r.setGiftCategory(giftCategory);
r.setGiftPlace(giftPlaceCode);
r.setGiftDate(parseDate(str(gift.get("giftDate"))));
r.setGiftStatus(blank(giftType) || "a01".equals(giftType) ? "미지급" : "지급대기");
r.setGiftCashAmount(toDecimal(gift.get("cashAmount")));
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")));
@@ -559,8 +639,8 @@ class CareReceptionController {
r.setPaymentCardYear(str(payment.get("cardYear")));
r.setPaymentCardMonth(str(payment.get("cardMonth")));
r.setPaymentCardNumber(str(payment.get("cardNumber")));
r.setProgressStatus("1.접수완료");
r.setSettleStatus("정산대기");
r.setProgressStatus("f01");
r.setSettleStatus("h01");
r.setOrderNumber(genOrderNumber());
r.setProcessor(r.getCounselor());
r.setDetailProgress("신규신청");
@@ -766,9 +846,21 @@ class CareReceptionController {
LocalDate gd = r.getGiftSended() != null ? r.getGiftSended() : r.getGiftDate();
if (gd == null || !gd.equals(giftSended)) return false;
}
if (progressF != null && !progressF.equals(r.getProgressStatus())) return false;
if (settle != null && !settle.equals(r.getSettleStatus())) return false;
if (giftStatus != null && !giftStatus.equals(r.getGiftStatus())) 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();
@@ -829,7 +921,7 @@ class CareReceptionController {
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(str(d.get("progressStatus")));
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")));
@@ -838,11 +930,13 @@ class CareReceptionController {
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(str(d.get("settleStatus")));
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")) r.setGiftStatus(str(d.get("giftStatus")));
if (d.containsKey("giftPlace")) r.setGiftPlace(str(d.get("giftPlace")));
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")));
@@ -953,12 +1047,14 @@ class CareReceptionController {
data.put("progressStatuses", PROGRESS_STATUSES);
data.put("giftStatuses", GIFT_STATUSES);
data.put("settleStatuses", SETTLE_STATUSES);
Map<String, String> statusOptions = new LinkedHashMap<>();
for (String s : PROGRESS_STATUSES) statusOptions.put(s, s);
data.put("statusOptions", statusOptions);
Map<String, String> giftStatusOptions = new LinkedHashMap<>();
for (String s : GIFT_STATUSES) giftStatusOptions.put(s, s);
data.put("giftStatusOptions", giftStatusOptions);
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;
@@ -1003,6 +1099,8 @@ class CareReceptionController {
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());
@@ -1038,8 +1136,11 @@ class CareReceptionController {
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("status", r.getProgressStatus());
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());
@@ -1050,7 +1151,8 @@ class CareReceptionController {
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 && !"미지급".equals(r.getGiftStatus()) && !"없음".equals(r.getGiftCategory())) {
&& r.getGiftStatus() != null && !r.getGiftStatus().isBlank()
&& !"미지급".equals(r.getGiftStatus()) && !"없음".equals(r.getGiftCategory())) {
giftAmt = r.getAmount();
}
m.put("giftAmount", giftAmt.signum() == 0 ? null : giftAmt);
@@ -71,8 +71,28 @@ class DataInitializer implements CommandLineRunner {
seedScheduleSampleIfEmpty();
seedPostitsIfEmpty();
seedStockInfoSampleIfEmpty();
migrateReceptionStatusesToCodes();
seedCalculateSamplesIfEmpty();
seedFeeSheetsIfEmpty();
seedBranchesIfEmpty();
}
/** 구 한글 진행/사은품/입력처 상태를 billing 코드(f/g/h)로 변환 */
private void migrateReceptionStatusesToCodes() {
List<HomesReception> rows = em.createQuery("select r from HomesReception r", HomesReception.class).getResultList();
for (HomesReception r : rows) {
String p = ReceptionStatuses.normalizeProgress(r.getProgressStatus());
if (p != null && !p.equals(r.getProgressStatus())) r.setProgressStatus(p.isBlank() ? null : p);
String g = ReceptionStatuses.normalizeGift(r.getGiftStatus());
if (g != null && !g.equals(nullToEmpty(r.getGiftStatus()))) r.setGiftStatus(g.isBlank() ? null : g);
String c = ReceptionStatuses.normalizeCenter(r.getSettleStatus());
if (c != null && !c.equals(nullToEmpty(r.getSettleStatus()))) r.setSettleStatus(c.isBlank() ? null : c);
}
em.flush();
}
private static String nullToEmpty(String s) { return s == null ? "" : s; }
private void seedStockInfoSampleIfEmpty() {
Long exists = em.createQuery("select count(s) from Stock s where s.groupId='demo' and s.serial='0018987'", Long.class).getSingleResult();
if (exists != null && exists > 0) return;
@@ -2325,16 +2345,16 @@ class DataInitializer implements CommandLineRunner {
em.flush();
if (em.createQuery("select count(r) from HomesReception r where r.groupId='demo'", Long.class).getSingleResult() == 0) {
Object[][] rows = {
{LocalDateTime.of(2026, 7, 10, 9, 12), "김상담", "강남점", "KT", "상품권", "인터넷", "기가인터넷 1G", "홍*동", "010-1111-2222", "서울", "1.접수완료", "SAMPLE-00001", "서울입고", "박처리", "접수", "현금", "100000", "정산대기", null, null, "미지급", "본사", "36개월", "02-1234-5678"},
{LocalDateTime.of(2026, 7, 10, 10, 30), "이상담", "서초점", "SKB", "가전", "TV", "B tv 프리미엄", "김*수", "010-2222-3333", "경기", "2.고객통화", "SAMPLE-00002", "", "최처리", "통화완료", "계좌", "80000", "정산대기", null, null, "지급대기", "영업점", "24개월", ""},
{LocalDateTime.of(2026, 7, 9, 14, 5), "김상담", "강남점", "LGU+", "상품권", "인터넷", "U+ 인터넷", "박*지", "010-3333-4444", "인천", "4.개통완료", "SAMPLE-00003", "서울입고", "박처리", "개통", "현금", "120000", "정산대기", LocalDate.of(2026, 7, 9), null, "미지급", "본사", "36개월", "032-555-1212"},
{LocalDateTime.of(2026, 7, 8, 11, 20), "정상담", "송파점", "KT", "캐시백", "정수기", "청호 450", "최*아", "010-4444-5555", "서울", "4.개통완료", "SAMPLE-00004", "물류완료", "이처리", "설치완료", "카드", "150000", "정산완료", LocalDate.of(2026, 7, 8), LocalDate.of(2026, 7, 10), "지급완료", "본사", "60개월", ""},
{LocalDateTime.of(2026, 7, 8, 16, 40), "이상담", "서초점", "SKT", "상품권", "전화", "집전화 스마트", "정*민", "010-5555-6666", "부산", "3.서류검토", "SAMPLE-00005", "", "최처리", "서류요청", "현금", "15000", "정산대기", null, null, "미지급", "본사", "24개월", ""},
{LocalDateTime.of(2026, 7, 7, 9, 50), "김상담", "강남점", "KT", "가전", "공기청정기", "퓨리케어 360", "이*수", "010-6666-7777", "서울", "5.사은품지급", "SAMPLE-00006", "서울입고", "박처리", "사은품발송", "계좌", "90000", "정산대기", LocalDate.of(2026, 7, 6), LocalDate.of(2026, 7, 7), "지급완료", "본사", "36개월", ""},
{LocalDateTime.of(2026, 7, 6, 13, 15), "정상담", "송파점", "SKB", "상품권", "인터넷", "B tv 인터넷", "윤*희", "010-7777-8888", "경기", "6.정산완료", "SAMPLE-00007", "완료", "이처리", "정산", "현금", "75000", "정산완료", LocalDate.of(2026, 7, 5), LocalDate.of(2026, 7, 6), "지급완료", "영업점", "36개월", ""},
{LocalDateTime.of(2026, 7, 5, 15, 0), "이상담", "서초점", "LGU+", "캐시백", "와이파이", "와이파이 메시", "한*진", "010-8888-9999", "대전", "9.취소", "SAMPLE-00008", "", "최처리", "고객취소", "", "0", "회수대기", null, null, "반송", "", "12개월", ""},
{LocalDateTime.of(2026, 7, 11, 8, 45), "김상담", "강남점", "KT", "상품권", "인터넷", "안심 인터넷 베이직", "오*라", "010-1212-3434", "서울", "1.접수완료", "SAMPLE-00009", "", "박처리", "접수", "현금", "60000", "정산대기", null, null, "미지급", "본사", "36개월", ""},
{LocalDateTime.of(2026, 7, 11, 11, 10), "정상담", "송파점", "SKB", "가전", "비데", "스스로케어 비데", "서*호", "010-5656-7878", "경남", "2.고객통화", "SAMPLE-00010", "물류대기", "이처리", "통화예약", "계좌", "60000", "정산대기", null, null, "지급대기", "본사", "36개월", ""},
{LocalDateTime.of(2026, 7, 10, 9, 12), "김상담", "강남점", "KT", "상품권", "인터넷", "기가인터넷 1G", "홍*동", "010-1111-2222", "서울", "f01", "SAMPLE-00001", "서울입고", "박처리", "접수", "현금", "100000", "h01", null, null, "", "b01", "36개월", "02-1234-5678"},
{LocalDateTime.of(2026, 7, 10, 10, 30), "이상담", "서초점", "SKB", "가전", "TV", "B tv 프리미엄", "김*수", "010-2222-3333", "경기", "f03", "SAMPLE-00002", "", "최처리", "통화완료", "계좌", "80000", "h01", null, null, "g01", "b02", "24개월", ""},
{LocalDateTime.of(2026, 7, 9, 14, 5), "김상담", "강남점", "LGU+", "상품권", "인터넷", "U+ 인터넷", "박*지", "010-3333-4444", "인천", "f14", "SAMPLE-00003", "서울입고", "박처리", "개통", "현금", "120000", "h01", LocalDate.of(2026, 7, 9), null, "g01", "b01", "36개월", "032-555-1212"},
{LocalDateTime.of(2026, 7, 8, 11, 20), "정상담", "송파점", "KT", "캐시백", "정수기", "청호 450", "최*아", "010-4444-5555", "서울", "f23", "SAMPLE-00004", "물류완료", "이처리", "설치완료", "카드", "150000", "h01", LocalDate.of(2026, 7, 8), LocalDate.of(2026, 7, 10), "g07", "b01", "60개월", ""},
{LocalDateTime.of(2026, 7, 8, 16, 40), "이상담", "서초점", "SKT", "상품권", "전화", "집전화 스마트", "정*민", "010-5555-6666", "부산", "f05", "SAMPLE-00005", "", "최처리", "서류요청", "현금", "15000", "h01", null, null, "", "b01", "24개월", ""},
{LocalDateTime.of(2026, 7, 7, 9, 50), "김상담", "강남점", "KT", "가전", "공기청정기", "퓨리케어 360", "이*수", "010-6666-7777", "서울", "f14", "SAMPLE-00006", "서울입고", "박처리", "사은품발송", "계좌", "90000", "h01", LocalDate.of(2026, 7, 6), LocalDate.of(2026, 7, 7), "g07", "b01", "36개월", ""},
{LocalDateTime.of(2026, 7, 6, 13, 15), "정상담", "송파점", "SKB", "상품권", "인터넷", "B tv 인터넷", "윤*희", "010-7777-8888", "경기", "f24", "SAMPLE-00007", "완료", "이처리", "정산", "현금", "75000", "h02", LocalDate.of(2026, 7, 5), LocalDate.of(2026, 7, 6), "g07", "b02", "36개월", ""},
{LocalDateTime.of(2026, 7, 5, 15, 0), "이상담", "서초점", "LGU+", "캐시백", "와이파이", "와이파이 메시", "한*진", "010-8888-9999", "대전", "f11", "SAMPLE-00008", "", "최처리", "고객취소", "", "0", "h05", null, null, "g03", "", "12개월", ""},
{LocalDateTime.of(2026, 7, 11, 8, 45), "김상담", "강남점", "KT", "상품권", "인터넷", "안심 인터넷 베이직", "오*라", "010-1212-3434", "서울", "f01", "SAMPLE-00009", "", "박처리", "접수", "현금", "60000", "h01", null, null, "", "b01", "36개월", ""},
{LocalDateTime.of(2026, 7, 11, 11, 10), "정상담", "송파점", "SKB", "가전", "비데", "스스로케어 비데", "서*호", "010-5656-7878", "경남", "f03", "SAMPLE-00010", "물류대기", "이처리", "통화예약", "계좌", "60000", "h01", null, null, "g01", "b01", "36개월", ""},
};
for (Object[] s : rows) {
HomesReception r = new HomesReception();
@@ -2359,7 +2379,7 @@ class DataInitializer implements CommandLineRunner {
r.setSettleStatus(String.valueOf(s[17]));
r.setOpenDate(s[18] == null ? null : (LocalDate) s[18]);
r.setGiftDate(s[19] == null ? null : (LocalDate) s[19]);
r.setGiftStatus(String.valueOf(s[20]));
r.setGiftStatus(String.valueOf(s[20]).isBlank() ? null : String.valueOf(s[20]));
r.setGiftPlace(String.valueOf(s[21]));
r.setAgreementName(String.valueOf(s[22]));
r.setContractNumber(String.valueOf(s[23]));
@@ -2841,7 +2861,8 @@ class DataInitializer implements CommandLineRunner {
if (r.getTel() == null || r.getTel().isBlank()) r.setTel("02-100" + i);
if (r.getGiftSended() == null && r.getGiftDate() != null) r.setGiftSended(r.getGiftDate());
if (r.getGiftCashAmount() == null && r.getAmount() != null
&& r.getGiftStatus() != null && !"미지급".equals(r.getGiftStatus()) && !"없음".equals(r.getGiftCategory())) {
&& r.getGiftStatus() != null && !r.getGiftStatus().isBlank()
&& !"미지급".equals(r.getGiftStatus()) && !"없음".equals(r.getGiftCategory())) {
r.setGiftCashAmount(r.getAmount());
}
if (r.getIspName() == null || r.getIspName().isBlank()) r.setIspName(r.getCarrier());
@@ -2962,4 +2983,485 @@ class DataInitializer implements CommandLineRunner {
em.persist(s);
return s;
}
/**
* billing_react 정산현황 테스트용 샘플.
* 영업점/입력처 각각 가정산·정산대기·정산완료·송금(입금)완료 단계를 채운다.
*/
private void seedCalculateSamplesIfEmpty() {
Long exists = em.createQuery(
"select count(r) from HomesReception r where r.groupId='demo' and r.orderNumber like 'CALC-%'",
Long.class)
.getSingleResult();
if (exists != null && exists > 0) return;
LocalDateTime now = LocalDateTime.now(ZoneId.of("Asia/Seoul"));
String[][] branches = {
{"강남점", "본사그룹", "서울", "김상담", "박처리", "1599-1001"},
{"서초점", "본사그룹", "경기", "이상담", "최처리", "1599-1002"},
{"송파점", "동부그룹", "서울", "정상담", "이처리", "1599-1003"},
};
String[] carriers = {"KT", "SKB", "LGU+"};
String[] products = {"기가인터넷 1G", "B tv 프리미엄", "U+ 인터넷", "안심 인터넷"};
int seq = 1;
// ---- 영업점: 가정산 (cost 없음, f23 / 환수 f19·f22) ----
for (String[] b : branches) {
for (int i = 0; i < 3; i++) {
HomesReception r = calcReception(
"CALC-" + String.format("%04d", seq++),
now.minusDays(3 + i), b, carriers[i % 3], products[i % 4],
"정산고객" + seq, "f23", "h01", 100000 + i * 10000, true);
em.persist(r);
}
HomesReception claw = calcReception(
"CALC-" + String.format("%04d", seq++),
now.minusDays(20), b, carriers[0], products[0],
"환수고객" + seq, "f19", "h05", 80000, true);
claw.setPrevProgressStatus("f14");
em.persist(claw);
}
// ---- 영업점: 정산대기 (cost 있음, group 없음) ----
for (String[] b : branches) {
for (int i = 0; i < 2; i++) {
HomesReception r = calcReception(
"CALC-" + String.format("%04d", seq++),
now.minusDays(10 + i), b, carriers[i % 3], products[i % 4],
"대기고객" + seq, "f23", "h01", 120000 + i * 5000, true);
em.persist(r);
em.flush();
HomesReceptionCost c = calcCost(r, "i01", 1);
em.persist(c);
em.flush();
r.setReceptionCostId(c.getId());
}
HomesReception claw = calcReception(
"CALC-" + String.format("%04d", seq++),
now.minusDays(25), b, carriers[1], products[1],
"대기환수" + seq, "f22", "h08", 70000, true);
claw.setPrevProgressStatus("f14");
em.persist(claw);
em.flush();
HomesReceptionCost cc = calcCost(claw, "i05", -1);
em.persist(cc);
em.flush();
claw.setReceptionCostId(cc.getId());
}
// ---- 영업점: 정산완료 그룹 (미송금) ----
for (String[] b : branches) {
HomesReceptionCostGroup g = new HomesReceptionCostGroup();
g.setGroupId("demo");
g.setBranchName(b[0]);
g.setPlusAmt(5000);
g.setMinusAmt(0);
g.setIsTax(1);
g.setRate(BigDecimal.valueOf(10));
g.setCompletedAt(now.minusDays(2));
g.setMemo("샘플 정산완료 — " + b[0]);
g.setBank("국민");
g.setAccountNumber("123-456-" + b[0].hashCode() % 10000);
g.setAccountName(b[0] + "대표");
em.persist(g);
em.flush();
int lineSum = 0;
for (int i = 0; i < 2; i++) {
HomesReception r = calcReception(
"CALC-" + String.format("%04d", seq++),
now.minusDays(15 + i), b, carriers[i % 3], products[i % 4],
"완료고객" + seq, "f24", "h02", 110000 + i * 8000, true);
em.persist(r);
em.flush();
HomesReceptionCost c = calcCost(r, "i01", 1);
c.setCostGroupId(g.getId());
em.persist(c);
em.flush();
r.setReceptionCostId(c.getId());
lineSum += c.getTotal();
}
// 환수 1건
HomesReception claw = calcReception(
"CALC-" + String.format("%04d", seq++),
now.minusDays(30), b, carriers[2], products[2],
"완료환수" + seq, "f26", "h09", 50000, true);
em.persist(claw);
em.flush();
HomesReceptionCost cc = calcCost(claw, "i02", -1);
cc.setCostGroupId(g.getId());
em.persist(cc);
em.flush();
claw.setReceptionCostId(cc.getId());
lineSum += cc.getTotal();
g.setTotal(g.getPlusAmt() + g.getMinusAmt() + lineSum);
}
// ---- 영업점: 송금완료 그룹 ----
for (String[] b : branches) {
HomesReceptionCostGroup g = new HomesReceptionCostGroup();
g.setGroupId("demo");
g.setBranchName(b[0]);
g.setPlusAmt(0);
g.setMinusAmt(0);
g.setIsTax(1);
g.setRate(BigDecimal.valueOf(10));
g.setTaxDate(now.minusDays(8));
g.setCompletedAt(now.minusDays(7));
g.setTransferredAt(now.minusDays(5));
g.setMemo("샘플 송금완료 — " + b[0]);
g.setBank("신한");
g.setAccountNumber("110-222-" + Math.abs(b[0].hashCode() % 10000));
g.setAccountName(b[0]);
em.persist(g);
em.flush();
int lineSum = 0;
for (int i = 0; i < 2; i++) {
HomesReception r = calcReception(
"CALC-" + String.format("%04d", seq++),
now.minusDays(40 + i), b, carriers[i % 3], products[i % 4],
"송금고객" + seq, "f25", "h03", 130000 + i * 7000, true);
em.persist(r);
em.flush();
HomesReceptionCost c = calcCost(r, "i01", 1);
c.setCostGroupId(g.getId());
em.persist(c);
em.flush();
r.setReceptionCostId(c.getId());
lineSum += c.getTotal();
}
g.setTotal(lineSum);
}
// ---- 입력처: 가정산 (center cost 없음) — 영업점 대기와 별도 접수 ----
for (String[] b : branches) {
for (int i = 0; i < 2; i++) {
HomesReception r = calcReception(
"CALC-" + String.format("%04d", seq++),
now.minusDays(4 + i), b, carriers[i % 3], products[i % 4],
"입력가정산" + seq, "f14", "h01", 90000 + i * 3000, true);
// 영업점 정산과는 분리: 개통완료+입력처 정산대기
em.persist(r);
}
HomesReception claw = calcReception(
"CALC-" + String.format("%04d", seq++),
now.minusDays(18), b, carriers[0], products[0],
"입력환수" + seq, "f14", "h05", 60000, true);
em.persist(claw);
}
// ---- 입력처: 정산대기 ----
for (String[] b : branches) {
for (int i = 0; i < 2; i++) {
HomesReception r = calcReception(
"CALC-" + String.format("%04d", seq++),
now.minusDays(12 + i), b, carriers[i % 3], products[i % 4],
"입력대기" + seq, "f14", "h01", 95000 + i * 4000, true);
em.persist(r);
em.flush();
HomesReceptionCenterCost c = calcCenterCost(r, "k01", 1);
em.persist(c);
em.flush();
r.setReceptionCenterCostId(c.getId());
}
}
// ---- 입력처: 정산완료 그룹 ----
for (String[] b : branches) {
HomesReceptionCenterCostGroup g = new HomesReceptionCenterCostGroup();
g.setGroupId("demo");
g.setCenterName(b[4]); // processor
g.setPlusAmt(3000);
g.setMinusAmt(0);
g.setIsTax(1);
g.setRate(BigDecimal.valueOf(10));
g.setCompletedAt(now.minusDays(3));
g.setMemo("샘플 입력처 정산완료 — " + b[4]);
em.persist(g);
em.flush();
int lineSum = 0;
for (int i = 0; i < 2; i++) {
HomesReception r = calcReception(
"CALC-" + String.format("%04d", seq++),
now.minusDays(16 + i), b, carriers[i % 3], products[i % 4],
"입력완료" + seq, "f14", "h02", 88000 + i * 2000, true);
em.persist(r);
em.flush();
HomesReceptionCenterCost c = calcCenterCost(r, "k01", 1);
c.setCostGroupId(g.getId());
em.persist(c);
em.flush();
r.setReceptionCenterCostId(c.getId());
lineSum += c.getTotal();
}
g.setTotal(g.getPlusAmt() + lineSum);
}
// ---- 입력처: 입금완료 그룹 ----
for (String[] b : branches) {
HomesReceptionCenterCostGroup g = new HomesReceptionCenterCostGroup();
g.setGroupId("demo");
g.setCenterName(b[4]);
g.setPlusAmt(0);
g.setMinusAmt(0);
g.setIsTax(1);
g.setRate(BigDecimal.valueOf(10));
g.setCompletedAt(now.minusDays(9));
g.setTransferredAt(now.minusDays(6));
g.setMemo("샘플 입금완료 — " + b[4]);
em.persist(g);
em.flush();
int lineSum = 0;
HomesReception r = calcReception(
"CALC-" + String.format("%04d", seq++),
now.minusDays(35), b, carriers[0], products[0],
"입력입금" + seq, "f14", "h03", 77000, true);
em.persist(r);
em.flush();
HomesReceptionCenterCost c = calcCenterCost(r, "k01", 1);
c.setCostGroupId(g.getId());
em.persist(c);
em.flush();
r.setReceptionCenterCostId(c.getId());
lineSum += c.getTotal();
g.setTotal(lineSum);
}
em.flush();
}
private HomesReception calcReception(
String orderNo, LocalDateTime orderDate, String[] branch,
String carrier, String product, String customer,
String progress, String settle, int amount, boolean opened) {
HomesReception r = new HomesReception();
r.setGroupId("demo");
r.setOrderDate(orderDate);
r.setCounselor(branch[3]);
r.setBranch(branch[0]);
r.setBranchGroup(branch[1]);
r.setBranchPhone(branch[5]);
r.setRegion(branch[2]);
r.setProcessor(branch[4]);
r.setCarrier(carrier);
r.setIspName(carrier);
r.setGiftCategory("상품권");
r.setProductCategory("인터넷");
r.setProductType("e01");
r.setProductName(product);
r.setCustomerType("public");
r.setCustomerName(customer);
r.setPhone("010-" + String.format("%04d", Math.abs(orderNo.hashCode() % 10000)) + "-"
+ String.format("%04d", Math.abs(customer.hashCode() % 10000)));
r.setProgressStatus(progress);
r.setOrderNumber(orderNo);
r.setContractNumber(orderNo.replace("CALC-", "CT-"));
r.setInboundInfo("서울입고");
r.setDetailProgress("정산샘플");
r.setPaymentType("현금");
r.setPaymentMethod("transfer");
r.setAmount(BigDecimal.valueOf(amount));
r.setSettleStatus(settle);
if (opened) {
r.setOpenDate(orderDate.toLocalDate().minusDays(1));
r.setGiftDate(orderDate.toLocalDate());
r.setGiftStatus("g07");
r.setGiftPlace("b01");
r.setGiftSended(orderDate.toLocalDate());
}
r.setAgreementName("36개월");
r.setMemo("정산현황 테스트 샘플 (" + progress + "/" + settle + ")");
r.setPostIt("");
return r;
}
private HomesReceptionCost calcCost(HomesReception r, String type, int sign) {
HomesReceptionCost c = new HomesReceptionCost();
c.setGroupId("demo");
c.setReceptionId(r.getId());
c.setType(type);
c.setTypeDesc(ReceptionStatuses.COST_TYPE.getOrDefault(type, ""));
int base = r.getAmount() == null ? 0 : r.getAmount().intValue();
c.setCost(base * sign);
c.setEventAmt(0);
c.setAgreementAmt(0);
c.setLocalAmt(0);
// 사은품 차감(본사발송 가정)
int gift = ("g07".equals(r.getGiftStatus()) ? -10000 : 0) * sign;
c.setGiftAmt(gift);
c.setVocAmt(0);
c.setEtcAmt(0);
c.setTotal(c.getCost() + c.getGiftAmt());
c.setMemo("샘플 정산라인");
return c;
}
private HomesReceptionCenterCost calcCenterCost(HomesReception r, String type, int sign) {
HomesReceptionCenterCost c = new HomesReceptionCenterCost();
c.setGroupId("demo");
c.setReceptionId(r.getId());
c.setType(type);
c.setTypeDesc(ReceptionStatuses.CENTER_COST_TYPE.getOrDefault(type, ""));
int base = r.getAmount() == null ? 0 : r.getAmount().intValue();
// 입력처 수수료는 영업점보다 낮게
c.setCost((base / 2) * sign);
c.setEventAmt(0);
c.setAgreementAmt(0);
c.setLocalAmt(0);
c.setGiftAmt(0);
c.setVocAmt(0);
c.setEtcAmt(0);
c.setTotal(c.getCost());
c.setMemo("샘플 입력처 정산라인");
return c;
}
/**
* 수수료 현황 팝업 테스트용 — billing_imeere branch_fees / center_fees 샘플.
* 서초점·강남점 등은 수수료 있음, 일부 통신사 조합은 없음으로 "적용가능한 수수료가 없습니다" 검증.
*/
private void seedFeeSheetsIfEmpty() {
Long n = em.createQuery("select count(f) from HomesBranchFee f where f.groupId='demo'", Long.class)
.getSingleResult();
if (n != null && n > 0) return;
LocalDateTime start = LocalDateTime.of(2024, 1, 1, 0, 0);
String[][] branchFees = {
{"alone", "강남점", "본사그룹", "KT"},
{"alone", "서초점", "본사그룹", "KT"},
{"alone", "서초점", "본사그룹", "SKB"},
{"alone", "서초점", "본사그룹", "LGU+"},
{"alone", "송파점", "동부그룹", "KT"},
{"alone", "송파점", "동부그룹", "SKB"},
{"group", "", "본사그룹", "KT"},
{"group", "", "동부그룹", "SKB"},
};
Object[][] products = {
{"e01", "기가인터넷 1G", 100000},
{"e01", "안심 인터넷", 80000},
{"e01", "B tv 인터넷", 90000},
{"e01", "U+ 인터넷", 95000},
{"e03", "B tv 프리미엄", 50000},
{"e02", "집전화 스마트", 20000},
};
for (String[] bf : branchFees) {
HomesBranchFee fee = new HomesBranchFee();
fee.setGroupId("demo");
fee.setFeeType(bf[0]);
fee.setBranchName(blank(bf[1]) ? null : bf[1]);
fee.setBranchGroupName(blank(bf[2]) ? null : bf[2]);
fee.setCarrier(bf[3]);
fee.setStartDate(start);
fee.setMemo("샘플 수수료 (" + bf[0] + "/" + bf[3] + ")");
em.persist(fee);
em.flush();
for (Object[] p : products) {
if (!String.valueOf(p[1]).contains(bf[3].substring(0, 1))
&& !"KT".equals(bf[3]) && !"e01".equals(p[0])) {
// keep most products for all carriers
}
HomesBranchFeeProduct row = new HomesBranchFeeProduct();
row.setGroupId("demo");
row.setFeeId(fee.getId());
row.setProductType(String.valueOf(p[0]));
row.setProductName(String.valueOf(p[1]));
row.setFee((Integer) p[2]);
em.persist(row);
}
}
String[] centers = {"박처리", "최처리", "이처리"};
String[] carriers = {"KT", "SKB", "LGU+"};
for (String center : centers) {
for (String carrier : carriers) {
// 최처리+LGU+ 는 의도적으로 제외 → 없음 알림 테스트
if ("최처리".equals(center) && "LGU+".equals(carrier)) continue;
HomesCenterFee fee = new HomesCenterFee();
fee.setGroupId("demo");
fee.setCenterName(center);
fee.setCarrier(carrier);
fee.setStartDate(start);
fee.setMemo("샘플 입력처 수수료");
em.persist(fee);
em.flush();
for (Object[] p : products) {
HomesCenterFeeProduct row = new HomesCenterFeeProduct();
row.setGroupId("demo");
row.setFeeId(fee.getId());
row.setProductType(String.valueOf(p[0]));
row.setProductName(String.valueOf(p[1]));
row.setFee(((Integer) p[2]) / 2);
em.persist(row);
}
}
}
em.flush();
}
/** 영업점관리 목록 — 정산/수수료 샘플과 동일 영업점명 */
private void seedBranchesIfEmpty() {
Long n = em.createQuery("select count(b) from HomesBranch b where b.groupId='demo'", Long.class)
.getSingleResult();
if (n != null && n > 0) return;
HomesBranchGroup g1 = new HomesBranchGroup();
g1.setGroupId("demo");
g1.setName("본사그룹");
g1.setSeq(1);
em.persist(g1);
HomesBranchGroup g2 = new HomesBranchGroup();
g2.setGroupId("demo");
g2.setName("동부그룹");
g2.setSeq(2);
em.persist(g2);
em.flush();
Object[][] rows = {
// officeName, name, userid, sido, groupId, level, grant, phone, isTax, hasBiz, email
{"강남점", "강남대표", "gangnam", "서울", g1.getId(), 1, 1, "010-1111-1001", 1, true, "gangnam@demo.local"},
{"서초점", "서초대표", "seocho", "서울", g1.getId(), 1, 1, "010-2222-1002", 1, false, "seocho@demo.local"},
{"송파점", "송파대표", "songpa", "서울", g2.getId(), 1, 1, "010-3333-1003", 0, true, "songpa@demo.local"},
{"딜러스", "딜러스", "dealers", "대구", g2.getId(), 1, 1, "010-4444-2001", 1, false, "dealers@demo.local"},
{"미승인점", "대기초기", "pending1", "부산", g1.getId(), 1, 0, "010-5555-3001", 1, false, "pending@demo.local"},
{"박처리", "박처리", "center_park", "서울", null, 5, 1, "010-6666-4001", 1, false, "park@demo.local"},
};
for (Object[] r : rows) {
HomesBranch b = new HomesBranch();
b.setGroupId("demo");
b.setOfficeName(String.valueOf(r[0]));
b.setName(String.valueOf(r[1]));
b.setUserid(String.valueOf(r[2]));
b.setPasswordHash(encoder.encode("demo1234"));
b.setSido(String.valueOf(r[3]));
b.setBranchGroupId(r[4] == null ? null : (Long) r[4]);
b.setLevel((Integer) r[5]);
b.setGrantFlag((Integer) r[6]);
b.setPhone(String.valueOf(r[7]));
b.setTel("02-000-" + String.valueOf(r[2]).substring(0, Math.min(4, String.valueOf(r[2]).length())));
b.setIsTax((Integer) r[8]);
b.setHasBusinessLicense((Boolean) r[9]);
b.setEmail(String.valueOf(r[10]));
b.setAddress(b.getSido() + " " + b.getOfficeName() + " 1길 1");
b.setZipcode("06234");
b.setAccountBank("국민은행");
b.setAccountNumber("123-456-7890");
b.setAccountName(b.getName());
if (Integer.valueOf(5).equals(b.getLevel())) {
b.setCenterName(b.getOfficeName());
}
if (b.isHasBusinessLicense()) {
b.setLicenseNumber("123-45-67890");
b.setLicenseType("도소매");
b.setLicenseItem("통신판매");
}
em.persist(b);
}
em.flush();
}
private static boolean blank(String s) { return s == null || s.isBlank(); }
}
@@ -456,6 +456,8 @@ class HomesReception extends BaseEntity {
String region;
/** 진행상황 */
String progressStatus;
/** 직전 진행상태 (가정산 취소 복원용) */
String prevProgressStatus;
/** 주문번호 */
String orderNumber;
/** 연결된 ISP id */
@@ -535,6 +537,92 @@ class HomesReception extends BaseEntity {
String adminFile1;
String adminFile2;
String adminFile3;
/** 영업점 정산 cost FK (가정산→정산대기 시 연결) */
Long receptionCostId;
/** 입력처 정산 cost FK */
Long receptionCenterCostId;
}
/** 접수별 영업점 정산 비용 — billing order_costs */
@Entity @Table(name = "homes_reception_costs") @Getter @Setter @Builder @NoArgsConstructor @AllArgsConstructor
class HomesReceptionCost extends BaseEntity {
String type;
String typeDesc;
Integer cost = 0;
Integer eventAmt = 0;
Integer agreementAmt = 0;
Integer localAmt = 0;
Integer giftAmt = 0;
Integer vocAmt = 0;
Integer etcAmt = 0;
Integer total = 0;
@Column(columnDefinition = "TEXT") String progressMemo;
@Column(columnDefinition = "TEXT") String memo;
Long receptionId;
Long costGroupId;
}
/** 영업점 정산 그룹 — billing order_cost_groups */
@Entity @Table(name = "homes_reception_cost_groups") @Getter @Setter @Builder @NoArgsConstructor @AllArgsConstructor
class HomesReceptionCostGroup extends BaseEntity {
String branchName;
Integer plusAmt = 0;
Integer minusAmt = 0;
Integer total = 0;
/** 세율(%) — 세금계산서 10 / 종합소득 -3.3 */
java.math.BigDecimal rate = java.math.BigDecimal.ZERO;
/** 1=세금계산서발행, 0=종합소득세공제 */
Integer isTax = 1;
LocalDateTime taxDate;
LocalDateTime completedAt;
LocalDateTime transferredAt;
String bank;
String accountNumber;
String accountName;
@Column(columnDefinition = "TEXT") String memo;
String file1;
String file2;
String file3;
}
/** 접수별 입력처 정산 비용 — billing order_center_costs */
@Entity @Table(name = "homes_reception_center_costs") @Getter @Setter @Builder @NoArgsConstructor @AllArgsConstructor
class HomesReceptionCenterCost extends BaseEntity {
String type;
String typeDesc;
Integer cost = 0;
Integer eventAmt = 0;
Integer agreementAmt = 0;
Integer localAmt = 0;
Integer giftAmt = 0;
Integer vocAmt = 0;
Integer etcAmt = 0;
Integer total = 0;
@Column(columnDefinition = "TEXT") String progressMemo;
@Column(columnDefinition = "TEXT") String memo;
Long receptionId;
Long costGroupId;
}
/** 입력처 정산 그룹 — billing order_center_cost_groups */
@Entity @Table(name = "homes_reception_center_cost_groups") @Getter @Setter @Builder @NoArgsConstructor @AllArgsConstructor
class HomesReceptionCenterCostGroup extends BaseEntity {
String centerName;
Integer plusAmt = 0;
Integer minusAmt = 0;
Integer total = 0;
java.math.BigDecimal rate = java.math.BigDecimal.ZERO;
Integer isTax = 1;
LocalDateTime taxDate;
LocalDateTime completedAt;
LocalDateTime transferredAt;
String bank;
String accountNumber;
String accountName;
@Column(columnDefinition = "TEXT") String memo;
String file1;
String file2;
String file3;
}
/** 접수 진행 이력 */
@@ -585,3 +673,168 @@ class HomesReceptionIspAgreement extends BaseEntity {
String name;
Boolean active;
}
/** 영업점 그룹 — billing branch_groups */
@Entity @Table(name = "homes_branch_groups") @Getter @Setter @Builder @NoArgsConstructor @AllArgsConstructor
class HomesBranchGroup extends BaseEntity {
String name;
Integer seq = 0;
}
/**
* 영업점 마스터 — billing Branch + 상세(계좌/사업자/권한 등)를 한 테이블로 축소.
* officeName 은 접수·수수료의 branch / branchName 문자열과 맞춤.
*/
@Entity @Table(name = "homes_branches") @Getter @Setter @Builder @NoArgsConstructor @AllArgsConstructor
class HomesBranch extends BaseEntity {
String name;
String officeName;
String userid;
String passwordHash;
String tel;
String phone;
String fax;
String email;
String zipcode;
String address;
String addressDetail;
/** 지역(시/도) */
String sido;
/** 1=영업점, 5=센터, 9=관리자, 10=최고관리자 */
Integer level = 1;
/** 1=승인, 0=미승인(거부) */
Integer grantFlag = 0;
Long branchGroupId;
/** 레벨=센터일 때 입력처명 */
String centerName;
boolean hasBusinessLicense = false;
Integer isTax = 1;
String accountBank;
String accountNumber;
String accountName;
String contactPhone;
String contactNateon;
Integer acceptPhone = 0;
Integer acceptNateon = 0;
@Column(columnDefinition = "TEXT") String memo;
@Column(columnDefinition = "TEXT") String adminMemo;
String licenseNumber;
String licenseType;
String licenseItem;
/** JSON 배열 문자열, 최대 6개 */
@Column(columnDefinition = "TEXT") String notifyPhonesJson;
boolean permissionOrderDateUpdate = false;
boolean permissionCalculate = false;
boolean permissionConfig = false;
boolean permissionExcelDownload = false;
boolean permissionChart = false;
}
/**
* 영업점 수수료 시트 — billing branch_fees / branch_group_fees 축소.
* BillCare는 영업점명을 문자열로 쓰므로 branchName 키.
*/
@Entity @Table(name = "homes_branch_fees") @Getter @Setter @Builder @NoArgsConstructor @AllArgsConstructor
class HomesBranchFee extends BaseEntity {
/** alone=개별, group=그룹 */
String feeType;
String branchName;
String branchGroupName;
String carrier;
LocalDateTime startDate;
@Column(columnDefinition = "TEXT") String memo;
/** 첨부 표시용 파일명 (업로드는 선택 구현) */
String file1;
String file2;
String file3;
String file4;
String file5;
}
@Entity @Table(name = "homes_branch_fee_products") @Getter @Setter @Builder @NoArgsConstructor @AllArgsConstructor
class HomesBranchFeeProduct extends BaseEntity {
Long feeId;
String productType;
String productName;
Integer fee = 0;
}
/** 입력처 수수료 시트 — billing center_fees */
@Entity @Table(name = "homes_center_fees") @Getter @Setter @Builder @NoArgsConstructor @AllArgsConstructor
class HomesCenterFee extends BaseEntity {
String centerName;
String carrier;
LocalDateTime startDate;
@Column(columnDefinition = "TEXT") String memo;
}
@Entity @Table(name = "homes_center_fee_products") @Getter @Setter @Builder @NoArgsConstructor @AllArgsConstructor
class HomesCenterFeeProduct extends BaseEntity {
Long feeId;
String productType;
String productName;
Integer fee = 0;
}
/** 통신사 그룹 */
@Entity @Table(name="homes_isp_groups") @Getter @Setter @Builder @NoArgsConstructor @AllArgsConstructor
class HomesIspGroup extends BaseEntity {
String name;
Integer seq = 0;
}
/** 통신사 인증링크 */
@Entity @Table(name="homes_isp_links") @Getter @Setter @Builder @NoArgsConstructor @AllArgsConstructor
class HomesIspLink extends BaseEntity {
Long ispId;
String name;
String url;
Integer seq = 0;
Boolean active = true;
}
/** 입력처(센터) 마스터 — billing Center */
@Entity @Table(name="homes_input_centers") @Getter @Setter @Builder @NoArgsConstructor @AllArgsConstructor
class HomesInputCenter extends BaseEntity {
String name;
String tel;
String phone;
String manager;
Integer seq = 0;
Boolean active = true;
String accountBank;
String accountNumber;
String accountName;
@Column(columnDefinition="TEXT") String memo;
String file1; String file2; String file3; String file4; String file5;
}
/** 게시판 정의 — billing Board (HomesBbs는 게시글) */
@Entity @Table(name="homes_boards") @Getter @Setter @Builder @NoArgsConstructor @AllArgsConstructor
class HomesBoard extends BaseEntity {
String name;
Integer listLevel = 1;
Integer readLevel = 1;
Integer writeLevel = 9;
Integer seq = 0;
Boolean active = true;
}
/** 퀵전송 입력처 인증 */
@Entity @Table(name="homes_center_quick_auths") @Getter @Setter @Builder @NoArgsConstructor @AllArgsConstructor
class HomesCenterQuickAuth extends BaseEntity {
Long centerId;
String userid;
String passwordEnc;
String baseUrl;
Boolean authorized = false;
}
/** 퀵전송 진행알림 로그 */
@Entity @Table(name="homes_quick_delivery_logs") @Getter @Setter @Builder @NoArgsConstructor @AllArgsConstructor
class HomesQuickDeliveryLog extends BaseEntity {
String name;
String status;
@Column(columnDefinition="TEXT") String memo;
Long orderQuickDeliveryClientId;
}
@@ -0,0 +1,215 @@
package com.bizcare.agent;
import java.util.*;
/**
* billing_react AppConstant 의 접수/사은품/입력처/정산 상태·전이를 BillCare 에 이식.
*/
public final class ReceptionStatuses {
private ReceptionStatuses() {}
public static final List<String> PRE_CALC_STATUSES =
List.of("f19", "f20", "f21", "f22", "f28", "f23");
public static final List<String> CENTER_PRE_CALC_STATUSES =
List.of("h01", "h05", "h06", "h07", "h08", "h11");
public static final Map<String, String> PROGRESS = map(
"f01", "접수", "f02", "재접수", "f03", "접수진행중", "f04", "접수완료",
"f05", "승인대기중", "f06", "회신요청", "f07", "접수반송", "f08", "미개통오더반송",
"f09", "TC실패반송", "f10", "접수보류", "f11", "접수취소", "f12", "개통중",
"f13", "기사완료", "f14", "개통완료", "f15", "개통보류", "f16", "개통취소",
"f17", "개통불가", "f18", "정산보류", "f19", "위면해지", "f20", "직권해지",
"f21", "유보중단환수", "f22", "해지", "f23", "정산대기", "f24", "정산완료",
"f25", "송금완료", "f26", "환수정산완료", "f27", "환수완료",
"f28", "VOC(귀책)차감", "f29", "VOC(귀책)차감완료");
public static final Map<String, String> GIFT = map(
"g01", "지급요청", "g02", "지급재요청", "g03", "반송", "g04", "처리중",
"g05", "지급보류", "g06", "지급취소", "g07", "지급완료", "g08", "회수요청",
"g09", "회수(반환)완료", "g10", "등록완료", "g11", "삭제완료", "g12", "입금증요청",
"g13", "입금증완료", "g14", "입금증첨부완료", "g15", "입금증(센터)전달완료");
public static final Map<String, String> CENTER = map(
"h01", "정산대기", "h02", "정산완료", "h03", "입금완료", "h04", "정산보류",
"h05", "위면해지", "h06", "직권해지", "h07", "유보중단환수", "h08", "해지",
"h09", "해지정산완료", "h10", "해지송금완료", "h11", "VOC(귀책)송금", "h12", "VOC(귀책)송금완료");
public static final Map<String, String> GIFT_PLACE = map(
"b02", "자체발송", "b01", "본사발송", "b03", "본사(센터)", "b04", "본사(관리자)");
public static final Map<String, String> COST_TYPE = map(
"i01", "정산", "i02", "위면해지", "i03", "직권해지", "i04", "유보중단환수", "i05", "해지", "i06", "voc차감");
public static final Map<String, String> CENTER_COST_TYPE = map(
"k01", "정산", "k02", "위면해지", "k03", "직권해지", "k04", "유보중단환수", "k05", "해지", "k06", "voc차감");
/** 구 BillCare 한글 라벨 → 코드 */
public static final Map<String, String> LEGACY_PROGRESS = Map.ofEntries(
Map.entry("1.접수완료", "f04"),
Map.entry("2.고객통화", "f03"),
Map.entry("3.서류검토", "f05"),
Map.entry("4.개통완료", "f14"),
Map.entry("5.사은품지급", "f14"),
Map.entry("6.정산완료", "f24"),
Map.entry("9.취소", "f11"),
Map.entry("접수", "f01"),
Map.entry("접수완료", "f04"),
Map.entry("개통완료", "f14"),
Map.entry("정산대기", "f23"),
Map.entry("정산완료", "f24"),
Map.entry("송금완료", "f25"),
Map.entry("접수취소", "f11")
);
public static final Map<String, String> LEGACY_GIFT = Map.of(
"미지급", "",
"지급대기", "g01",
"지급요청", "g01",
"지급완료", "g07",
"반송", "g03"
);
public static final Map<String, String> LEGACY_CENTER = Map.of(
"정산대기", "h01",
"정산완료", "h02",
"입금완료", "h03",
"회수대기", "h05",
"회수완료", "h09",
"미지정", "h01"
);
public static List<String> progressCodes() {
return new ArrayList<>(PROGRESS.keySet());
}
public static List<String> giftCodes() {
return new ArrayList<>(GIFT.keySet());
}
public static List<String> centerCodes() {
return new ArrayList<>(CENTER.keySet());
}
public static String progressLabel(String code) {
if (code == null || code.isBlank()) return "";
return PROGRESS.getOrDefault(code, code);
}
public static String giftLabel(String code) {
if (code == null || code.isBlank()) return "";
return GIFT.getOrDefault(code, code);
}
public static String centerLabel(String code) {
if (code == null || code.isBlank()) return "";
return CENTER.getOrDefault(code, code);
}
public static String normalizeProgress(String raw) {
if (raw == null || raw.isBlank()) return "";
if (PROGRESS.containsKey(raw)) return raw;
return LEGACY_PROGRESS.getOrDefault(raw, raw);
}
public static String normalizeGift(String raw) {
if (raw == null) return "";
if (raw.isBlank() || "미지급".equals(raw)) return "";
if (GIFT.containsKey(raw)) return raw;
return LEGACY_GIFT.getOrDefault(raw, raw);
}
public static String normalizeCenter(String raw) {
if (raw == null || raw.isBlank()) return "";
if (CENTER.containsKey(raw)) return raw;
return LEGACY_CENTER.getOrDefault(raw, raw);
}
public static Set<String> orderStatusTransitions(String current) {
if (current == null) return Set.of();
return switch (current) {
case "f01", "f02", "f03", "f04", "f05", "f06", "f07", "f08", "f09", "f10", "f11", "f12",
"f13", "f15", "f16", "f17" ->
set("f01", "f02", "f03", "f04", "f05", "f06", "f07", "f08", "f09",
"f10", "f11", "f12", "f13", "f14", "f15", "f16", "f17", "f18");
case "f18", "f14" ->
set("f01", "f02", "f03", "f04", "f05", "f06", "f07", "f08", "f09",
"f10", "f11", "f12", "f13", "f14", "f15", "f16", "f17", "f18",
"f19", "f20", "f21", "f22", "f23");
case "f23" -> set("f23");
case "f24" -> set("f24");
case "f25" -> set("f25", "f19", "f20", "f21", "f22", "f28");
case "f19" -> set("f19");
case "f20" -> set("f20");
case "f21" -> set("f21");
case "f22" -> set("f22");
case "f28" -> set("f28");
case "f29" -> set("f28", "f29", "f19", "f20", "f21", "f22");
case "f26" -> set("f26");
case "f27" -> set("f27");
default -> Set.of();
};
}
public static Set<String> centerStatusTransitions(String current) {
if (current == null || current.isBlank()) return set("h01");
return switch (current) {
case "h01" -> set("h01");
case "h02" -> set("h02");
case "h03" -> set("h03", "h05", "h06", "h07", "h08", "h11");
case "h04" -> set("h04", "h01");
case "h05" -> set("h05");
case "h06" -> set("h06");
case "h07" -> set("h07");
case "h08" -> set("h08");
case "h09" -> set("h09");
case "h10" -> set("h10");
case "h11" -> set("h11");
case "h12" -> set("h05", "h06", "h07", "h08", "h11");
default -> Set.of();
};
}
public static String orderCostType(String orderStatus) {
if (orderStatus == null) return "";
return switch (orderStatus) {
case "f19" -> "i02";
case "f20" -> "i03";
case "f21" -> "i04";
case "f22" -> "i05";
case "f28" -> "i06";
case "f23" -> "i01";
default -> "";
};
}
public static String centerCostType(String status) {
if (status == null) return "";
return switch (status) {
case "h05" -> "k02";
case "h06" -> "k03";
case "h07" -> "k04";
case "h08" -> "k05";
case "h11" -> "k06";
case "h01" -> "k01";
default -> "";
};
}
public static boolean isPreCalc(String status) {
return status != null && PRE_CALC_STATUSES.contains(status);
}
public static boolean isCenterPreCalc(String status) {
return status != null && CENTER_PRE_CALC_STATUSES.contains(status);
}
private static Map<String, String> map(String... kv) {
Map<String, String> m = new LinkedHashMap<>();
for (int i = 0; i < kv.length; i += 2) m.put(kv[i], kv[i + 1]);
return Collections.unmodifiableMap(m);
}
private static Set<String> set(String... v) {
return new LinkedHashSet<>(Arrays.asList(v));
}
}