279 lines
13 KiB
Java
279 lines
13 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.*;
|
|
import java.util.regex.Matcher;
|
|
import java.util.regex.Pattern;
|
|
|
|
@Service
|
|
@RequiredArgsConstructor
|
|
@Slf4j
|
|
public class LayoutService extends BaseService {
|
|
|
|
private final CommonService commonService;
|
|
private final ObjectMapper objectMapper;
|
|
|
|
// ─────────────────────────────────────────────────────────
|
|
// 목록 조회
|
|
// ─────────────────────────────────────────────────────────
|
|
|
|
/**
|
|
* 레이아웃 목록 조회 (페이지네이션 + 필터)
|
|
*
|
|
* @return { data: List, total: int, page: int, size: int, totalPages: int }
|
|
*/
|
|
public Map<String, Object> getLayouts(Map<String, Object> params) {
|
|
int page = toInt(params.getOrDefault("page", 1), 1);
|
|
int size = toInt(params.getOrDefault("size", 20), 20);
|
|
|
|
// includePublic: String "true"/"false" → Boolean
|
|
Object ipRaw = params.get("includePublic");
|
|
boolean includePublic = ipRaw == null || "true".equalsIgnoreCase(ipRaw.toString());
|
|
|
|
params.put("page", page);
|
|
params.put("size", size);
|
|
params.put("limit", size);
|
|
params.put("offset", (page - 1) * size);
|
|
params.put("includePublic", includePublic);
|
|
|
|
List<Map<String, Object>> data = sqlSession.selectList("com.erp.mapper.LayoutMapper.selectLayouts", params);
|
|
int total = sqlSession.selectOne("com.erp.mapper.LayoutMapper.countLayouts", params);
|
|
|
|
Map<String, Object> result = new LinkedHashMap<>();
|
|
result.put("data", data);
|
|
result.put("total", total);
|
|
result.put("page", page);
|
|
result.put("size", size);
|
|
result.put("totalPages", (int) Math.ceil((double) total / size));
|
|
return result;
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────
|
|
// 단건 조회
|
|
// ─────────────────────────────────────────────────────────
|
|
|
|
public Map<String, Object> getLayoutById(String layoutCode, String companyCode) {
|
|
Map<String, Object> params = new HashMap<>();
|
|
params.put("layoutCode", layoutCode);
|
|
params.put("companyCode", companyCode);
|
|
return sqlSession.selectOne("com.erp.mapper.LayoutMapper.selectLayoutByCode", params);
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────
|
|
// 생성
|
|
// ─────────────────────────────────────────────────────────
|
|
|
|
@Transactional
|
|
public Map<String, Object> createLayout(Map<String, Object> body,
|
|
String companyCode,
|
|
String userId) {
|
|
String layoutType = (String) body.get("layoutType");
|
|
String layoutCode = generateLayoutCode(layoutType, companyCode);
|
|
|
|
Map<String, Object> params = new HashMap<>();
|
|
params.put("layoutCode", layoutCode);
|
|
params.put("companyCode", companyCode);
|
|
params.put("userId", userId);
|
|
params.put("layoutName", body.get("layoutName"));
|
|
params.put("layoutNameEng", body.get("layoutNameEng"));
|
|
params.put("description", body.get("description"));
|
|
params.put("layoutType", layoutType);
|
|
params.put("category", body.get("category"));
|
|
params.put("iconName", body.get("iconName"));
|
|
params.put("defaultSize", toJsonString(body.get("defaultSize")));
|
|
params.put("layoutConfig", toJsonString(body.get("layoutConfig")));
|
|
params.put("zonesConfig", toJsonString(body.get("zonesConfig")));
|
|
params.put("isPublic", toBooleanYN(body.get("isPublic")));
|
|
|
|
sqlSession.insert("com.erp.mapper.LayoutMapper.insertLayout", params);
|
|
|
|
return getLayoutById(layoutCode, companyCode);
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────
|
|
// 수정
|
|
// ─────────────────────────────────────────────────────────
|
|
|
|
@Transactional
|
|
public Map<String, Object> updateLayout(String layoutCode,
|
|
Map<String, Object> body,
|
|
String companyCode,
|
|
String userId) {
|
|
// 소유권 확인 (is_public 무관, 자사 레이아웃만 수정 가능)
|
|
Map<String, Object> checkParams = new HashMap<>();
|
|
checkParams.put("layoutCode", layoutCode);
|
|
checkParams.put("companyCode", companyCode);
|
|
if (sqlSession.selectOne("com.erp.mapper.LayoutMapper.selectLayoutByCodeAndCompany", checkParams) == null) {
|
|
return null; // Controller 에서 404 처리
|
|
}
|
|
|
|
Map<String, Object> params = new HashMap<>();
|
|
params.put("layoutCode", layoutCode);
|
|
params.put("companyCode", companyCode);
|
|
params.put("userId", userId);
|
|
|
|
// 존재하는 필드만 params 에 추가
|
|
copyIfPresent(body, params, "layoutName");
|
|
copyIfPresent(body, params, "layoutNameEng");
|
|
copyIfPresent(body, params, "description");
|
|
copyIfPresent(body, params, "layoutType");
|
|
copyIfPresent(body, params, "category");
|
|
copyIfPresent(body, params, "iconName");
|
|
|
|
// JSON 필드는 직렬화 후 추가
|
|
if (body.containsKey("defaultSize")) params.put("defaultSize", toJsonString(body.get("defaultSize")));
|
|
if (body.containsKey("layoutConfig")) params.put("layoutConfig", toJsonString(body.get("layoutConfig")));
|
|
if (body.containsKey("zonesConfig")) params.put("zonesConfig", toJsonString(body.get("zonesConfig")));
|
|
|
|
// boolean → Y/N
|
|
if (body.containsKey("isPublic")) params.put("isPublic", toBooleanYN(body.get("isPublic")));
|
|
|
|
sqlSession.update("com.erp.mapper.LayoutMapper.updateLayout", params);
|
|
return getLayoutById(layoutCode, companyCode);
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────
|
|
// 삭제 (소프트)
|
|
// ─────────────────────────────────────────────────────────
|
|
|
|
@Transactional
|
|
public void deleteLayout(String layoutCode, String companyCode, String userId) {
|
|
Map<String, Object> checkParams = new HashMap<>();
|
|
checkParams.put("layoutCode", layoutCode);
|
|
checkParams.put("companyCode", companyCode);
|
|
if (sqlSession.selectOne("com.erp.mapper.LayoutMapper.selectLayoutByCodeAndCompany", checkParams) == null) {
|
|
throw new IllegalArgumentException("레이아웃을 찾을 수 없거나 삭제 권한이 없습니다.");
|
|
}
|
|
|
|
Map<String, Object> params = new HashMap<>();
|
|
params.put("layoutCode", layoutCode);
|
|
params.put("companyCode", companyCode);
|
|
params.put("userId", userId);
|
|
sqlSession.update("com.erp.mapper.LayoutMapper.softDeleteLayout", params);
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────
|
|
// 복제
|
|
// ─────────────────────────────────────────────────────────
|
|
|
|
@Transactional
|
|
public Map<String, Object> duplicateLayout(String layoutCode,
|
|
String newName,
|
|
String companyCode,
|
|
String userId) {
|
|
Map<String, Object> original = getLayoutById(layoutCode, companyCode);
|
|
if (original == null) {
|
|
throw new IllegalArgumentException("복제할 레이아웃을 찾을 수 없습니다.");
|
|
}
|
|
|
|
// DB 결과는 snake_case → createLayout 이 요구하는 camelCase 로 변환
|
|
Map<String, Object> body = new HashMap<>();
|
|
body.put("layoutName", newName);
|
|
body.put("layoutType", original.get("layout_type"));
|
|
body.put("category", original.get("category"));
|
|
body.put("description", original.get("description"));
|
|
body.put("iconName", original.get("icon_name"));
|
|
body.put("defaultSize", original.get("default_size"));
|
|
body.put("layoutConfig", original.get("layout_config"));
|
|
body.put("zonesConfig", original.get("zones_config"));
|
|
body.put("isPublic", "N");
|
|
|
|
// layoutNameEng 에 "Copy" 접미사
|
|
Object engName = original.get("layout_name_eng");
|
|
if (engName != null) {
|
|
body.put("layoutNameEng", engName + " Copy");
|
|
}
|
|
|
|
return createLayout(body, companyCode, userId);
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────
|
|
// 카테고리별 개수
|
|
// ─────────────────────────────────────────────────────────
|
|
|
|
public Map<String, Object> getLayoutCountsByCategory(String companyCode) {
|
|
Map<String, Object> params = new HashMap<>();
|
|
params.put("companyCode", companyCode);
|
|
|
|
List<Map<String, Object>> rows = sqlSession.selectList(
|
|
"com.erp.mapper.LayoutMapper.selectCountsByCategory", params);
|
|
|
|
Map<String, Object> result = new LinkedHashMap<>();
|
|
for (Map<String, Object> row : rows) {
|
|
String category = String.valueOf(row.get("category"));
|
|
Object count = row.get("count");
|
|
result.put(category, count instanceof Number
|
|
? ((Number) count).intValue()
|
|
: Integer.parseInt(String.valueOf(count)));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────
|
|
// private helpers
|
|
// ─────────────────────────────────────────────────────────
|
|
|
|
/**
|
|
* 레이아웃 코드 자동 생성: {LAYOUT_TYPE}_{COMPANY_CODE}_{NNN}
|
|
* Node.js generateLayoutCode 동일 로직
|
|
*/
|
|
private String generateLayoutCode(String layoutType, String companyCode) {
|
|
String prefix = layoutType.toUpperCase() + "_" + companyCode;
|
|
|
|
Map<String, Object> params = new HashMap<>();
|
|
params.put("prefix", prefix + "%");
|
|
List<Map<String, Object>> existing = sqlSession.selectList(
|
|
"com.erp.mapper.LayoutMapper.selectCodesLike", params);
|
|
|
|
Pattern numPattern = Pattern.compile("_(\\d+)$");
|
|
int maxNumber = existing.stream()
|
|
.map(row -> String.valueOf(row.get("layout_code")))
|
|
.mapToInt(code -> {
|
|
Matcher m = numPattern.matcher(code);
|
|
return m.find() ? Integer.parseInt(m.group(1)) : 0;
|
|
})
|
|
.max()
|
|
.orElse(0);
|
|
|
|
return String.format("%s_%03d", prefix, maxNumber + 1);
|
|
}
|
|
|
|
/** Object → JSON 문자열 (null 허용) */
|
|
private String toJsonString(Object obj) {
|
|
if (obj == null) return null;
|
|
if (obj instanceof String s) return s;
|
|
try {
|
|
return objectMapper.writeValueAsString(obj);
|
|
} catch (JsonProcessingException e) {
|
|
log.warn("JSON 직렬화 실패: {}", e.getMessage());
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/** Boolean/String → "Y"/"N" */
|
|
private String toBooleanYN(Object val) {
|
|
if (val == null) return "N";
|
|
if (val instanceof Boolean b) return b ? "Y" : "N";
|
|
return "true".equalsIgnoreCase(val.toString()) ? "Y" : "N";
|
|
}
|
|
|
|
/** params 에 key 가 존재하면 dest 에 복사 */
|
|
private void copyIfPresent(Map<String, Object> src, Map<String, Object> dest, String key) {
|
|
if (src.containsKey(key)) dest.put(key, src.get(key));
|
|
}
|
|
|
|
/** Object → int (기본값 포함) */
|
|
private int toInt(Object val, int defaultVal) {
|
|
if (val == null) return defaultVal;
|
|
try { return Integer.parseInt(val.toString()); }
|
|
catch (NumberFormatException e) { return defaultVal; }
|
|
}
|
|
}
|