Files
invyone/backend-spring/src/main/java/com/erp/service/LayoutService.java
T

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; }
}
}