259 lines
12 KiB
Java
259 lines
12 KiB
Java
package com.erp.service;
|
|
|
|
import com.erp.common.BaseService;
|
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
import lombok.RequiredArgsConstructor;
|
|
import lombok.extern.slf4j.Slf4j;
|
|
import org.springframework.stereotype.Service;
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
|
|
import java.util.*;
|
|
|
|
/**
|
|
* Component Standard 서비스
|
|
*
|
|
* Node.js componentStandardService.ts 포팅.
|
|
* component_standards 테이블 CRUD + 카테고리/통계/정렬/복제.
|
|
*/
|
|
@Service
|
|
@RequiredArgsConstructor
|
|
@Slf4j
|
|
public class ComponentStandardService extends BaseService {
|
|
|
|
private static final String NS = "componentStandard.";
|
|
|
|
private final CommonService commonService;
|
|
private final ObjectMapper objectMapper;
|
|
|
|
/** 허용된 정렬 컬럼 (SQL 인젝션 방지) */
|
|
private static final Set<String> VALID_SORT_COLUMNS = Set.of(
|
|
"sort_order", "component_name", "category", "created_date", "updated_date");
|
|
|
|
// ══════════════════════════════════════════════════════════
|
|
// 목록 조회
|
|
// ══════════════════════════════════════════════════════════
|
|
|
|
/**
|
|
* 컴포넌트 목록 조회 (필터, 정렬, 페이지네이션 포함)
|
|
*
|
|
* @return { components, total, limit, offset }
|
|
*/
|
|
public Map<String, Object> getComponents(Map<String, Object> params) {
|
|
// 기본값: active = 'Y'
|
|
if (params.get("active") == null) params.put("active", "Y");
|
|
|
|
// 정렬 컬럼 화이트리스트 검증
|
|
String sort = str(params.getOrDefault("sort", "sort_order"));
|
|
params.put("sortColumn", VALID_SORT_COLUMNS.contains(sort) ? sort : "sort_order");
|
|
params.put("sortOrder", "desc".equalsIgnoreCase(str(params.get("order"))) ? "DESC" : "ASC");
|
|
|
|
// 검색 패턴
|
|
if (params.get("search") != null) {
|
|
params.put("searchPattern", "%" + params.get("search") + "%");
|
|
}
|
|
|
|
// 페이지네이션
|
|
commonService.applyPagination(params);
|
|
|
|
List<Map<String, Object>> components = sqlSession.selectList(NS + "selectComponentList", params);
|
|
Integer totalObj = sqlSession.selectOne(NS + "countComponents", params);
|
|
int total = totalObj != null ? totalObj : 0;
|
|
|
|
Map<String, Object> result = new LinkedHashMap<>();
|
|
result.put("components", components);
|
|
result.put("total", total);
|
|
result.put("limit", params.get("limit"));
|
|
result.put("offset", params.getOrDefault("offset", 0));
|
|
return result;
|
|
}
|
|
|
|
// ══════════════════════════════════════════════════════════
|
|
// 단건 조회
|
|
// ══════════════════════════════════════════════════════════
|
|
|
|
public Map<String, Object> getComponent(String componentCode) {
|
|
Map<String, Object> params = Map.of("component_code", componentCode);
|
|
Map<String, Object> component = sqlSession.selectOne(NS + "selectComponent", params);
|
|
if (component == null) {
|
|
throw new RuntimeException("컴포넌트를 찾을 수 없습니다: " + componentCode);
|
|
}
|
|
return component;
|
|
}
|
|
|
|
// ══════════════════════════════════════════════════════════
|
|
// 생성
|
|
// ══════════════════════════════════════════════════════════
|
|
|
|
@Transactional
|
|
public Map<String, Object> createComponent(Map<String, Object> params) {
|
|
// 중복 코드 확인
|
|
if (sqlSession.selectOne(NS + "checkDuplicate", params) != null) {
|
|
throw new RuntimeException("이미 존재하는 컴포넌트 코드입니다: " + params.get("component_code"));
|
|
}
|
|
|
|
// 'active' → 'is_active' 변환
|
|
if (params.containsKey("active")) {
|
|
params.put("is_active", params.remove("active"));
|
|
}
|
|
|
|
// 기본값 설정
|
|
params.putIfAbsent("is_active", "Y");
|
|
params.putIfAbsent("is_public", "N");
|
|
params.putIfAbsent("sort_order", 0);
|
|
|
|
// JSONB 필드 직렬화
|
|
serializeJsonFields(params);
|
|
|
|
sqlSession.insert(NS + "insertComponent", params);
|
|
return sqlSession.selectOne(NS + "selectComponent", Map.of("component_code", params.get("component_code")));
|
|
}
|
|
|
|
// ══════════════════════════════════════════════════════════
|
|
// 수정
|
|
// ══════════════════════════════════════════════════════════
|
|
|
|
@Transactional
|
|
public Map<String, Object> updateComponent(String componentCode, Map<String, Object> data) {
|
|
// 존재 확인
|
|
getComponent(componentCode);
|
|
|
|
// 'active' → 'is_active' 변환
|
|
if (data.containsKey("active")) {
|
|
data.put("is_active", data.remove("active"));
|
|
}
|
|
|
|
data.put("component_code", componentCode);
|
|
serializeJsonFields(data);
|
|
|
|
sqlSession.update(NS + "updateComponent", data);
|
|
return sqlSession.selectOne(NS + "selectComponent", Map.of("component_code", componentCode));
|
|
}
|
|
|
|
// ══════════════════════════════════════════════════════════
|
|
// 삭제
|
|
// ══════════════════════════════════════════════════════════
|
|
|
|
@Transactional
|
|
public Map<String, Object> deleteComponent(String componentCode) {
|
|
getComponent(componentCode); // 존재 확인
|
|
sqlSession.delete(NS + "deleteComponent", Map.of("component_code", componentCode));
|
|
return Map.of("message", "컴포넌트가 삭제되었습니다: " + componentCode);
|
|
}
|
|
|
|
// ══════════════════════════════════════════════════════════
|
|
// 정렬 순서 업데이트 (배치)
|
|
// ══════════════════════════════════════════════════════════
|
|
|
|
@Transactional
|
|
public Map<String, Object> updateSortOrder(List<Map<String, Object>> updates) {
|
|
for (Map<String, Object> item : updates) {
|
|
sqlSession.update(NS + "updateSortOrder", item);
|
|
}
|
|
return Map.of("message", "정렬 순서가 업데이트되었습니다.");
|
|
}
|
|
|
|
// ══════════════════════════════════════════════════════════
|
|
// 복제
|
|
// ══════════════════════════════════════════════════════════
|
|
|
|
@Transactional
|
|
public Map<String, Object> duplicateComponent(String sourceCode, String newCode, String newName) {
|
|
Map<String, Object> source = getComponent(sourceCode);
|
|
|
|
// 새 코드 중복 확인
|
|
Map<String, Object> dupCheck = new HashMap<>();
|
|
dupCheck.put("component_code", newCode);
|
|
if (sqlSession.selectOne(NS + "checkDuplicate", dupCheck) != null) {
|
|
throw new RuntimeException("이미 존재하는 컴포넌트 코드입니다: " + newCode);
|
|
}
|
|
|
|
Map<String, Object> newComponent = new HashMap<>(source);
|
|
newComponent.put("component_code", newCode);
|
|
newComponent.put("component_name", newName);
|
|
|
|
serializeJsonFields(newComponent);
|
|
sqlSession.insert(NS + "insertComponent", newComponent);
|
|
return sqlSession.selectOne(NS + "selectComponent", Map.of("component_code", newCode));
|
|
}
|
|
|
|
// ══════════════════════════════════════════════════════════
|
|
// 카테고리 목록
|
|
// ══════════════════════════════════════════════════════════
|
|
|
|
public List<String> getCategories(Map<String, Object> params) {
|
|
List<Map<String, Object>> rows = sqlSession.selectList(NS + "selectCategories", params);
|
|
List<String> categories = new ArrayList<>();
|
|
for (Map<String, Object> row : rows) {
|
|
Object cat = row.get("category");
|
|
if (cat != null) categories.add(cat.toString());
|
|
}
|
|
return categories;
|
|
}
|
|
|
|
// ══════════════════════════════════════════════════════════
|
|
// 통계
|
|
// ══════════════════════════════════════════════════════════
|
|
|
|
public Map<String, Object> getStatistics(Map<String, Object> params) {
|
|
Integer totalObj = sqlSession.selectOne(NS + "countStatisticsTotal", params);
|
|
int total = totalObj != null ? totalObj : 0;
|
|
|
|
List<Map<String, Object>> byCategoryRaw = sqlSession.selectList(NS + "selectStatisticsByCategory", params);
|
|
List<Map<String, Object>> byCategory = new ArrayList<>();
|
|
for (Map<String, Object> row : byCategoryRaw) {
|
|
Map<String, Object> item = new LinkedHashMap<>();
|
|
item.put("category", row.get("category"));
|
|
item.put("count", toLong(row.get("count")));
|
|
byCategory.add(item);
|
|
}
|
|
|
|
List<Map<String, Object>> byStatusRaw = sqlSession.selectList(NS + "selectStatisticsByStatus");
|
|
List<Map<String, Object>> byStatus = new ArrayList<>();
|
|
for (Map<String, Object> row : byStatusRaw) {
|
|
Map<String, Object> item = new LinkedHashMap<>();
|
|
item.put("status", row.get("is_active"));
|
|
item.put("count", toLong(row.get("count")));
|
|
byStatus.add(item);
|
|
}
|
|
|
|
Map<String, Object> result = new LinkedHashMap<>();
|
|
result.put("total", total);
|
|
result.put("byCategory", byCategory);
|
|
result.put("byStatus", byStatus);
|
|
return result;
|
|
}
|
|
|
|
// ══════════════════════════════════════════════════════════
|
|
// 중복 코드 체크
|
|
// ══════════════════════════════════════════════════════════
|
|
|
|
public boolean checkDuplicate(Map<String, Object> params) {
|
|
return sqlSession.selectOne(NS + "checkDuplicate", params) != null;
|
|
}
|
|
|
|
// ══ private helpers ═══════════════════════════════════════
|
|
|
|
/** component_config, default_size 값이 Map/List이면 JSON 문자열로 직렬화 */
|
|
private void serializeJsonFields(Map<String, Object> params) {
|
|
for (String field : new String[]{"component_config", "default_size"}) {
|
|
Object val = params.get(field);
|
|
if (val != null && !(val instanceof String)) {
|
|
try {
|
|
params.put(field, objectMapper.writeValueAsString(val));
|
|
} catch (JsonProcessingException e) {
|
|
log.warn("JSON 직렬화 실패 ({}): {}", field, e.getMessage());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private String str(Object o) { return o == null ? "" : o.toString(); }
|
|
|
|
private long toLong(Object o) {
|
|
if (o == null) return 0L;
|
|
if (o instanceof Number n) return n.longValue();
|
|
return Long.parseLong(o.toString());
|
|
}
|
|
}
|