공지사항 메뉴 수정

This commit is contained in:
Macbook
2026-07-13 10:07:33 +09:00
parent af45c5dbee
commit 2d26735463
17 changed files with 1217 additions and 9 deletions
@@ -2373,6 +2373,153 @@ class CareHomesController {
return (asc ? c : c.reversed()).thenComparing(HomesBbs::getId, Comparator.reverseOrder());
}
/** 로그인 후 홈 팝업용 — isPopup=1 이고 기간 유효한 공지 (최근 10건) */
@GetMapping("/announces/popups") ApiResponse announcePopups(@AuthenticationPrincipal JwtUser u) {
LocalDateTime now = LocalDateTime.now();
List<Map<String, Object>> list = listAll(HomesAnnounce.class, u).stream()
.filter(a -> a.getIsPopup() != null && a.getIsPopup() == 1)
.filter(a -> a.getPopupStart() == null || !a.getPopupStart().isAfter(now))
.filter(a -> a.getPopupEnd() == null || !a.getPopupEnd().isBefore(now))
.sorted(Comparator.comparing(HomesAnnounce::getId, Comparator.nullsLast(Long::compareTo)).reversed())
.limit(10)
.map(this::announceMap)
.toList();
return ApiResponse.ok(list);
}
@GetMapping("/announces") ApiResponse announces(@AuthenticationPrincipal JwtUser u, @RequestParam Map<String, String> f,
@RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "15") int size) {
String searchType = blankToNull(f.get("searchType"));
String q = qOf(f);
List<HomesAnnounce> filtered = listAll(HomesAnnounce.class, u).stream().filter(a -> {
if (q == null) return true;
String hay = switch (searchType == null ? "제목" : searchType) {
case "직원", "작성자" -> nullToEmpty(a.getAuthorName());
case "내용" -> nullToEmpty(a.getContents());
default -> nullToEmpty(a.getTitle());
};
return hay.toLowerCase().contains(q.toLowerCase());
}).sorted(announceComparator(f.get("sort"))).toList();
int from = Math.max(0, page) * Math.max(1, size);
List<Map<String, Object>> list = new ArrayList<>();
int i = 0;
for (HomesAnnounce a : filtered.stream().skip(from).limit(Math.min(Math.max(size, 1), 200)).toList()) {
Map<String, Object> row = announceMap(a);
row.put("no", filtered.size() - from - i);
list.add(row);
i++;
}
Map<String, Object> data = new LinkedHashMap<>();
data.put("total", filtered.size());
data.put("list", list);
return ApiResponse.ok(data);
}
@GetMapping("/announces/{id}") @Transactional ApiResponse announceGet(@PathVariable Long id, @AuthenticationPrincipal JwtUser u) {
HomesAnnounce a = crud.get(HomesAnnounce.class, id, u);
a.setViews((a.getViews() == null ? 0 : a.getViews()) + 1);
return ApiResponse.ok(announceMap(a));
}
@PostMapping("/announces") @Transactional ApiResponse announcePost(@RequestBody Map<String, Object> d, @AuthenticationPrincipal JwtUser u) {
d.putIfAbsent("authorName", u.getUsername());
d.putIfAbsent("views", 0);
applyAnnouncePopupFields(d);
HomesAnnounce a = crud.create(HomesAnnounce.class, d, u);
writeLog(u, "ANNOUNCE", "공지사항-등록", "공지 [" + nullToEmpty(a.getTitle()) + "] 등록하였습니다.");
return ApiResponse.ok(announceMap(a));
}
@PutMapping("/announces/{id}") @Transactional ApiResponse announcePut(@PathVariable Long id, @RequestBody Map<String, Object> d, @AuthenticationPrincipal JwtUser u) {
applyAnnouncePopupFields(d);
HomesAnnounce a = crud.update(HomesAnnounce.class, id, d, u);
writeLog(u, "ANNOUNCE", "공지사항-수정", "공지 [" + nullToEmpty(a.getTitle()) + "] 수정하였습니다.");
return ApiResponse.ok(announceMap(a));
}
@DeleteMapping("/announces/{id}") @Transactional ApiResponse announceDel(@PathVariable Long id, @AuthenticationPrincipal JwtUser u) {
HomesAnnounce a = crud.get(HomesAnnounce.class, id, u);
writeLog(u, "ANNOUNCE", "공지사항-삭제", "공지 [" + nullToEmpty(a.getTitle()) + "] 삭제하였습니다.");
crud.delete(HomesAnnounce.class, id, u);
return ApiResponse.ok(null);
}
/** 팝업 ON/OFF 토글 */
@PostMapping("/announces/{id}/popup") @Transactional ApiResponse announceTogglePopup(@PathVariable Long id, @AuthenticationPrincipal JwtUser u) {
HomesAnnounce a = crud.get(HomesAnnounce.class, id, u);
boolean on = a.getIsPopup() != null && a.getIsPopup() == 1;
a.setIsPopup(on ? 0 : 1);
LocalDateTime now = LocalDateTime.now();
if (!on) {
if (a.getPopupStart() == null) a.setPopupStart(now);
if (a.getPopupEnd() == null) a.setPopupEnd(now.plusDays(7));
}
return ApiResponse.ok(announceMap(a));
}
private void applyAnnouncePopupFields(Map<String, Object> d) {
Object popup = d.get("isPopup");
boolean on = popup instanceof Number n ? n.intValue() == 1
: popup instanceof Boolean b && b
|| "1".equals(String.valueOf(popup)) || "true".equalsIgnoreCase(String.valueOf(popup));
LocalDateTime now = LocalDateTime.now();
d.put("isPopup", on ? 1 : 0);
if (on) {
LocalDateTime start = parseDateTime(d.get("popupStart"));
LocalDateTime end = parseDateTime(d.get("popupEnd"));
d.put("popupStart", start != null ? start : now);
d.put("popupEnd", end != null ? end : now.plusDays(7));
} else {
d.put("popupStart", now);
d.put("popupEnd", now);
}
}
private LocalDateTime parseDateTime(Object v) {
if (v == null) return null;
String s = String.valueOf(v).trim().replace(' ', 'T');
if (s.isEmpty() || "null".equals(s)) return null;
try {
if (s.length() == 10) return LocalDate.parse(s).atStartOfDay();
if (s.length() == 16) return LocalDateTime.parse(s + ":00");
return LocalDateTime.parse(s.length() > 19 ? s.substring(0, 19) : s);
} catch (Exception e) {
return null;
}
}
private Comparator<HomesAnnounce> announceComparator(String sort) {
String field = sort == null || sort.isBlank() ? "id,desc" : sort;
boolean asc = field.endsWith(",asc");
String key = field.split(",")[0];
Comparator<HomesAnnounce> c = switch (key) {
case "views", "조회" -> Comparator.comparing(a -> a.getViews() == null ? 0 : a.getViews());
case "authorName", "직원", "작성자" -> Comparator.comparing(HomesAnnounce::getAuthorName, Comparator.nullsLast(String::compareTo));
case "createdDate", "createdAt", "등록일" -> Comparator.comparing(HomesAnnounce::getCreatedAt, Comparator.nullsLast(Instant::compareTo));
case "title", "제목" -> Comparator.comparing(HomesAnnounce::getTitle, Comparator.nullsLast(String::compareTo));
case "isPopup", "팝업" -> Comparator.comparing(a -> a.getIsPopup() == null ? 0 : a.getIsPopup());
default -> Comparator.comparing(HomesAnnounce::getId, Comparator.nullsLast(Long::compareTo));
};
return (asc ? c : c.reversed()).thenComparing(HomesAnnounce::getId, Comparator.reverseOrder());
}
private Map<String, Object> announceMap(HomesAnnounce a) {
Map<String, Object> m = new LinkedHashMap<>();
m.put("id", a.getId());
m.put("title", a.getTitle());
m.put("authorName", a.getAuthorName());
m.put("contents", a.getContents());
m.put("content", a.getContents());
m.put("views", a.getViews());
m.put("isPopup", a.getIsPopup() == null ? 0 : a.getIsPopup());
m.put("popupStart", a.getPopupStart() == null ? null
: a.getPopupStart().format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss")));
m.put("popupEnd", a.getPopupEnd() == null ? null
: a.getPopupEnd().format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss")));
m.put("createdDate", a.getCreatedAt() == null ? null : LocalDate.ofInstant(a.getCreatedAt(), ZoneId.of("Asia/Seoul")).toString());
return m;
}
@GetMapping("/logs") ApiResponse logs(@AuthenticationPrincipal JwtUser u, @RequestParam Map<String, String> f,
@RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "10") int size) {
String domain = blankToNull(f.get("domain"));
@@ -2343,6 +2343,20 @@ class DataInitializer implements CommandLineRunner {
em.remove(b);
}
em.flush();
// 사내 공지사항 샘플 (팝업 ON → 로그인 후 홈에서 표시)
if (em.createQuery("select count(a) from HomesAnnounce a where a.groupId='demo'", Long.class).getSingleResult() == 0) {
HomesAnnounce a = new HomesAnnounce();
a.setGroupId("demo");
a.setTitle("BizCare 공지사항 안내");
a.setAuthorName("김대표");
a.setContents("팝업이 ON인 공지사항은 로그인 후 초기화면에 표시됩니다.\n공지사항 메뉴에서 등록·팝업 기간을 설정하세요.");
a.setViews(0);
a.setIsPopup(1);
a.setPopupStart(LocalDateTime.now().minusDays(1));
a.setPopupEnd(LocalDateTime.now().plusMonths(1));
em.persist(a);
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", "서울", "f01", "SAMPLE-00001", "서울입고", "박처리", "접수", "현금", "100000", "h01", null, null, "", "b01", "36개월", "02-1234-5678"},
@@ -413,6 +413,18 @@ class HomesBbs extends BaseEntity {
Integer views;
}
/** 사내 공지사항 (billing_imeere posts / board_id=1 대응) — 팝업 ON 시 로그인 후 홈에서 표시 */
@Entity @Table(name="homes_announces") @Getter @Setter @Builder @NoArgsConstructor @AllArgsConstructor
class HomesAnnounce extends BaseEntity {
String title, authorName;
@Column(columnDefinition="TEXT") String contents;
Integer views;
/** 1=팝업 ON, 0=OFF */
Integer isPopup;
LocalDateTime popupStart;
LocalDateTime popupEnd;
}
/** 접수관리 — billing_react orders 목록 필드를 Care Homes 멀티테넌트에 맞게 정리 */
@Entity @Table(name="homes_receptions") @Getter @Setter @Builder @NoArgsConstructor @AllArgsConstructor
class HomesReception extends BaseEntity {