diff --git a/src/main/java/cokr/xit/fims/cmmn/CmmnUtil.java b/src/main/java/cokr/xit/fims/cmmn/CmmnUtil.java index 309aca64..d4a5f28e 100644 --- a/src/main/java/cokr/xit/fims/cmmn/CmmnUtil.java +++ b/src/main/java/cokr/xit/fims/cmmn/CmmnUtil.java @@ -5,7 +5,10 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.text.SimpleDateFormat; +import java.util.Calendar; import java.util.Comparator; +import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -171,4 +174,48 @@ public class CmmnUtil { } } + + + /** + * 현재 날짜와 지정한 날짜와의 차이(일)를 반환한다. + * @param dayStr 지정한 날짜 + * @return 현재 날짜와의 차이 일수 + */ + public static int compareToday(String dayStr) { + + SimpleDateFormat yyyyMMddFormat = new SimpleDateFormat("yyyyMMdd"); + Date curDate = new Date(); + String strCurrentDate = yyyyMMddFormat.format(curDate); + + if(dayStr.length() == 8) { + return Integer.parseInt(strCurrentDate) - Integer.parseInt(dayStr); + } else if(dayStr.length() == 14) { + return Integer.parseInt(strCurrentDate) - Integer.parseInt(dayStr.substring(0,8)); + } else { + throw new RuntimeException("날짜 형식 오류"); + } + } + + /** + * 지정한 날짜에 일수를 더한 날짜를 반환한다. + * @param yyyyMMdd 지정한 날짜, add 추가 일수 + * @return 계산된 날짜 + */ + public static String addDay(String yyyyMMdd, int add) { + + SimpleDateFormat yyyyMMddFormat = new SimpleDateFormat("yyyyMMdd"); + + Calendar cal = Calendar.getInstance(); + + try { + Date dt = yyyyMMddFormat.parse(yyyyMMdd); + cal.setTime(dt); + } catch (Exception e){ + throw new RuntimeException(e); + } + + cal.add(Calendar.DATE, add); + + return yyyyMMddFormat.format(cal.getTime()); + } } diff --git a/src/main/java/cokr/xit/fims/crdn/parsing/CodeConverter.java b/src/main/java/cokr/xit/fims/crdn/parsing/CodeConverter.java index b690777d..e9d49da8 100644 --- a/src/main/java/cokr/xit/fims/crdn/parsing/CodeConverter.java +++ b/src/main/java/cokr/xit/fims/crdn/parsing/CodeConverter.java @@ -13,11 +13,11 @@ public class CodeConverter { Map> commonCodes = new HashMap>(); - CodeConverter(Map> commonCodes){ + public CodeConverter(Map> commonCodes){ this.commonCodes = commonCodes; } - String valueToCode(String codeGroupName, String value){ + public String valueToCode(String codeGroupName, String value){ String result = ""; List commonCodeList = commonCodes.get(codeGroupName); @@ -30,7 +30,7 @@ public class CodeConverter { return result; } - String codeToValue(String codeGroupName, String code){ + public String codeToValue(String codeGroupName, String code){ String result = ""; List commonCodeList = commonCodes.get(codeGroupName); diff --git a/src/main/java/cokr/xit/fims/stat/Stat.java b/src/main/java/cokr/xit/fims/stat/Stat.java index de00889f..3538bda5 100644 --- a/src/main/java/cokr/xit/fims/stat/Stat.java +++ b/src/main/java/cokr/xit/fims/stat/Stat.java @@ -24,27 +24,27 @@ public class Stat { private String statSubTitle; /** - * 복합 항목 명 제목 + * 집계 여부 */ - private String[] compositeItemNameTitle; + private String aggregateYn; /** - * 복합 값 제목 + * 항목 제목 */ - private String[] compositeValueTitle; + private String[] itemsLabel; /** - * 집계 여부 + * 수치 값 제목 */ - private String groupYn; + private String[] numberValueLabel; /** - * (집계시)통계 항목 목록 + * (집계일 때)통계 항목 */ private List statItems; /** - * 결과 목록(현황) + * (목록일 때)결과 목록(현황) */ private List resultList; diff --git a/src/main/java/cokr/xit/fims/stat/StatItem.java b/src/main/java/cokr/xit/fims/stat/StatItem.java index 7348faf4..c076ba40 100644 --- a/src/main/java/cokr/xit/fims/stat/StatItem.java +++ b/src/main/java/cokr/xit/fims/stat/StatItem.java @@ -10,28 +10,34 @@ import lombok.Setter; @Setter public class StatItem { + /** + * 항목 ID + */ + private String itemId; + /** * 항목 명 */ - private String itemName;; + private String itemName; /** * 복합 항목 명 */ - private String[] compositeItemName; + private String[] compositeItemId; /** - * 값 + * 복합 항목 명 */ - private String value; + private String[] compositeItemName; /** - * 복합 값 + * 수치 값 */ - private String[] compositeValue; + private int[] numberValue; /** * 참조 목록 */ private List refList; + } diff --git a/src/main/java/cokr/xit/fims/stat/StatQuery.java b/src/main/java/cokr/xit/fims/stat/StatQuery.java index 04e70ca9..2cf1c327 100644 --- a/src/main/java/cokr/xit/fims/stat/StatQuery.java +++ b/src/main/java/cokr/xit/fims/stat/StatQuery.java @@ -16,5 +16,143 @@ public class StatQuery extends CmmnQuery { private static final long serialVersionUID = 1L; + /** + * 구조 형식(aggregate 집계, list 목록) + */ + private String structureType; + /** + * 항목 분류 방법(컬럼명) + */ + private String[] categorization; + + /** + * 항목명 변환(코드그룹ID) + */ + private String[] categoryNameConverter; + + /** + * 고정 항목 ID (항목이 단일키일 경우) + */ + private String[] fixedItemId; + + /** + * 항목 병합 방법 (항목이 단일키일 경우) + */ + private String[] regroupInfos; + + /** + * 명명된 복합 수치값 구분자 + */ + private String namedNumberValueSeperator; + + /** + * 복합 수치 값 구분자 + */ + private String[] compositeNumberValueSeperator; + + /** + * 수치값 집계 유형(count,sum) + */ + private String[] aggregateType; + + /** + * 날짜 컬럼 + */ + private String dayColumn; + + /** + * 날짜 범위 + */ + private int dayRange; + + public String getStructureType() { + return ifEmpty(structureType, () -> null); + } + + public T setStructureType(String structureType) { + this.structureType = structureType; + return self(); + } + + public String getNamedNumberValueSeperator() { + return ifEmpty(namedNumberValueSeperator, () -> null); + } + + public T setNamedNumberValueSeperator(String namedNumberValueSeperator) { + this.namedNumberValueSeperator = namedNumberValueSeperator; + return self(); + } + + public String[] getCategorization() { + return ifEmpty(categorization, () -> null); + } + + public T setCategorization(String... categorization) { + this.categorization = categorization; + return self(); + } + + public String[] getCategoryNameConverter() { + return ifEmpty(categoryNameConverter, () -> null); + } + + public T setCategoryNameConverter(String... categoryNameConverter) { + this.categoryNameConverter = categoryNameConverter; + return self(); + } + + public String[] getRegroupInfos() { + return ifEmpty(regroupInfos, () -> null); + } + + public T setRegroupInfos(String... regroupInfos) { + this.regroupInfos = regroupInfos; + return self(); + } + + public String[] getAggregateType() { + return ifEmpty(aggregateType, () -> null); + } + + public T setAggregateType(String... aggregateType) { + this.aggregateType = aggregateType; + return self(); + } + + public String[] getCompositeNumberValueSeperator() { + return ifEmpty(compositeNumberValueSeperator, () -> null); + } + + public T setCompositeNumberValueSeperator(String... compositeNumberValueSeperator) { + this.compositeNumberValueSeperator = compositeNumberValueSeperator; + return self(); + } + + public String[] getFixedItemId() { + return ifEmpty(fixedItemId, () -> null); + } + + public T setFixedItemId(String... fixedItemId) { + this.fixedItemId = fixedItemId; + return self(); + } + + public int getDayRange() { + return ifEmpty(dayRange, () -> null); + } + + public T setDayRange(int dayRange) { + this.dayRange = dayRange; + return self(); + } + + public String getDayColumn() { + return ifEmpty(dayColumn, () -> null); + } + + public T setDayColumn(String dayColumn) { + this.dayColumn = dayColumn; + return self(); + } } diff --git a/src/main/java/cokr/xit/fims/stat/service/StatService.java b/src/main/java/cokr/xit/fims/stat/service/StatService.java index 373f2e7b..d7eed07e 100644 --- a/src/main/java/cokr/xit/fims/stat/service/StatService.java +++ b/src/main/java/cokr/xit/fims/stat/service/StatService.java @@ -1,10 +1,14 @@ package cokr.xit.fims.stat.service; +import java.util.List; +import java.util.Map; + +import cokr.xit.base.code.CommonCode; import cokr.xit.fims.stat.Stat; import cokr.xit.fims.stat.StatQuery; public interface StatService { - Stat getStatistics(StatQuery query); + Stat getStatistics(StatQuery statQuery, Map> commonCodes); } diff --git a/src/main/java/cokr/xit/fims/stat/service/bean/StatBean.java b/src/main/java/cokr/xit/fims/stat/service/bean/StatBean.java new file mode 100644 index 00000000..fd5b4763 --- /dev/null +++ b/src/main/java/cokr/xit/fims/stat/service/bean/StatBean.java @@ -0,0 +1,320 @@ +package cokr.xit.fims.stat.service.bean; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import org.springframework.stereotype.Component; + +import cokr.xit.base.code.CommonCode; +import cokr.xit.fims.cmmn.CmmnQuery; +import cokr.xit.fims.crdn.parsing.CodeConverter; +import cokr.xit.fims.stat.Stat; +import cokr.xit.fims.stat.StatItem; +import cokr.xit.fims.stat.StatQuery; +import cokr.xit.foundation.AbstractComponent; +import cokr.xit.foundation.data.DataObject; + + +@Component("statBean") +public class StatBean extends AbstractComponent { + + /**통계요청에 따라 데이터목록에서 통계를 생성한다. + * @param queryResult SQL쿼리 처리 결과, statQuery 통계 요청 + * @return 통계 + */ + public Stat groupByCartegory(List queryResult, StatQuery statQuery) { + + Stat stat = new Stat(); + + //통계 요청 설정 + int compositeSize = 0; + + String[] compositeNumberValueSeperator = statQuery.getCompositeNumberValueSeperator(); + String[] aggregateType = statQuery.getAggregateType(); + + if(compositeNumberValueSeperator == null && aggregateType == null) { + compositeSize = 1; + compositeNumberValueSeperator = new String[] {""}; + aggregateType = new String[] {"count"}; + } else if(compositeNumberValueSeperator != null && aggregateType == null) { + compositeSize = compositeNumberValueSeperator.length; + aggregateType = new String[compositeSize]; + } else if(compositeNumberValueSeperator == null && aggregateType != null) { + compositeSize = aggregateType.length; + compositeNumberValueSeperator = new String[compositeSize]; + } else { + if(compositeNumberValueSeperator.length > aggregateType.length) { + compositeSize = compositeNumberValueSeperator.length; + } else { + compositeSize = aggregateType.length; + } + } + + for(int i=0; i < compositeSize; i++) { + if(compositeNumberValueSeperator[i] == null) { + compositeNumberValueSeperator[i] = ""; + } + if(aggregateType[i] == null || aggregateType[i].equals("")) { + aggregateType[i] = "count"; + } + } + + statQuery.setAggregateType(aggregateType); + statQuery.setCompositeNumberValueSeperator(compositeNumberValueSeperator); + + //통계 항목별 그룹핑 + Map> group; + + if(statQuery.getCategorization().length < 2) { + group = queryResult.stream() + .collect(Collectors.groupingBy(item -> item.string(statQuery.getCategorization()[0]))); + + } else { + String[] categorizations = statQuery.getCategorization(); + + group = queryResult.stream() + .collect( + Collectors.groupingBy( + (item) -> { + String result = ""; + for(int i = 0; i < categorizations.length; i++) { + if(i != 0) { + result += ","; + } + result += item.string(categorizations[i]); + } + return result; + } + ) + ); + } + + //고정 항목 설정 + if(statQuery.getFixedItemId() != null) { + this.itemFix(group, statQuery.getFixedItemId()); + } + + //수치 값 추출 + Set keySet = group.keySet(); + Iterator it = keySet.iterator(); + + List statItems = new ArrayList<>(); + while(it.hasNext()){ + String key = it.next(); + StatItem statItem = new StatItem(); + + if(statQuery.getCategorization().length < 2) { + statItem.setItemId(key); + } else { + statItem.setCompositeItemId(key.split(",")); + } + + List listByKey = group.get(key); + + int[] numberValues = this.extractNumberValue(listByKey, statQuery); + + statItem.setNumberValue(numberValues); + + statItems.add(statItem); + } + + stat.setStatItems(statItems); + + return stat; + } + + /** 통계에 고정적으로 표시할 항목을 설정한다. + * @param group 그룹핑한 데이터 목록, fixedItemId 고정적으로 표시할 통계 항목 ID; + * @return + */ + public void itemFix(Map> group, String[] fixedItemId) { + + for(int i=0; i < fixedItemId.length; i++) { + if(!group.containsKey(fixedItemId[i])) { + group.put(fixedItemId[i], null); + } + } + + Set keySet = group.keySet(); + Iterator it = keySet.iterator(); + + List deleteTargets = new ArrayList<>(); + + while(it.hasNext()) { + String key = it.next(); + if(!Arrays.asList(fixedItemId).contains(key)) { + deleteTargets.add(key); + } + } + + for(String deleteTarget : deleteTargets) { + group.remove(deleteTarget); + } + } + + /**통계요청에 따라 수치값(합계, 건수)을 계산한다. + * @param listByKey 특정 범주로 묶인 데이터 목록, statQuery 통계 요청 + * @return 수치값 + */ + public int[] extractNumberValue(List listByKey, StatQuery statQuery) { + + String[] compositeNumberValueSeperator = statQuery.getCompositeNumberValueSeperator(); + String[] aggregateType = statQuery.getAggregateType(); + + int[] aggregateArr = new int[compositeNumberValueSeperator.length]; + for(int i=0; i < compositeNumberValueSeperator.length; i++) { + aggregateArr[i] = 0; + } + + if(listByKey != null) { + for(int i=0; i statItems, String[] categoryNameConverter, Map> allCode) { + + CodeConverter codeConverter = new CodeConverter(allCode); + + if(categoryNameConverter.length > 1) { + String[] codeGroups = categoryNameConverter; + for(StatItem statItem : statItems) { + + String[] compositeItemId = statItem.getCompositeItemId(); + String[] compositeItemName = new String[compositeItemId.length]; + for(int i=0; i < compositeItemId.length ;i++) { + if(codeGroups[i].equals("")) { + compositeItemName[i] = compositeItemId[i]; + } else { + String itemName = codeConverter.codeToValue(codeGroups[i], compositeItemId[i]); + compositeItemName[i] = itemName; + } + } + statItem.setCompositeItemName(compositeItemName); + } + } else { + for(StatItem statItem : statItems) { + String itemId = statItem.getItemId(); + String itemName = codeConverter.codeToValue(categoryNameConverter[0] , itemId); + statItem.setItemName(itemName); + } + } + + } + + /** 통계의 일부 항목을 합쳐 새 범주로 묶는다. + * @param statItems 통계 항목 목록, regroupInfos 재그룹화 정보 + * @return + */ + public void regroupItem(List statItems, String[] regroupInfos) { + + for(int i=0; i < regroupInfos.length; i++) { + + String[] regroupInfo = regroupInfos[i].split("="); + String[] sourceCodes = regroupInfo[0].split(","); + String targetCodeName = regroupInfo[1]; + + StatItem newItem = new StatItem(); + newItem.setItemId("regroup"+i); + newItem.setItemName(targetCodeName); + + int[] numberValues = null; + + for(StatItem statItem : statItems) { + if(Arrays.asList(sourceCodes).contains(statItem.getItemId())) { + if(numberValues == null) { + numberValues = statItem.getNumberValue(); + } else { + for(int j=0; j < numberValues.length; j++) { + numberValues[j] += statItem.getNumberValue()[j]; + } + } + } + } + + newItem.setNumberValue(numberValues); + + statItems.removeIf(item -> Arrays.asList(sourceCodes).contains(item.getItemId())); + + statItems.add(newItem); + + } + } + + /**쿼리에서 날짜와 관련된 파라미터를 설정한다. + * @param sql처리용 query, dayCol 날짜조회용 컬럼, from 조회시작일, to 조회종료일 + * @return + */ + public void daySetting(CmmnQuery someQuery, String dayCol, String from, String to) { + try { + if(dayCol.equals("REG_DT")) { + + Method m0 = CmmnQuery.class.getDeclaredMethod("setSchDateOpt", String.class); + m0.invoke(someQuery, "regDt"); + Method m1 = CmmnQuery.class.getDeclaredMethod("setSchDateFrom", String.class); + m1.invoke(someQuery, from); + Method m2 = CmmnQuery.class.getDeclaredMethod("setSchDateTo", String.class); + m2.invoke(someQuery, to); + } else { + + if(dayCol.equals("CRDN_YMD")) { + Method m1 = someQuery.getClass().getDeclaredMethod("setSchCrdnYmdFrom", String.class); + m1.invoke(someQuery, from); + Method m2 = someQuery.getClass().getDeclaredMethod("setSchCrdnYmdTo", String.class); + m2.invoke(someQuery, to); + } + + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } + +} diff --git a/src/main/java/cokr/xit/fims/stat/service/bean/StatServiceBean.java b/src/main/java/cokr/xit/fims/stat/service/bean/StatServiceBean.java index 35e4d805..b5e2ad9f 100644 --- a/src/main/java/cokr/xit/fims/stat/service/bean/StatServiceBean.java +++ b/src/main/java/cokr/xit/fims/stat/service/bean/StatServiceBean.java @@ -1,16 +1,28 @@ package cokr.xit.fims.stat.service.bean; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Map; + import javax.annotation.Resource; import org.springframework.stereotype.Service; +import cokr.xit.base.code.CommonCode; +import cokr.xit.fims.cmmn.CmmnUtil; +import cokr.xit.fims.crdn.CrdnQuery; import cokr.xit.fims.crdn.service.CrdnService; +import cokr.xit.fims.excl.LevyExclQuery; import cokr.xit.fims.excl.service.OpnnSbmsnService; +import cokr.xit.fims.sndb.SndbQuery; import cokr.xit.fims.sndb.service.SndngService; import cokr.xit.fims.stat.Stat; import cokr.xit.fims.stat.StatQuery; import cokr.xit.fims.stat.service.StatService; import cokr.xit.foundation.component.AbstractServiceBean; +import cokr.xit.foundation.data.DataObject; @Service("statService") public class StatServiceBean extends AbstractServiceBean implements StatService { @@ -24,13 +36,175 @@ public class StatServiceBean extends AbstractServiceBean implements StatService @Resource(name = "sndngService") private SndngService sndngService; + @Resource(name = "statBean") + private StatBean statBean; + @Override - public Stat getStatistics(StatQuery query) { + public Stat getStatistics(StatQuery statQuery, Map> commonCodes) { Stat stat = new Stat(); + List queryResult = new ArrayList<>(); + + SimpleDateFormat yyyyMMdd = new SimpleDateFormat("yyyyMMdd"); + Date curDate = new Date(); + String today = yyyyMMdd.format(curDate); + + boolean daySetting = false; + + + if(statQuery.getStructureType().equals("aggregate")) { + + + String[] numberValueLabel = null; + + int dayRange = statQuery.getDayRange() == 0 ? 15 : statQuery.getDayRange(); + String firstDay = CmmnUtil.addDay(today, (-1 * (dayRange-1))); + String dayColumn = statQuery.getDayColumn() == null ? "REG_DT" : statQuery.getDayColumn(); + + if(statQuery.getNamedNumberValueSeperator().equals("completeAndTotal")) { //완료자료 및 전체자료 + + String[] compositeNumberValueSeperator = new String[] {"COMPLETE_YN=Y", ""}; + statQuery.setCompositeNumberValueSeperator(compositeNumberValueSeperator); + + if(statQuery.getAggregateType() == null) { + String[] aggregateType = new String[] {"count", "count"}; + statQuery.setAggregateType(aggregateType); + } + + daySetting = true; + + numberValueLabel = new String[] {"처리건수","전체건수"}; + + } else if(statQuery.getNamedNumberValueSeperator().equals("lastFewDays")) { + + String[] compositeNumberValueSeperator = new String[dayRange]; + for(int i=0; i"").equals("")) { + defaultType = "count"; + } + + for(int i=0; i> commonCodes = getCodesOf(categoryNameConverter); + + Stat stat = statService.getStatistics(statQuery, commonCodes); mav.addObject("stat", stat); return mav; diff --git a/src/main/java/cokr/xit/fims/task/web/CmnController.java b/src/main/java/cokr/xit/fims/task/web/CmnController.java index f685eaae..70dcd114 100644 --- a/src/main/java/cokr/xit/fims/task/web/CmnController.java +++ b/src/main/java/cokr/xit/fims/task/web/CmnController.java @@ -7,6 +7,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.servlet.ModelAndView; import cokr.xit.fims.sprt.SprtQuery; +import cokr.xit.fims.stat.StatQuery; @Controller public class CmnController { @@ -364,6 +365,18 @@ public class CmnController { } + @Controller + @RequestMapping(name="통계", value="/stat") + class StatController extends cokr.xit.fims.stat.web.StatController { + + @Override + @RequestMapping(name="통계 요청", value="/010/info.do") + public ModelAndView getStatistics(StatQuery query) { + return super.getStatistics(query); + } + + } + @Controller @RequestMapping(name="납부자", value="/payer") class PayerController extends cokr.xit.fims.payer.web.PayerController { diff --git a/src/main/webapp/WEB-INF/jsp/include/dashboard.jsp b/src/main/webapp/WEB-INF/jsp/include/dashboard.jsp index 4160cb38..5f135c15 100644 --- a/src/main/webapp/WEB-INF/jsp/include/dashboard.jsp +++ b/src/main/webapp/WEB-INF/jsp/include/dashboard.jsp @@ -5,20 +5,28 @@
-

?/?

+

+ ?/? +

-

?/?

- +

+ ?/? +

+
-

?/?

- +

+ ?/? +

+
-

?/?

- +

+ ?/? +

+