diff --git a/WebContent/jsp/onl/apim/portalterms/portalTermsMan.jsp b/WebContent/jsp/onl/apim/portalterms/portalTermsMan.jsp
index 09faeab..066b07f 100644
--- a/WebContent/jsp/onl/apim/portalterms/portalTermsMan.jsp
+++ b/WebContent/jsp/onl/apim/portalterms/portalTermsMan.jsp
@@ -14,6 +14,12 @@
+
@@ -154,6 +179,7 @@
+ tune 약관 종류 관리
add <%= localeMessage.getString("button.new") %>
<%-- search <%= localeMessage.getString("button.search") %> --%>
diff --git a/WebContent/jsp/onl/apim/portalterms/portalTermsTypeManPopup.jsp b/WebContent/jsp/onl/apim/portalterms/portalTermsTypeManPopup.jsp
new file mode 100644
index 0000000..b80f855
--- /dev/null
+++ b/WebContent/jsp/onl/apim/portalterms/portalTermsTypeManPopup.jsp
@@ -0,0 +1,210 @@
+<%-- portalTermsTypeManPopup.jsp : 약관 종류 관리(사용 여부 / 약관 페이지 노출 순서) --%>
+<%@ page language="java" contentType="text/html; charset=utf-8"%>
+<%@ page import="java.io.*"%>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
+<%@ include file="/jsp/common/include/localemessage.jsp" %>
+<%
+ response.setHeader("Pragma", "No-cache");
+ response.setHeader("Cache-Control", "no-cache");
+ response.setHeader("Expires", "0");
+%>
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/java/com/eactive/eai/rms/onl/apim/portalterms/PortalTermsManController.java b/src/main/java/com/eactive/eai/rms/onl/apim/portalterms/PortalTermsManController.java
index 418bde8..bba5fcd 100644
--- a/src/main/java/com/eactive/eai/rms/onl/apim/portalterms/PortalTermsManController.java
+++ b/src/main/java/com/eactive/eai/rms/onl/apim/portalterms/PortalTermsManController.java
@@ -28,10 +28,13 @@ import java.util.stream.Collectors;
@Controller
public class PortalTermsManController extends BaseAnnotationController {
private final PortalTermsManService portalTermsManService;
+ private final PortalTermsTypeConfigService portalTermsTypeConfigService;
@Autowired
- public PortalTermsManController(PortalTermsManService portalTermsManService){
+ public PortalTermsManController(PortalTermsManService portalTermsManService,
+ PortalTermsTypeConfigService portalTermsTypeConfigService){
this.portalTermsManService = portalTermsManService;
+ this.portalTermsTypeConfigService = portalTermsTypeConfigService;
}
@GetMapping(value = "/onl/apim/portalterms/portalTermsMan.view")
@@ -44,6 +47,12 @@ public class PortalTermsManController extends BaseAnnotationController {
return "/onl/apim/portalterms/portalTermsManDetail";
}
+ /** 약관 종류 관리 팝업 (사용 여부 / 약관 페이지 노출 순서) */
+ @GetMapping(value = "/onl/apim/portalterms/portalTermsMan.view", params = "cmd=TYPE_CONFIG_POPUP")
+ public String typeConfigPopupView() {
+ return "/onl/apim/portalterms/portalTermsTypeManPopup";
+ }
+
@PostMapping(value = "/onl/apim/portalterms/portalTermsMan.json", params = "cmd=LIST")
public ResponseEntity
> selectList(@SortDefault.SortDefaults({
@@ -56,11 +65,12 @@ public class PortalTermsManController extends BaseAnnotationController {
@PostMapping(value = "/onl/apim/portalterms/portalTermsMan.json", params = "cmd=LIST_INIT_COMBO")
public ResponseEntity> initCombo() {
+ Set enabledTypes = portalTermsTypeConfigService.getEnabledTypes();
List termsTypeList = Arrays.stream(AgreementType.values())
.map(type -> {
ComboVo vo = new ComboVo();
vo.setCode(type.name());
- vo.setCodename(PortalTermsManService.displayLabel(type));
+ vo.setCodename(PortalTermsManService.displayLabel(type, enabledTypes));
return vo;
}).collect(Collectors.toList());
@@ -69,6 +79,27 @@ public class PortalTermsManController extends BaseAnnotationController {
return ResponseEntity.ok(resultMap);
}
+ /** 약관 종류 관리(사용 여부 / 약관 페이지 노출 순서) 조회 */
+ @PostMapping(value = "/onl/apim/portalterms/portalTermsMan.json", params = "cmd=TYPE_CONFIG")
+ public ResponseEntity> selectTypeConfig() {
+ Map resultMap = new HashMap<>();
+ resultMap.put("typeRows", portalTermsTypeConfigService.loadTypeConfig());
+ return ResponseEntity.ok(resultMap);
+ }
+
+ /**
+ * 약관 종류 관리 저장.
+ *
+ * @param enabledTypes 사용할 약관 종류 코드(쉼표 구분)
+ * @param displayTypes 약관 페이지에 배치한 약관 종류 코드(쉼표 구분, 기재 순서 = 노출 순서)
+ */
+ @PostMapping(value = "/onl/apim/portalterms/portalTermsMan.json", params = "cmd=TYPE_CONFIG_SAVE")
+ public ResponseEntity saveTypeConfig(@RequestParam(value = "enabledTypes", required = false) String enabledTypes,
+ @RequestParam(value = "displayTypes", required = false) String displayTypes) {
+ portalTermsTypeConfigService.saveTypeConfig(enabledTypes, displayTypes);
+ return ResponseEntity.ok().build();
+ }
+
@PostMapping(value = "/onl/apim/portalterms/portalTermsMan.json", params = "cmd=DETAIL")
public ResponseEntity selectDetail(String agreementsType, int revision) {
PortalTermsUI portalTermsUI = portalTermsManService.selectDetail(agreementsType, revision);
diff --git a/src/main/java/com/eactive/eai/rms/onl/apim/portalterms/PortalTermsManService.java b/src/main/java/com/eactive/eai/rms/onl/apim/portalterms/PortalTermsManService.java
index c9d6696..ae26fd6 100644
--- a/src/main/java/com/eactive/eai/rms/onl/apim/portalterms/PortalTermsManService.java
+++ b/src/main/java/com/eactive/eai/rms/onl/apim/portalterms/PortalTermsManService.java
@@ -11,6 +11,7 @@ import com.eactive.apim.portal.user.entity.UserInfo;
import com.eactive.eai.rms.common.base.BaseService;
import com.eactive.eai.rms.data.entity.man.user.UserInfoService;
import com.eactive.eai.rms.data.entity.onl.apim.portalterms.PortalTermsService;
+import com.eactive.eai.rms.onl.common.exception.BizException;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
@@ -21,7 +22,6 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
-import java.util.EnumSet;
import java.util.HashSet;
import java.util.Set;
@@ -34,6 +34,7 @@ public class PortalTermsManService extends BaseService {
private final UserInfoService userInfoService;
private final EditorImageService editorImageService;
private final FileService fileService;
+ private final PortalTermsTypeConfigService portalTermsTypeConfigService;
@Autowired
public PortalTermsManService(PortalTermsService portalTermsService,
@@ -41,30 +42,28 @@ public class PortalTermsManService extends BaseService {
AgreementsService agreementsService,
UserInfoService userInfoService,
EditorImageService editorImageService,
- FileService fileService){
+ FileService fileService,
+ PortalTermsTypeConfigService portalTermsTypeConfigService){
this.portalTermsService = portalTermsService;
this.portalTermsUIMapper = portalTermsUIMapper;
this.agreementsService = agreementsService;
this.userInfoService = userInfoService;
this.editorImageService = editorImageService;
this.fileService = fileService;
+ this.portalTermsTypeConfigService = portalTermsTypeConfigService;
}
- /** 관리 화면에서 '(사용안함)'으로 표시할(=신규 등록에 더 이상 쓰지 않는) 약관 유형 */
- private static final Set DEPRECATED_AGREEMENT_TYPES = EnumSet.of(
- AgreementType.PRIVACY_POLICY,
- AgreementType.PRIVACY_COLLECT_IND,
- AgreementType.PRIVACY_COLLECT_ORG);
-
/**
* 관리 화면(콤보/목록)에 표시할 약관 유형 라벨.
- * 개인정보처리방침 및 개인용/법인용 개인정보수집동의서는 신규 통합 유형 {@link AgreementType#PRIVACY_COLLECT} 등으로
- * 대체될 예정이므로 '(사용안함)'을 덧붙여 안내한다.
+ * 약관 목록 화면 하단의 '약관 종류 관리'에서 사용 OFF 한 유형은 '(사용안함)'을 덧붙여 안내한다
+ * (기존 데이터 조회/수정은 계속 가능해야 하므로 목록·콤보에서 감추지는 않는다).
* enum 자체의 description은 포털 사용자 화면(AgreementsController#agreementTitle)에서도 쓰이므로 변경하지 않는다.
+ *
+ * @param enabledTypes 시스템에서 사용하는 약관 유형 ({@link PortalTermsTypeConfigService#getEnabledTypes()})
*/
- public static String displayLabel(AgreementType type) {
+ public static String displayLabel(AgreementType type, Set enabledTypes) {
String label = type.getDescription();
- if (DEPRECATED_AGREEMENT_TYPES.contains(type)) {
+ if (!enabledTypes.contains(type)) {
label += "(사용안함)";
}
return label;
@@ -82,12 +81,13 @@ public class PortalTermsManService extends BaseService {
}
public Page selectList(Pageable pageable, String searchAgreementsType) {
+ Set enabledTypes = portalTermsTypeConfigService.getEnabledTypes();
return portalTermsService.findAll(pageable, searchAgreementsType)
.map(agreements -> {
PortalTermsUI portalTermsUI = portalTermsUIMapper.toVo(agreements);
portalTermsUI.setCreatedByName(findName(agreements.getCreatedBy()));
portalTermsUI.setLastModifiedByName(findName(agreements.getLastModifiedBy()));
- portalTermsUI.setName(displayLabel(agreements.getAgreementsType()));
+ portalTermsUI.setName(displayLabel(agreements.getAgreementsType(), enabledTypes));
return portalTermsUI;
});
}
@@ -119,7 +119,13 @@ public class PortalTermsManService extends BaseService {
}
public void insert(PortalTermsUI portalTermsUI, MultipartFile attachedFile) throws IOException {
- portalTermsUI.setName(AgreementType.fromCode(portalTermsUI.getAgreementsType()).getDescription());
+ AgreementType type = AgreementType.fromCode(portalTermsUI.getAgreementsType());
+ // 사용하지 않는 약관 종류로는 신규 등록할 수 없다(기존 데이터 수정은 허용).
+ if (!portalTermsTypeConfigService.getEnabledTypes().contains(type)) {
+ throw new BizException("사용하지 않는 약관 종류입니다. 약관 목록 화면의 '약관 종류 관리'에서 먼저 사용으로 변경해 주세요. : "
+ + type.getDescription());
+ }
+ portalTermsUI.setName(type.getDescription());
portalTermsUI.setContents(StringEscapeUtils.unescapeHtml(portalTermsUI.getContents()));
Agreements agreements = portalTermsUIMapper.toEntity(portalTermsUI);
diff --git a/src/main/java/com/eactive/eai/rms/onl/apim/portalterms/PortalTermsTypeConfigService.java b/src/main/java/com/eactive/eai/rms/onl/apim/portalterms/PortalTermsTypeConfigService.java
new file mode 100644
index 0000000..336635f
--- /dev/null
+++ b/src/main/java/com/eactive/eai/rms/onl/apim/portalterms/PortalTermsTypeConfigService.java
@@ -0,0 +1,219 @@
+package com.eactive.eai.rms.onl.apim.portalterms;
+
+import com.eactive.apim.portal.agreements.entity.AgreementType;
+import com.eactive.apim.portal.portalproperty.entity.PortalProperty;
+import com.eactive.apim.portal.portalproperty.entity.PortalPropertyGroup;
+import com.eactive.apim.portal.portalproperty.entity.PortalPropertyId;
+import com.eactive.apim.portal.portalproperty.repository.PortalPropertyGroupRepository;
+import com.eactive.apim.portal.portalproperty.service.PortalPropertyService;
+import com.eactive.eai.rms.common.base.BaseService;
+import com.eactive.eai.rms.onl.common.exception.BizException;
+import lombok.RequiredArgsConstructor;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.EnumSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * 약관 종류 운영 설정. PTL_PROPERTY(그룹 {@code Portal})로 관리한다.
+ *
+ *
+ * PTL_PROPERTY (group = Portal)
+ * {@code portal.terms.enabled-types}
+ * 시스템에서 사용할 약관 종류. 쉼표 구분, 순서 무의미
+ * {@code portal.terms.display-types}
+ * 약관 페이지에 노출할 약관 종류. 쉼표 구분, 기재 순서가 노출 순서
+ *
+ *
+ * 노출(display) 목록은 항상 사용(enabled) 목록의 부분집합이어야 한다.
+ * 알 수 없는 코드는 읽을 때 무시한다(enum 항목이 제거돼도 화면이 깨지지 않도록).
+ *
+ *
PTL_PROPERTY 는 (PROPERTY_GROUP_NAME, PROPERTY_NAME) 에 유니크 제약이 없어 중복 행이 존재할 수 있다
+ * ({@code PortalPropertyDuplicateChecker} 참고). 따라서 단건 조회({@code findById})는 쓰지 않고
+ * 그룹 단위로 읽고 쓴다. 조회 시에는 값이 없어도 프로퍼티를 만들지 않으며(동시 요청이 각각 행을 만드는 것을 막는다)
+ * 기본값을 메모리에서만 사용하고, 실제 행은 관리 화면에서 저장할 때 생성된다.
+ */
+@Service
+@RequiredArgsConstructor
+@Transactional(transactionManager = "transactionManagerForEMS")
+public class PortalTermsTypeConfigService extends BaseService {
+
+ static final String PROPERTY_GROUP = "Portal";
+ static final String ENABLED_TYPES_KEY = "portal.terms.enabled-types";
+ static final String DISPLAY_TYPES_KEY = "portal.terms.display-types";
+
+ private static final String ENABLED_TYPES_DESC = "시스템에서 사용할 약관 종류(쉼표 구분, AgreementType 코드)";
+ private static final String DISPLAY_TYPES_DESC = "포털 약관 페이지에 노출할 약관 종류(쉼표 구분, 기재 순서가 노출 순서)";
+
+ /** 프로퍼티 미설정 시 기본값 — 현재 운영중인 약관 종류 */
+ private static final List DEFAULT_TYPES = Arrays.asList(
+ AgreementType.TERMS_OF_USE,
+ AgreementType.PRIVACY_COLLECT,
+ AgreementType.NOTIFICATION_CONSENT);
+
+ private final PortalPropertyService portalPropertyService;
+ private final PortalPropertyGroupRepository portalPropertyGroupRepository;
+
+ /** 시스템에서 사용하는 약관 종류 */
+ public Set getEnabledTypes() {
+ Set enabled = EnumSet.noneOf(AgreementType.class);
+ enabled.addAll(readTypes(readGroupProperties(), ENABLED_TYPES_KEY));
+ return enabled;
+ }
+
+ /** 약관 페이지 노출 약관 종류(노출 순서대로) */
+ public List getDisplayTypes() {
+ Map properties = readGroupProperties();
+ List enabled = readTypes(properties, ENABLED_TYPES_KEY);
+ return readTypes(properties, DISPLAY_TYPES_KEY).stream()
+ .filter(enabled::contains)
+ .collect(Collectors.toList());
+ }
+
+ /** 약관 종류 관리 화면 행 목록 (AgreementType 선언 순서) */
+ public List loadTypeConfig() {
+ Map properties = readGroupProperties();
+ List enabled = readTypes(properties, ENABLED_TYPES_KEY);
+ List display = readTypes(properties, DISPLAY_TYPES_KEY).stream()
+ .filter(enabled::contains)
+ .collect(Collectors.toList());
+
+ return Arrays.stream(AgreementType.values())
+ .map(type -> {
+ PortalTermsTypeUI ui = new PortalTermsTypeUI();
+ ui.setCode(type.name());
+ ui.setName(type.getDescription());
+ ui.setEnabled(enabled.contains(type));
+ ui.setDisplayed(display.contains(type));
+ ui.setDisplayOrder(display.indexOf(type) + 1);
+ return ui;
+ })
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * 약관 종류 설정 저장.
+ *
+ * @param enabledCodes 사용할 약관 종류 코드 목록(쉼표 구분)
+ * @param displayCodes 약관 페이지 노출 약관 종류 코드 목록(쉼표 구분, 기재 순서 = 노출 순서)
+ */
+ public void saveTypeConfig(String enabledCodes, String displayCodes) {
+ List enabled = parseStrict(enabledCodes);
+ List display = parseStrict(displayCodes);
+
+ List notEnabled = display.stream()
+ .filter(type -> !enabled.contains(type))
+ .collect(Collectors.toList());
+ if (!notEnabled.isEmpty()) {
+ throw new BizException("사용하지 않는 약관 종류는 약관 페이지에 배치할 수 없습니다. : "
+ + notEnabled.stream().map(AgreementType::getDescription).collect(Collectors.joining(", ")));
+ }
+
+ Map values = new LinkedHashMap<>();
+ values.put(ENABLED_TYPES_KEY, join(enabled));
+ values.put(DISPLAY_TYPES_KEY, join(display));
+ writeGroupProperties(values);
+
+ logger.info("약관 종류 설정 저장 - 사용: [{}], 노출: [{}]", join(enabled), join(display));
+ }
+
+ /** {@code Portal} 그룹 프로퍼티 전체를 읽는다(중복 행이 있어도 예외 없이 마지막 값을 쓴다). */
+ private Map readGroupProperties() {
+ return portalPropertyService.getPortalPropertiesAsMap(PROPERTY_GROUP);
+ }
+
+ /** 프로퍼티 값을 약관 종류 목록으로 변환. 미설정이면 기본값, 알 수 없는 코드는 무시한다. */
+ private List readTypes(Map properties, String propertyName) {
+ String value = properties.get(propertyName);
+ if (value == null) {
+ value = join(DEFAULT_TYPES);
+ }
+
+ List types = new ArrayList<>();
+ for (String code : splitCodes(value)) {
+ try {
+ types.add(AgreementType.valueOf(code));
+ } catch (IllegalArgumentException e) {
+ logger.warn("PTL_PROPERTY {}/{} 에 알 수 없는 약관 종류 코드가 있어 무시합니다. : {}",
+ PROPERTY_GROUP, propertyName, code);
+ }
+ }
+ return new ArrayList<>(new LinkedHashSet<>(types));
+ }
+
+ /**
+ * {@code Portal} 그룹에 프로퍼티를 기록한다.
+ *
+ * 단건 조회는 중복 행에서 터지므로 그룹의 프로퍼티 컬렉션에서 찾아 갱신하고, 없을 때만 새로 만든다.
+ * (중복 행이 이미 있더라도 갱신 UPDATE 는 같은 키의 모든 행에 적용되므로 값이 어긋나지 않는다.)
+ */
+ private void writeGroupProperties(Map values) {
+ PortalPropertyGroup group = portalPropertyGroupRepository.findByIdWithProperties(PROPERTY_GROUP)
+ .orElseThrow(() -> new BizException("PTL_PROPERTY 프로퍼티 그룹이 없습니다. : " + PROPERTY_GROUP));
+
+ values.forEach((propertyName, propertyValue) -> {
+ Optional existing = group.getPortalProperties().stream()
+ .filter(property -> property.getId() != null
+ && propertyName.equals(property.getId().getPropertyName()))
+ .findFirst();
+
+ if (existing.isPresent()) {
+ PortalProperty property = existing.get();
+ property.setPropertyValue(propertyValue);
+ if (StringUtils.isBlank(property.getPropertyDesc())) {
+ property.setPropertyDesc(describe(propertyName));
+ }
+ } else {
+ PortalProperty property = new PortalProperty();
+ property.setId(new PortalPropertyId(PROPERTY_GROUP, propertyName));
+ property.setPropertyValue(propertyValue);
+ property.setPropertyDesc(describe(propertyName));
+ property.setPortalPropertyGroup(group);
+ group.getPortalProperties().add(property);
+ }
+ });
+
+ portalPropertyGroupRepository.save(group);
+ }
+
+ private static String describe(String propertyName) {
+ return DISPLAY_TYPES_KEY.equals(propertyName) ? DISPLAY_TYPES_DESC : ENABLED_TYPES_DESC;
+ }
+
+ /** 화면에서 넘어온 코드 목록 파싱. 알 수 없는 코드는 오류로 처리한다. */
+ private List parseStrict(String codes) {
+ List types = new ArrayList<>();
+ for (String code : splitCodes(codes)) {
+ try {
+ types.add(AgreementType.valueOf(code));
+ } catch (IllegalArgumentException e) {
+ throw new BizException("알 수 없는 약관 종류입니다. : " + code);
+ }
+ }
+ return new ArrayList<>(new LinkedHashSet<>(types));
+ }
+
+ private static List splitCodes(String value) {
+ if (StringUtils.isBlank(value)) {
+ return new ArrayList<>();
+ }
+ return Arrays.stream(value.split(","))
+ .map(String::trim)
+ .filter(StringUtils::isNotBlank)
+ .collect(Collectors.toList());
+ }
+
+ private static String join(List types) {
+ return types.stream().map(AgreementType::name).collect(Collectors.joining(","));
+ }
+}
diff --git a/src/main/java/com/eactive/eai/rms/onl/apim/portalterms/PortalTermsTypeUI.java b/src/main/java/com/eactive/eai/rms/onl/apim/portalterms/PortalTermsTypeUI.java
new file mode 100644
index 0000000..8acf96f
--- /dev/null
+++ b/src/main/java/com/eactive/eai/rms/onl/apim/portalterms/PortalTermsTypeUI.java
@@ -0,0 +1,27 @@
+package com.eactive.eai.rms.onl.apim.portalterms;
+
+import lombok.Data;
+
+/**
+ * 약관 종류 관리 화면(약관 목록 하단)의 한 행.
+ *
+ * @see PortalTermsTypeConfigService
+ */
+@Data
+public class PortalTermsTypeUI {
+
+ /** 약관 종류 코드 (AgreementType 이름) */
+ private String code;
+
+ /** 약관명 (AgreementType description) */
+ private String name;
+
+ /** 시스템에서 사용하는 약관 종류 여부 (토글) */
+ private boolean enabled;
+
+ /** 약관 페이지 노출(배치) 여부 */
+ private boolean displayed;
+
+ /** 약관 페이지 노출 순서. 1부터 시작하며 미배치는 0 */
+ private int displayOrder;
+}