SpringBoot 배달의민족-24 관리자페이지 구현 ( 매출 관리 )

hanteng·2022년 8월 4일
0

SpringBoot 배달의민족

목록 보기
24/27

오늘은 매출관리 페이지에 대해서 구현해보도록 하겠습니다
관리자(사장님)는 특정날짜 또는 기간동안 판매한 메뉴와 총금액을 알수 있어야 합니다
지금 구현하려고 하는 페이지에서는 특정날짜에 배달이 완료된 주문들의
메뉴목록과 결제금액 모든 주문의 총금액을 나타내고 년/월/일의 총 매출금액을
그래프로 나타내도록 하겠습니다

배달의민족24 - 화면구성파일

위의 주소에서 화면구성을 위한 파일들을 받아 추가해주고 화면을 구성할 데이터를
DB에서 화면으로 넘겨줄 DTO를 2개 생성해줍니다

SalesDto.java 전체코드

@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인 리스트가 됩니다

SalesDetailDto.java 전체코드

@Data
public class SalesDetailDto {
	
	private int totalPrice;
	private String foodInfo;
	
}

SalesDetailDto는 특정날짜의 판매한 주문들의 메뉴목록과 주문금액을 전달한 객체입니다
7월31일 하루에 총30건의 주문을 완료하였다면 List<SalesDetailDto>는 길이가 30인
리스트가 됩니다. 총 금액의 경우 SalesDto와는 다르게 이 안에 저장하지 않습니다

이제 utils패키지안에 List정렬을 위한 클래스를 하나 추가해줍니다

SalesSort.java 전체코드

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을
비교하여 오름차순 또는 내림차순으로 리스트내의 아이템을 정렬합니다
가장 기본적인 정렬 코드이므로 설명은 생략하도록 하겠습니다

AdminController.java 추가코드

	@GetMapping("/admin/management/sales/{id}")
	public String sales(@PathVariable long id) {
		return "admin/sales";
	}

우리가 위에서 추가한 화면을 사용자에게 보여주기 위한 코드를 추가해줍니다

AdminApiController.java 추가코드

	// 특정일 판매 데이터
	@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(기간)을
파라메터로 받습니다 (년/월/주)

AdminService.java 추가코드

	//특정일 판매 목록
	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처럼
달까지만 넘어오기 때문에 붙여줍니다.

AdminMapper.java 추가코드

	//특정일 판매 데이터
	public List<SalesDetailDto> salesDetail(Map<String, Object> map);
	
	//특정기간 매출 데이터
	public List<SalesDto> sales(Map<String, Object> map);

AdminMapper.xml 추가코드

	<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월까지 월단위로 행을 만듭니다

profile
이메일 : ehfvndcjstk@naver.com

0개의 댓글