오늘은 매출관리 페이지에 대해서 구현해보도록 하겠습니다
관리자(사장님)는 특정날짜 또는 기간동안 판매한 메뉴와 총금액을 알수 있어야 합니다
지금 구현하려고 하는 페이지에서는 특정날짜에 배달이 완료된 주문들의
메뉴목록과 결제금액 모든 주문의 총금액을 나타내고 년/월/일의 총 매출금액을
그래프로 나타내도록 하겠습니다
위의 주소에서 화면구성을 위한 파일들을 받아 추가해주고 화면을 구성할 데이터를
DB에서 화면으로 넘겨줄 DTO를 2개 생성해줍니다
@Data
public class SalesDto {
private Date orderDate;
private int total;
}
SalesDto는 특정기간들의 총매출액을 전달할 객체입니다. 관리자가 7월 한달간의
매출액을 나타내길 원할시 orderDate는 7월1일부터 7월31일까지가 들어가게 되며
total에는 각날의 총매출액이 저장됩니다 제일 마지막에는 7월1일~7월31일까지의
모든 total을 더한 값이 저장됩니다 즉 List<salesDto>
는 길이가 32인 리스트가 됩니다
@Data
public class SalesDetailDto {
private int totalPrice;
private String foodInfo;
}
SalesDetailDto는 특정날짜의 판매한 주문들의 메뉴목록과 주문금액을 전달한 객체입니다
7월31일 하루에 총30건의 주문을 완료하였다면 List<SalesDetailDto>
는 길이가 30인
리스트가 됩니다. 총 금액의 경우 SalesDto와는 다르게 이 안에 저장하지 않습니다
이제 utils패키지안에 List정렬을 위한 클래스를 하나 추가해줍니다
public class SalesSort {
public SalesSort(List<CartDto> menuList, String sort) {
sort(menuList, sort);
}
public void sort(List<CartDto> menuList, String sort) {
Collections.sort(menuList, new Comparator<CartDto>() {
@Override
public int compare(CartDto o1, CartDto o2) {
// 가격 오름차순
if("price".equals(sort)) {
return o1.getTotalPrice() - o2.getTotalPrice();
}
// 가격 내림차순
else if("priceR".equals(sort)) {
return o2.getTotalPrice() - o1.getTotalPrice();
}
// 기본 정렬 이름 오름차순
else {
String name1= o1.getFoodName();
String name2= o2.getFoodName();
if(name1.compareTo(name2) == 0) {
String[] option1 = o1.getOptionName();
String[] option2 = o2.getOptionName();
if(option1 == null) {
return 1;
}
if(option2 == null) {
return -1;
}
return option1[0].compareTo(option2[0]);
}
if("nameR".equals(sort)) {
return o2.getFoodName().compareTo(o1.getFoodName());
} else {
return o1.getFoodName().compareTo(o2.getFoodName());
}
}
}
});
}
}
정렬을 DB에서 하지 않고 새로운 클래스를 만들어 서버에서 하는지에 대해서는
밑에서 설명하도록 하겠습니다. 자바콜렉션을 사용하여 price또는 foodName을
비교하여 오름차순 또는 내림차순으로 리스트내의 아이템을 정렬합니다
가장 기본적인 정렬 코드이므로 설명은 생략하도록 하겠습니다
@GetMapping("/admin/management/sales/{id}")
public String sales(@PathVariable long id) {
return "admin/sales";
}
우리가 위에서 추가한 화면을 사용자에게 보여주기 위한 코드를 추가해줍니다
// 특정일 판매 데이터
@GetMapping("/api/admin/management/salesDetail")
public ResponseEntity<Map<String, Object>> salesDetail(long storeId, String date, String sort){
System.out.printf("가게 번호 : %d, 날짜 : %s ", storeId, date);
Map<String, Object> salseToday = adminService.salesDetail(storeId, date, sort);
return ResponseEntity.ok().body(salseToday);
}
//특정 기간 매출 데이터
@GetMapping("/api/admin/management/sales")
public ResponseEntity<List<SalesDto>> sales(long storeId, String date, String term) {
System.out.printf("가게 번호 : %d, 날짜 : %s ", storeId, date);
List<SalesDto> sales = adminService.sales(storeId,date, term);
return ResponseEntity.ok().body(sales);
}
관리자가 특정일 또는 특정 기간에 대한 매출정보를 조회할시 데이터를 보내주기 위한
코드를 추가해줍니다. 특정일 매출을 나타낼땐 어떤 메뉴가 얼마나 팔렸는지를 이름순/가격순
으로 나열하기 위해 파라메터로 sort를 추가로 받고 특정기간매출에 대해서는 term(기간)을
파라메터로 받습니다 (년/월/주)
//특정일 판매 목록
public Map<String, Object> salesDetail(long storeId, String date, String sort) {
Map<String, Object> map = new HashMap<>();
map.put("storeId", storeId);
map.put("date", date);
List<SalesDetailDto> salesToday = adminMapper.salesDetail(map);
long total = 0;
List<CartDto> menuList = new ArrayList<>();
for(int i=0;i<salesToday.size();i++) {
List<CartDto> cartList = FoodInfoFromJson.foodInfoFromJson(salesToday.get(i).getFoodInfo());
for(int j=0;j<cartList.size();j++) {
CartDto cart = cartList.get(j);
if(menuList.contains(cart)) {
int index = menuList.indexOf(cart);
int amount = cart.getAmount();
int price = cart.getTotalPrice();
menuList.get(index).setAmount(amount + menuList.get(index).getAmount());
menuList.get(index).setTotalPrice(price + menuList.get(index).getTotalPrice());
} else {
menuList.add(cartList.get(j));
}
}
total += salesToday.get(i).getTotalPrice();
}
map.remove("storeId'");
map.remove("date");
new SalesSort(menuList, sort);
map.put("menuList", menuList);
map.put("total", total);
return map;
}
//특정 기간 매출 데이터
public List<SalesDto> sales(long storeId, String date, String term) {
date = date + "-01";
Map<String, Object> map = new HashMap<>();
map.put("storeId", storeId);
map.put("date", date);
map.put("term", term);
return adminMapper.sales(map);
}
특정일 판매목록 코드에 대해서 위에서 정렬클래스를 추가했던 이유와 같이 설명하도록
하겠습니다. 우리가 위에서 추가한 SalesDetailDto에 특정날짜의 주문메뉴와 금액에
대해서 가져올때 하루 30건의 주문완료건이 있었다면 List<SalesDetailDto>
는
내부에 30개의 아이템을 가진 리스트가 됩니다.
우리는 FOOD_INFO에 메뉴와 옵션을 JSON데이터로 FoodId를 포함하여 저장했었습니다.
그렇기 때문에 아무리 FOOD_INFO 컬럼으로 정렬을 하여도 FoodId순으로 정렬이 되므로
DB에서 ORDER BY절을 이용하여 FoodName순으로 정렬할수 있는 방법이 없습니다.
따라서 JSON데이터를 List<CarDto>
로 변환하여 메뉴와 옵션이
모두 일치하는경우 수량에 +1을 해주고 (2건의 배달이 모두 포테이토피자에 치즈추가일경우
name = 포테이토피자 , option = 치즈추가 , 수량 = 2 , price = 18000 * 수량 )
이 작업이 끝나면 위에서 생성한 정렬클래스를 이용하여 이름 또는 가격순으로 정렬합니다
특정 기간 매출데이터에서 date + "-01"을 해주는 이유는 현재 date의 경우 2022-08처럼
달까지만 넘어오기 때문에 붙여줍니다.
//특정일 판매 데이터
public List<SalesDetailDto> salesDetail(Map<String, Object> map);
//특정기간 매출 데이터
public List<SalesDto> sales(Map<String, Object> map);
<select id="salesDetail" resultType="com.han.delivery.dto.SalesDetailDto">
WITH T_ORDER AS (
SELECT * FROM (
SELECT ORDER_NUM, STORE_ID, ORDER_DATE, TOTAL_PRICE, DELIVERY_STATUS FROM DL_ORDER_USER
UNION ALL
SELECT ORDER_NUM, STORE_ID, ORDER_DATE, TOTAL_PRICE, DELIVERY_STATUS FROM DL_ORDER_NON_USER
)
WHERE STORE_ID = #{storeId }
AND DELIVERY_STATUS = '배달 완료'
<if test="date == null">
AND TO_CHAR(ORDER_DATE, 'YYYYMMDD') = TO_CHAR(SYSDATE, 'YYYYMMDD')
</if>
<if test="date != null">
AND TO_CHAR(ORDER_DATE, 'YYYY-MM-DD') = #{date }
</if>
),
T_DETAIL AS (
SELECT ORDER_NUM,
LISTAGG(FOOD_INFO, '/') FOOD_INFO
FROM DL_ORDER_DETAIL_USER N
GROUP BY ORDER_NUM
UNION ALL
SELECT ORDER_NUM,
LISTAGG(FOOD_INFO, '/') FOOD_INFO
FROM DL_ORDER_DETAIL_NON_USER N
GROUP BY ORDER_NUM
)
SELECT TOTAL_PRICE
,FOOD_INFO
FROM T_ORDER O
LEFT JOIN T_DETAIL D
ON O.ORDER_NUM = D.ORDER_NUM
</select>
<select id="sales" resultType="com.han.delivery.dto.SalesDto">
WITH T_ORDER AS (
SELECT STORE_ID
,TO_DATE(ORDER_DATE) ORDER_DATE
,TOTAL_PRICE
FROM (
SELECT TO_CHAR(ORDER_DATE, 'YYYY/MM/DD') ORDER_DATE, STORE_ID, TOTAL_PRICE, DELIVERY_STATUS FROM DL_ORDER_USER
UNION ALL
SELECT TO_CHAR(ORDER_DATE, 'YYYY/MM/DD') ORDER_DATE, STORE_ID, TOTAL_PRICE, DELIVERY_STATUS FROM DL_ORDER_NON_USER
)
WHERE STORE_ID = #{storeId }
AND DELIVERY_STATUS = '배달 완료'
<choose>
<when test="term == 'year'">
AND ORDER_DATE BETWEEN TRUNC(TO_DATE(#{date }), 'YYYY') AND LAST_DAY(#{date })
</when>
<otherwise>
AND ORDER_DATE BETWEEN TRUNC(TO_DATE(#{date }), 'MM') AND LAST_DAY(#{date })
</otherwise>
</choose>
)
<if test="term == 'month'">
SELECT CAL.ORDER_DATE
,SUM(O.TOTAL_PRICE) TOTAL
FROM (
SELECT FIRST_DAY + LEVEL -1 ORDER_DATE
FROM (
SELECT TRUNC(TO_DATE(#{date }), 'MM') FIRST_DAY FROM DUAL
)
CONNECT BY FIRST_DAY + LEVEL -1 <![CDATA[ <= ]]> LAST_DAY(#{date })
) CAL
LEFT JOIN T_ORDER O
ON CAL.ORDER_DATE = O.ORDER_DATE
GROUP BY ROLLUP(CAL.ORDER_DATE)
ORDER BY ORDER_DATE
</if>
<if test="term == 'week'">
SELECT CAL.ORDER_DATE
,SUM(O.TOTAL_PRICE) TOTAL
FROM (
SELECT FIRST_DAY + LEVEL -1 ORDER_DATE
FROM (
SELECT TRUNC(SYSDATE, 'IW') FIRST_DAY FROM DUAL
)
CONNECT BY FIRST_DAY + LEVEL <![CDATA[ <= ]]> FIRST_DAY + 7
) CAL
LEFT JOIN T_ORDER O
ON CAL.ORDER_DATE = O.ORDER_DATE
GROUP BY ROLLUP(CAL.ORDER_DATE)
ORDER BY ORDER_DATE
</if>
<if test="term == 'year'">
SELECT TRUNC(CAL.ORDER_DATE, 'MM') ORDER_DATE
,SUM(O.TOTAL_PRICE) TOTAL
FROM (
SELECT ADD_MONTHS(FIRST_DAY, LEVEL -1) ORDER_DATE
FROM (
SELECT TRUNC(SYSDATE, 'YYYY') FIRST_DAY FROM DUAL
)
CONNECT BY LEVEL <![CDATA[ <= ]]> 12
) CAL
LEFT JOIN T_ORDER O
ON CAL.ORDER_DATE = TRUNC(O.ORDER_DATE, 'MM')
GROUP BY ROLLUP(TRUNC(CAL.ORDER_DATE, 'MM'))
ORDER BY ORDER_DATE
</if>
</select>
salesDetail의 경우 이미 이전에 다 설명했던 쿼리이므로 생략하도록 하겠습니다
IF절의 date == null일경우는 오늘날짜를 뜻합니다
sales의 쿼리가 조금 복잡한데 년 / 월 / 주에 따라 쿼리가 달라지기 때문입니다
TRUNC함수의 경우 YYYY또는 MM과 같이 패턴에 따라 해 또는 달까지 표시해줍니다
LAST_DAY함수를 사용할 경우 해당 해 또는 달의 마지막 날짜까지 검색합니다
즉 검색기간이 year이고 오늘이 8월4일이라면 2022-01-01 ~ 2022-12-31까지 검색하며
검색기간이 year이 아니라면 2022-08-01 ~ 2022-08-31까지 검색합니다
이제 만약 term이 month일 경우 FIRST_DAY함수를 사용해 해당달의 첫째날(8월1일)을 구하고
LAST_DAY함수를 이용해 해당달의 마지막날(8월31일)을 구하여 CONNECT BY를 사용하여
8월1일~8월31일까지의 모든날짜를 기준으로 행을 만들고 ROLLUP함수를 사용해
각행에 ORDER_DATE를 기준으로 합계를 구합니다
term이 week일경우 SYSDATE를 이용하여 현재 주의 첫째날부터 끝날까지 일단위로
year일 경우에는 1월~12월까지 월단위로 행을 만듭니다