This commit is contained in:
OhSeongRak 2025-06-11 16:10:31 +09:00
commit 8679eba53e
8 changed files with 121 additions and 69 deletions

View File

@ -1,5 +1,9 @@
package com.won.smarketing.store.config; package com.won.smarketing.store.config;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing; import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
@ -10,7 +14,6 @@ import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
@Configuration @Configuration
@EnableJpaAuditing @EnableJpaAuditing
public class JpaConfig { public class JpaConfig {
}리는 50자 이하여야 합니다")
private String category; private String category;
@Schema(description = "가격", example = "4500", required = true) @Schema(description = "가격", example = "4500", required = true)

View File

@ -12,7 +12,7 @@ import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import javax.validation.Valid; import jakarta.validation.Valid;
import java.util.List; import java.util.List;
/** /**

View File

@ -12,7 +12,7 @@ import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import javax.validation.Valid; import jakarta.validation.Valid;
/** /**
* 매장 관리를 위한 REST API 컨트롤러 * 매장 관리를 위한 REST API 컨트롤러

View File

@ -6,8 +6,8 @@ import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import javax.validation.constraints.Min; import jakarta.validation.constraints.Min;
import javax.validation.constraints.Size; import jakarta.validation.constraints.Size;
/** /**
* 메뉴 수정 요청 DTO * 메뉴 수정 요청 DTO

View File

@ -7,6 +7,8 @@ import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.List;
/** /**
* 매출 정보 데이터 접근을 위한 Repository * 매출 정보 데이터 접근을 위한 Repository
@ -16,29 +18,50 @@ import java.math.BigDecimal;
public interface SalesRepository extends JpaRepository<Sales, Long> { public interface SalesRepository extends JpaRepository<Sales, Long> {
/** /**
* 매장의 오늘 매출 조회 * 매장의 특정 날짜 매출 조회
*
* @param storeId 매장 ID
* @param salesDate 매출 날짜
* @return 해당 날짜 매출 목록
*/
List<Sales> findByStoreIdAndSalesDate(Long storeId, LocalDate salesDate);
/**
* 매장의 특정 기간 매출 조회
*
* @param storeId 매장 ID
* @param startDate 시작 날짜
* @param endDate 종료 날짜
* @return 해당 기간 매출 목록
*/
List<Sales> findByStoreIdAndSalesDateBetween(Long storeId, LocalDate startDate, LocalDate endDate);
/**
* 매장의 오늘 매출 조회 (네이티브 쿼리)
* *
* @param storeId 매장 ID * @param storeId 매장 ID
* @return 오늘 매출 * @return 오늘 매출
*/ */
@Query("SELECT COALESCE(SUM(s.salesAmount), 0) FROM Sales s WHERE s.storeId = :storeId AND s.salesDate = CURRENT_DATE") @Query(value = "SELECT COALESCE(SUM(sales_amount), 0) FROM sales WHERE store_id = :storeId AND sales_date = CURRENT_DATE", nativeQuery = true)
BigDecimal findTodaySalesByStoreId(@Param("storeId") Long storeId); BigDecimal findTodaySalesByStoreIdNative(@Param("storeId") Long storeId);
/** /**
* 매장의 이번 매출 조회 * 매장의 어제 매출 조회 (네이티브 쿼리)
*
* @param storeId 매장 ID
* @return 어제 매출
*/
@Query(value = "SELECT COALESCE(SUM(sales_amount), 0) FROM sales WHERE store_id = :storeId AND sales_date = CURRENT_DATE - INTERVAL '1 day'", nativeQuery = true)
BigDecimal findYesterdaySalesByStoreIdNative(@Param("storeId") Long storeId);
/**
* 매장의 이번 매출 조회 (네이티브 쿼리)
* *
* @param storeId 매장 ID * @param storeId 매장 ID
* @return 이번 매출 * @return 이번 매출
*/ */
@Query("SELECT COALESCE(SUM(s.salesAmount), 0) FROM Sales s WHERE s.storeId = :storeId AND YEAR(s.salesDate) = YEAR(CURRENT_DATE) AND MONTH(s.salesDate) = MONTH(CURRENT_DATE)") @Query(value = "SELECT COALESCE(SUM(sales_amount), 0) FROM sales WHERE store_id = :storeId " +
BigDecimal findMonthSalesByStoreId(@Param("storeId") Long storeId); "AND EXTRACT(YEAR FROM sales_date) = EXTRACT(YEAR FROM CURRENT_DATE) " +
"AND EXTRACT(MONTH FROM sales_date) = EXTRACT(MONTH FROM CURRENT_DATE)", nativeQuery = true)
/** BigDecimal findMonthSalesByStoreIdNative(@Param("storeId") Long storeId);
* 매장의 전일 대비 매출 변화량 조회
*
* @param storeId 매장 ID
* @return 전일 대비 매출 변화량
*/
@Query("SELECT COALESCE((SELECT SUM(s1.salesAmount) FROM Sales s1 WHERE s1.storeId = :storeId AND s1.salesDate = CURRENT_DATE) - (SELECT SUM(s2.salesAmount) FROM Sales s2 WHERE s2.storeId = :storeId AND s2.salesDate = CURRENT_DATE - 1), 0)")
BigDecimal findPreviousDayComparisonByStoreId(@Param("storeId") Long storeId);
} }

View File

@ -83,7 +83,7 @@ public class MenuServiceImpl implements MenuService {
.orElseThrow(() -> new BusinessException(ErrorCode.MENU_NOT_FOUND)); .orElseThrow(() -> new BusinessException(ErrorCode.MENU_NOT_FOUND));
// 메뉴 정보 업데이트 // 메뉴 정보 업데이트
menu.updateMenuInfo( menu.updateMenu(
request.getMenuName(), request.getMenuName(),
request.getCategory(), request.getCategory(),
request.getPrice(), request.getPrice(),

View File

@ -1,60 +1,84 @@
package com.won.smarketing.store.service; package com.won.smarketing.store.service;
import com.won.smarketing.store.dto.SalesResponse; import com.won.smarketing.store.dto.SalesResponse;
import com.won.smarketing.store.entity.Sales;
import com.won.smarketing.store.repository.SalesRepository;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.RoundingMode; import java.time.LocalDate;
import java.util.List;
/** /**
* 매출 서비스 구현체 * 매출 관리 서비스 구현체
* 매출 조회 기능 구현 (현재는 Mock 데이터) * 매출 조회 기능 구현
*/ */
@Slf4j
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
@Transactional(readOnly = true) @Transactional(readOnly = true)
public class SalesServiceImpl implements SalesService { public class SalesServiceImpl implements SalesService {
private final SalesRepository salesRepository;
/** /**
* 매출 정보 조회 * 매출 정보 조회
* 현재는 Mock 데이터를 반환 (실제로는 매출 데이터 조회 로직 필요)
* *
* @return 매출 정보 * @return 매출 정보 (오늘, 월간, 전일 대비)
*/ */
@Override @Override
public SalesResponse getSales() { public SalesResponse getSales() {
log.info("매출 정보 조회"); // TODO: 현재는 더미 데이터 반환, 실제로는 현재 로그인한 사용자의 매장 ID를 사용해야
Long storeId = 1L; // 임시로 설정
// Mock 데이터 (실제로는 데이터베이스에서 조회) // 오늘 매출 계산
BigDecimal todaySales = new BigDecimal("150000"); BigDecimal todaySales = calculateSalesByDate(storeId, LocalDate.now());
BigDecimal monthSales = new BigDecimal("4500000");
BigDecimal yesterdaySales = new BigDecimal("125000");
BigDecimal targetSales = new BigDecimal("176000");
// 전일 대비 변화 계산 // 이번 매출 계산
BigDecimal monthSales = calculateMonthSales(storeId);
// 어제 매출 계산
BigDecimal yesterdaySales = calculateSalesByDate(storeId, LocalDate.now().minusDays(1));
// 전일 대비 매출 변화량 계산
BigDecimal previousDayComparison = todaySales.subtract(yesterdaySales); BigDecimal previousDayComparison = todaySales.subtract(yesterdaySales);
BigDecimal previousDayChangeRate = yesterdaySales.compareTo(BigDecimal.ZERO) > 0
? previousDayComparison.divide(yesterdaySales, 4, RoundingMode.HALF_UP)
.multiply(new BigDecimal("100"))
: BigDecimal.ZERO;
// 목표 대비 달성률 계산
BigDecimal goalAchievementRate = targetSales.compareTo(BigDecimal.ZERO) > 0
? todaySales.divide(targetSales, 4, RoundingMode.HALF_UP)
.multiply(new BigDecimal("100"))
: BigDecimal.ZERO;
return SalesResponse.builder() return SalesResponse.builder()
.todaySales(todaySales) .todaySales(todaySales)
.monthSales(monthSales) .monthSales(monthSales)
.previousDayComparison(previousDayComparison) .previousDayComparison(previousDayComparison)
.previousDayChangeRate(previousDayChangeRate)
.goalAchievementRate(goalAchievementRate)
.build(); .build();
} }
}
/**
* 특정 날짜의 매출 계산
*
* @param storeId 매장 ID
* @param date 날짜
* @return 해당 날짜 매출
*/
private BigDecimal calculateSalesByDate(Long storeId, LocalDate date) {
List<Sales> salesList = salesRepository.findByStoreIdAndSalesDate(storeId, date);
return salesList.stream()
.map(Sales::getSalesAmount)
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
/**
* 이번 매출 계산
*
* @param storeId 매장 ID
* @return 이번 매출
*/
private BigDecimal calculateMonthSales(Long storeId) {
LocalDate now = LocalDate.now();
LocalDate startOfMonth = now.withDayOfMonth(1);
LocalDate endOfMonth = now.withDayOfMonth(now.lengthOfMonth());
List<Sales> salesList = salesRepository.findByStoreIdAndSalesDateBetween(storeId, startOfMonth, endOfMonth);
return salesList.stream()
.map(Sales::getSalesAmount)
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
}

View File

@ -1,31 +1,33 @@
server: server:
port: ${SERVER_PORT:8082} port: ${SERVER_PORT:8082}
servlet:
context-path: /
spring: spring:
application: application:
name: store-service name: store-service
datasource: datasource:
url: jdbc:postgresql://${POSTGRES_HOST:localhost}:${POSTGRES_PORT:5432}/${POSTGRES_DB:storedb} url: jdbc:postgresql://${POSTGRES_HOST:localhost}:${POSTGRES_PORT:5432}/${POSTGRES_DB:StoreDB}
username: ${POSTGRES_USER:postgres} username: ${POSTGRES_USER:postgres}
password: ${POSTGRES_PASSWORD:postgres} password: ${POSTGRES_PASSWORD:postgres}
driver-class-name: org.postgresql.Driver
jpa: jpa:
hibernate: hibernate:
ddl-auto: ${JPA_DDL_AUTO:update} ddl-auto: ${DDL_AUTO:update}
show-sql: ${JPA_SHOW_SQL:true} show-sql: ${SHOW_SQL:true}
properties: properties:
hibernate: hibernate:
dialect: org.hibernate.dialect.PostgreSQLDialect dialect: org.hibernate.dialect.PostgreSQLDialect
format_sql: true format_sql: true
data:
springdoc: redis:
swagger-ui: host: ${REDIS_HOST:localhost}
path: /swagger-ui.html port: ${REDIS_PORT:6379}
operations-sorter: method password: ${REDIS_PASSWORD:}
api-docs:
path: /api-docs
logging: logging:
level: level:
com.won.smarketing.store: ${LOG_LEVEL:DEBUG} com.won.smarketing.store: ${LOG_LEVEL:DEBUG}
jwt:
secret: ${JWT_SECRET:mySecretKeyForJWTTokenGenerationAndValidation123456789}
access-token-validity: ${JWT_ACCESS_VALIDITY:3600000}
refresh-token-validity: ${JWT_REFRESH_VALIDITY:604800000}