package com.erp.service; import com.erp.common.BaseService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.*; @Service @RequiredArgsConstructor @Slf4j public class CascadingMutualExclusionService extends BaseService { private static final String NS = "cascadingMutualExclusion."; private final CommonService commonService; private final JdbcTemplate jdbcTemplate; public Map getCascadingMutualExclusionList(Map params) { commonService.applyCompanyCodeFilter(params); commonService.applyPagination(params); int totalCount = sqlSession.selectOne(NS + "getCascadingMutualExclusionListCnt", params); List> list = sqlSession.selectList(NS + "getCascadingMutualExclusionList", params); return commonService.buildListResponse(list, totalCount, params); } public Map getCascadingMutualExclusionInfo(Map params) { commonService.applyCompanyCodeFilter(params); return sqlSession.selectOne(NS + "getCascadingMutualExclusionInfo", params); } @Transactional public Map insertCascadingMutualExclusion(Map params) { commonService.applyCompanyCodeFilter(params); if (params.get("exclusion_type") == null) params.put("exclusion_type", "SAME_VALUE"); if (params.get("error_message") == null) params.put("error_message", "동일한 값을 선택할 수 없습니다"); // 배제 코드 자동 생성: EX_XXXX_NNN String companyCode = (String) params.get("company_code"); Map countParams = new LinkedHashMap<>(); countParams.put("company_code", companyCode); int count = sqlSession.selectOne(NS + "getCascadingMutualExclusionCount", countParams); String ts = Long.toString(System.currentTimeMillis(), 36).toUpperCase(); ts = ts.substring(Math.max(0, ts.length() - 4)); params.put("exclusion_code", String.format("EX_%s_%03d", ts, count + 1)); sqlSession.insert(NS + "insertCascadingMutualExclusion", params); return params; } @Transactional public Map updateCascadingMutualExclusion(Map params) { commonService.applyCompanyCodeFilter(params); sqlSession.update(NS + "updateCascadingMutualExclusion", params); return params; } @Transactional public Map deleteCascadingMutualExclusion(Map params) { commonService.applyCompanyCodeFilter(params); sqlSession.delete(NS + "deleteCascadingMutualExclusion", params); return params; } /** * 상호 배제 검증: 선택한 값들 간 충돌 여부 확인 (SAME_VALUE 타입) */ public Map validateCascadingMutualExclusion(Map params) { commonService.applyCompanyCodeFilter(params); Map exclusion = sqlSession.selectOne(NS + "getCascadingMutualExclusionByCode", params); if (exclusion == null) throw new NoSuchElementException("상호 배제 규칙을 찾을 수 없습니다."); @SuppressWarnings("unchecked") Map fieldValues = (Map) params.getOrDefault("field_values", Collections.emptyMap()); String fieldNamesStr = (String) exclusion.get("field_names"); String[] fields = fieldNamesStr != null ? fieldNamesStr.split(",") : new String[0]; List values = new ArrayList<>(); for (String field : fields) { Object v = fieldValues.get(field.trim()); if (v != null) values.add(v.toString()); } boolean isValid = true; String errorMessage = null; List conflictingFields = new ArrayList<>(); String exclusionType = (String) exclusion.getOrDefault("exclusion_type", "SAME_VALUE"); if ("SAME_VALUE".equals(exclusionType)) { Set seen = new LinkedHashSet<>(); boolean hasDuplicate = false; for (String v : values) { if (!seen.add(v)) { hasDuplicate = true; break; } } if (hasDuplicate) { isValid = false; errorMessage = (String) exclusion.get("error_message"); Map> valueCounts = new LinkedHashMap<>(); for (String field : fields) { Object v = fieldValues.get(field.trim()); if (v != null) { valueCounts.computeIfAbsent(v.toString(), k -> new ArrayList<>()).add(field.trim()); } } for (List fl : valueCounts.values()) { if (fl.size() > 1) { conflictingFields = fl; break; } } } } Map result = new LinkedHashMap<>(); result.put("is_valid", isValid); result.put("error_message", isValid ? null : errorMessage); result.put("conflicting_fields", conflictingFields); return result; } /** * 배제 옵션 조회: source_table에서 이미 선택된 값을 제외한 목록 반환 */ public List> getExcludedOptions(Map params) { commonService.applyCompanyCodeFilter(params); String companyCode = (String) params.get("company_code"); Map exclusion = sqlSession.selectOne(NS + "getCascadingMutualExclusionByCode", params); if (exclusion == null) throw new NoSuchElementException("상호 배제 규칙을 찾을 수 없습니다."); String sourceTable = (String) exclusion.get("source_table"); String valueColumn = (String) exclusion.get("value_column"); String labelColumn = (String) exclusion.get("label_column"); if (labelColumn == null || labelColumn.isEmpty()) labelColumn = valueColumn; boolean hasCompanyCode = hasColumn(sourceTable, "company_code"); List queryParams = new ArrayList<>(); StringBuilder sql = new StringBuilder(); sql.append("SELECT ") .append(valueColumn).append(" AS value, ") .append(labelColumn).append(" AS label") .append(" FROM ").append(sourceTable) .append(" WHERE 1=1"); if (hasCompanyCode && !"*".equals(companyCode)) { sql.append(" AND company_code = ?"); queryParams.add(companyCode); } Object selectedValuesParam = params.get("selected_values"); if (selectedValuesParam != null) { List excludeValues = new ArrayList<>(); for (String v : selectedValuesParam.toString().split(",")) { String trimmed = v.trim(); if (!trimmed.isEmpty()) excludeValues.add(trimmed); } if (!excludeValues.isEmpty()) { String placeholders = String.join(", ", Collections.nCopies(excludeValues.size(), "?")); sql.append(" AND ").append(valueColumn).append(" NOT IN (").append(placeholders).append(")"); queryParams.addAll(excludeValues); } } sql.append(" ORDER BY ").append(labelColumn); return jdbcTemplate.queryForList(sql.toString(), queryParams.toArray()); } private boolean hasColumn(String tableName, String columnName) { String sql = "SELECT COUNT(*) FROM information_schema.columns WHERE table_name = ? AND column_name = ?"; Integer count = jdbcTemplate.queryForObject(sql, Integer.class, tableName, columnName); return count != null && count > 0; } }