add: init project

This commit is contained in:
unknown
2025-06-11 11:01:53 +09:00
parent b68c7c5fa1
commit e6ef3f0671
63 changed files with 1088 additions and 1492 deletions
+1
View File
@@ -1,5 +1,6 @@
dependencies {
implementation project(':common')
implementation 'com.mysql:mysql-connector-j'
}
bootJar {
@@ -0,0 +1,28 @@
package com.won.smarketing.store.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
/**
* JPA 설정 클래스
* JPA Auditing 기능 활성화
*/
@Configuration
@EnableJpaAuditing
public class JpaConfig {
}리는 50자 이하여야 합니다")
private String category;
@Schema(description = "가격", example = "4500", required = true)
@NotNull(message = "가격은 필수입니다")
@Min(value = 0, message = "가격은 0원 이상이어야 합니다")
private Integer price;
@Schema(description = "메뉴 설명", example = "진한 맛의 아메리카노")
@Size(max = 500, message = "메뉴 설명은 500자 이하여야 합니다")
private String description;
@Schema(description = "이미지 URL", example = "https://example.com/americano.jpg")
@Size(max = 500, message = "이미지 URL은 500자 이하여야 합니다")
private String image;
}
@@ -1,49 +1,49 @@
package com.won.smarketing.store.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
/**
* 메뉴 등록 요청 DTO
* 메뉴 등록 시 필요한 정보를 담는 데이터 전송 객체
* 메뉴 등록 시 필요한 정보를 전달합니다.
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "메뉴 등록 요청 정보")
@Schema(description = "메뉴 등록 요청")
public class MenuCreateRequest {
@Schema(description = "매장 ID", example = "1", required = true)
@NotNull(message = "매장 ID는 필수입니다.")
@NotNull(message = "매장 ID는 필수입니다")
private Long storeId;
@Schema(description = "메뉴명", example = "아메리카노", required = true)
@NotBlank(message = "메뉴명은 필수입니다.")
@Size(max = 200, message = "메뉴명은 200자 이하여야 합니다.")
@NotBlank(message = "메뉴명은 필수입니다")
@Size(max = 100, message = "메뉴명은 100자 이하여야 합니다")
private String menuName;
@Schema(description = "메뉴 카테고리", example = "커피", required = true)
@NotBlank(message = "카테고리는 필수입니다.")
@Size(max = 100, message = "카테고리는 100자 이하여야 합니다.")
@Schema(description = "카테고리", example = "커피")
@Size(max = 50, message = "카테고리는 50자 이하여야 합니다")
private String category;
@Schema(description = "가격", example = "4500", required = true)
@NotNull(message = "가격은 필수입니다.")
@Min(value = 0, message = "가격은 0 이상이어야 합니다.")
@Schema(description = "가격", example = "4500")
@Min(value = 0, message = "가격은 0원 이상이어야 합니다")
private Integer price;
@Schema(description = "메뉴 설명", example = "진한 원두의 깊은 맛")
@Schema(description = "메뉴 설명", example = "진한 맛의 아메리카노")
@Size(max = 500, message = "메뉴 설명은 500자 이하여야 합니다")
private String description;
@Schema(description = "메뉴 이미지 URL", example = "https://example.com/americano.jpg")
@Schema(description = "이미지 URL", example = "https://example.com/americano.jpg")
@Size(max = 500, message = "이미지 URL은 500자 이하여야 합니다")
private String image;
}
@@ -9,37 +9,41 @@ import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
/**
* 메뉴 정보 응답 DTO
* 메뉴 정보 조회/등록/수정 시 반환되는 데이터
* 메뉴 응답 DTO
* 메뉴 정보를 클라이언트에게 전달합니다.
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "메뉴 정보 응답")
@Schema(description = "메뉴 응답")
public class MenuResponse {
@Schema(description = "메뉴 ID", example = "1")
private Long menuId;
@Schema(description = "매장 ID", example = "1")
private Long storeId;
@Schema(description = "메뉴명", example = "아메리카노")
private String menuName;
@Schema(description = "메뉴 카테고리", example = "커피")
@Schema(description = "카테고리", example = "커피")
private String category;
@Schema(description = "가격", example = "4500")
private Integer price;
@Schema(description = "메뉴 설명", example = "진한 원두의 깊은 맛")
@Schema(description = "메뉴 설명", example = "진한 맛의 아메리카노")
private String description;
@Schema(description = "메뉴 이미지 URL", example = "https://example.com/americano.jpg")
@Schema(description = "이미지 URL", example = "https://example.com/americano.jpg")
private String image;
@Schema(description = "등록 시각")
@Schema(description = "등록일시", example = "2024-01-15T10:30:00")
private LocalDateTime createdAt;
@Schema(description = "수정 시각")
@Schema(description = "수정일시", example = "2024-01-15T10:30:00")
private LocalDateTime updatedAt;
}
@@ -9,22 +9,28 @@ import lombok.NoArgsConstructor;
import java.math.BigDecimal;
/**
* 매출 정보 응답 DTO
* 오늘 매출, 월간 매출, 전일 대비 매출 정보
* 매출 응답 DTO
* 매출 정보를 클라이언트에게 전달합니다.
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "매출 정보 응답")
@Schema(description = "매출 응답")
public class SalesResponse {
@Schema(description = "오늘 매출", example = "150000")
private BigDecimal todaySales;
@Schema(description = "이번 달 매출", example = "3200000")
@Schema(description = "월간 매출", example = "4500000")
private BigDecimal monthSales;
@Schema(description = "전일 대비 매출 변화", example = "25000")
@Schema(description = "전일 대비 매출 변화", example = "25000")
private BigDecimal previousDayComparison;
@Schema(description = "전일 대비 매출 변화율 (%)", example = "15.5")
private BigDecimal previousDayChangeRate;
@Schema(description = "목표 매출 대비 달성율 (%)", example = "85.2")
private BigDecimal goalAchievementRate;
}
@@ -1,79 +1,58 @@
package com.won.smarketing.store.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
/**
* 매장 등록 요청 DTO
* 매장 등록 시 필요한 정보를 담는 데이터 전송 객체
* 매장 등록 시 필요한 정보를 전달합니다.
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "매장 등록 요청 정보")
@Schema(description = "매장 등록 요청")
public class StoreCreateRequest {
@Schema(description = "매장 소유자 사용자 ID", example = "testuser", required = true)
@NotBlank(message = "사용자 ID는 필수입니다.")
private String userId;
@Schema(description = "매장명", example = "맛있는 카페", required = true)
@NotBlank(message = "매장명은 필수입니다.")
@Size(max = 200, message = "매장명은 200자 이하여야 합니다.")
@NotBlank(message = "매장명은 필수입니다")
@Size(max = 100, message = "매장명은 100자 이하여야 합니다")
private String storeName;
@Schema(description = "매장 이미지 URL", example = "https://example.com/store.jpg")
private String storeImage;
@Schema(description = "업종", example = "카페", required = true)
@NotBlank(message = "업종은 필수입니다.")
@Size(max = 100, message = "업종은 100자 이하여야 합니다.")
@Schema(description = "업종", example = "카페")
@Size(max = 50, message = "업종은 50자 이하여야 합니다")
private String businessType;
@Schema(description = "매장 주소", example = "서울시 강남구 테헤란로 123", required = true)
@NotBlank(message = "주소는 필수입니다.")
@Size(max = 500, message = "주소는 500자 이하여야 합니다.")
@Schema(description = "주소", example = "서울시 강남구 테헤란로 123", required = true)
@NotBlank(message = "주소는 필수입니다")
@Size(max = 200, message = "주소는 200자 이하여야 합니다")
private String address;
@Schema(description = "매장 전화번호", example = "02-1234-5678", required = true)
@NotBlank(message = "전화번호는 필수입니다.")
@Pattern(regexp = "^\\d{2,3}-\\d{3,4}-\\d{4}$", message = "올바른 전화번호 형식이 아닙니다.")
@Schema(description = "전화번호", example = "02-1234-5678")
@Size(max = 20, message = "전화번호는 20자 이하여야 합니다")
private String phoneNumber;
@Schema(description = "사업자 번호", example = "123-45-67890", required = true)
@NotBlank(message = "사업자 번호는 필수입니다.")
@Pattern(regexp = "^\\d{3}-\\d{2}-\\d{5}$", message = "사업자 번호 형식이 올바르지 않습니다.")
private String businessNumber;
@Schema(description = "인스타그램 계정", example = "@mycafe")
@Size(max = 100, message = "인스타그램 계정은 100자 이하여야 합니다.")
private String instaAccount;
@Schema(description = "네이버 블로그 계정", example = "mycafe_blog")
@Size(max = 100, message = "네이버 블로그 계정은 100자 이하여야 합니다.")
private String naverBlogAccount;
@Schema(description = "오픈 시간", example = "09:00")
@Pattern(regexp = "^\\d{2}:\\d{2}$", message = "올바른 시간 형식이 아닙니다. (HH:MM)")
private String openTime;
@Schema(description = "마감 시간", example = "22:00")
@Pattern(regexp = "^\\d{2}:\\d{2}$", message = "올바른 시간 형식이 아닙니다. (HH:MM)")
private String closeTime;
@Schema(description = "휴무일", example = "매주 월요일")
@Size(max = 100, message = "휴무일은 100자 이하여야 합니다.")
@Schema(description = "영업시간", example = "09:00 - 22:00")
@Size(max = 100, message = "영업시간은 100자 이하여야 합니다")
private String businessHours;
@Schema(description = "휴무일", example = "매주 일요일")
@Size(max = 100, message = "휴무일은 100자 이하여야 합니다")
private String closedDays;
@Schema(description = "좌석 수", example = "20")
private Integer seatCount;
@Schema(description = "SNS 계정 정보", example = "인스타그램: @mystore")
@Size(max = 500, message = "SNS 계정 정보는 500자 이하여야 합니다")
private String snsAccounts;
@Schema(description = "매장 설명", example = "따뜻한 분위기의 동네 카페입니다.")
@Size(max = 1000, message = "매장 설명은 1000자 이하여야 합니다")
private String description;
}
@@ -9,58 +9,50 @@ import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
/**
* 매장 정보 응답 DTO
* 매장 정보 조회/등록/수정 시 반환되는 데이터
* 매장 응답 DTO
* 매장 정보를 클라이언트에게 전달합니다.
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "매장 정보 응답")
@Schema(description = "매장 응답")
public class StoreResponse {
@Schema(description = "매장 ID", example = "1")
private Long storeId;
@Schema(description = "매장명", example = "맛있는 카페")
private String storeName;
@Schema(description = "매장 이미지 URL", example = "https://example.com/store.jpg")
private String storeImage;
@Schema(description = "업종", example = "카페")
private String businessType;
@Schema(description = "매장 주소", example = "서울시 강남구 테헤란로 123")
@Schema(description = "주소", example = "서울시 강남구 테헤란로 123")
private String address;
@Schema(description = "매장 전화번호", example = "02-1234-5678")
@Schema(description = "전화번호", example = "02-1234-5678")
private String phoneNumber;
@Schema(description = "사업자 번호", example = "123-45-67890")
private String businessNumber;
@Schema(description = "인스타그램 계정", example = "@mycafe")
private String instaAccount;
@Schema(description = "네이버 블로그 계정", example = "mycafe_blog")
private String naverBlogAccount;
@Schema(description = "오픈 시간", example = "09:00")
private String openTime;
@Schema(description = "마감 시간", example = "22:00")
private String closeTime;
@Schema(description = "휴무일", example = "매주 월요일")
@Schema(description = "영업시간", example = "09:00 - 22:00")
private String businessHours;
@Schema(description = "휴무일", example = "매주 일요일")
private String closedDays;
@Schema(description = "좌석 수", example = "20")
private Integer seatCount;
@Schema(description = "등록 시각")
@Schema(description = "SNS 계정 정보", example = "인스타그램: @mystore")
private String snsAccounts;
@Schema(description = "매장 설명", example = "따뜻한 분위기의 동네 카페입니다.")
private String description;
@Schema(description = "등록일시", example = "2024-01-15T10:30:00")
private LocalDateTime createdAt;
@Schema(description = "수정 시각")
@Schema(description = "수정일시", example = "2024-01-15T10:30:00")
private LocalDateTime updatedAt;
}
@@ -1,60 +1,55 @@
package com.won.smarketing.store.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Size;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
/**
* 매장 수정 요청 DTO
* 매장 정보 수정 시 필요한 정보를 담는 데이터 전송 객체
* 매장 정보 수정 시 필요한 정보를 전달합니다.
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema(description = "매장 수정 요청 정보")
@Schema(description = "매장 수정 요청")
public class StoreUpdateRequest {
@Schema(description = "매장명", example = "맛있는 카페")
@Size(max = 200, message = "매장명은 200자 이하여야 합니다.")
@Size(max = 100, message = "매장명은 100자 이하여야 합니다")
private String storeName;
@Schema(description = "매장 이미지 URL", example = "https://example.com/store.jpg")
private String storeImage;
@Schema(description = "매장 주소", example = "서울시 강남구 테헤란로 123")
@Size(max = 500, message = "주소는 500자 이하여야 합니다.")
@Schema(description = "업종", example = "카페")
@Size(max = 50, message = "업종은 50자 이하여야 합니다")
private String businessType;
@Schema(description = "주소", example = "서울시 강남구 테헤란로 123")
@Size(max = 200, message = "주소는 200자 이하여야 합니다")
private String address;
@Schema(description = "매장 전화번호", example = "02-1234-5678")
@Pattern(regexp = "^\\d{2,3}-\\d{3,4}-\\d{4}$", message = "올바른 전화번호 형식이 아닙니다.")
@Schema(description = "전화번호", example = "02-1234-5678")
@Size(max = 20, message = "전화번호는 20자 이하여야 합니다")
private String phoneNumber;
@Schema(description = "인스타그램 계정", example = "@mycafe")
@Size(max = 100, message = "인스타그램 계정은 100자 이하여야 합니다.")
private String instaAccount;
@Schema(description = "네이버 블로그 계정", example = "mycafe_blog")
@Size(max = 100, message = "네이버 블로그 계정은 100자 이하여야 합니다.")
private String naverBlogAccount;
@Schema(description = "오픈 시간", example = "09:00")
@Pattern(regexp = "^\\d{2}:\\d{2}$", message = "올바른 시간 형식이 아닙니다. (HH:MM)")
private String openTime;
@Schema(description = "마감 시간", example = "22:00")
@Pattern(regexp = "^\\d{2}:\\d{2}$", message = "올바른 시간 형식이 아닙니다. (HH:MM)")
private String closeTime;
@Schema(description = "휴무일", example = "매주 월요일")
@Size(max = 100, message = "휴무일은 100자 이하여야 합니다.")
@Schema(description = "영업시간", example = "09:00 - 22:00")
@Size(max = 100, message = "영업시간은 100자 이하여야 합니다")
private String businessHours;
@Schema(description = "휴무일", example = "매주 일요일")
@Size(max = 100, message = "휴무일은 100자 이하여야 합니다")
private String closedDays;
@Schema(description = "좌석 수", example = "20")
private Integer seatCount;
@Schema(description = "SNS 계정 정보", example = "인스타그램: @mystore")
@Size(max = 500, message = "SNS 계정 정보는 500자 이하여야 합니다")
private String snsAccounts;
@Schema(description = "매장 설명", example = "따뜻한 분위기의 동네 카페입니다.")
@Size(max = 1000, message = "매장 설명은 1000자 이하여야 합니다")
private String description;
}
@@ -1,98 +1,62 @@
package com.won.smarketing.store.entity;
import jakarta.persistence.*;
import lombok.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import java.time.LocalDateTime;
/**
* 메뉴 정보를 나타내는 엔티티
* 메뉴명, 카테고리, 가격, 설명, 이미지 정보 저장
* 메뉴 엔티티
* 매장의 메뉴 정보를 관리
*/
@Entity
@Table(name = "menus")
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@NoArgsConstructor
@AllArgsConstructor
@Builder
@EntityListeners(AuditingEntityListener.class)
public class Menu {
/**
* 메뉴 고유 식별자
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "menu_id")
private Long id;
/**
* 매장 ID
*/
@Column(name = "store_id", nullable = false)
private Long storeId;
/**
* 메뉴명
*/
@Column(name = "menu_name", nullable = false, length = 200)
@Column(name = "menu_name", nullable = false, length = 100)
private String menuName;
/**
* 메뉴 카테고리
*/
@Column(name = "category", nullable = false, length = 100)
@Column(name = "category", length = 50)
private String category;
/**
* 가격
*/
@Column(name = "price", nullable = false)
private Integer price;
/**
* 메뉴 설명
*/
@Column(name = "description", columnDefinition = "TEXT")
@Column(name = "description", length = 500)
private String description;
/**
* 메뉴 이미지 URL
*/
@Column(name = "image", length = 500)
@Column(name = "image_url", length = 500)
private String image;
/**
* 메뉴 등록 시각
*/
@CreatedDate
@Column(name = "created_at", nullable = false, updatable = false)
private LocalDateTime createdAt;
/**
* 메뉴 정보 수정 시각
*/
@Column(name = "updated_at", nullable = false)
@LastModifiedDate
@Column(name = "updated_at")
private LocalDateTime updatedAt;
/**
* 엔티티 저장 전 실행되는 메서드
* 생성 시각과 수정 시각을 현재 시각으로 설정
*/
@PrePersist
protected void onCreate() {
createdAt = LocalDateTime.now();
updatedAt = LocalDateTime.now();
}
/**
* 엔티티 업데이트 전 실행되는 메서드
* 수정 시각을 현재 시각으로 갱신
*/
@PreUpdate
protected void onUpdate() {
updatedAt = LocalDateTime.now();
}
/**
* 메뉴 정보 업데이트 메서드
* 메뉴 정보 업데이트
*
* @param menuName 메뉴명
* @param category 카테고리
@@ -100,10 +64,17 @@ public class Menu {
* @param description 설명
* @param image 이미지 URL
*/
public void updateMenuInfo(String menuName, String category, Integer price, String description, String image) {
this.menuName = menuName;
this.category = category;
this.price = price;
public void updateMenu(String menuName, String category, Integer price,
String description, String image) {
if (menuName != null && !menuName.trim().isEmpty()) {
this.menuName = menuName;
}
if (category != null && !category.trim().isEmpty()) {
this.category = category;
}
if (price != null && price > 0) {
this.price = price;
}
this.description = description;
this.image = image;
}
@@ -59,3 +59,4 @@ public class Sales {
createdAt = LocalDateTime.now();
}
}
@@ -1,164 +1,103 @@
package com.won.smarketing.store.entity;
import jakarta.persistence.*;
import lombok.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import java.time.LocalDateTime;
import java.time.LocalTime;
/**
* 매장 정보를 나타내는 엔티티
* 매장의 기본 정보, 운영 정보, SNS 계정 정보 저장
* 매장 엔티티
* 매장의 기본 정보 운영 정보를 관리
*/
@Entity
@Table(name = "stores")
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@NoArgsConstructor
@AllArgsConstructor
@Builder
@EntityListeners(AuditingEntityListener.class)
public class Store {
/**
* 매장 고유 식별자
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "store_id")
private Long id;
/**
* 매장 소유자 사용자 ID
*/
@Column(name = "user_id", unique = true, nullable = false, length = 50)
private String userId;
@Column(name = "member_id", nullable = false)
private Long memberId;
/**
* 매장명
*/
@Column(name = "store_name", nullable = false, length = 200)
@Column(name = "store_name", nullable = false, length = 100)
private String storeName;
/**
* 매장 이미지 URL
*/
@Column(name = "store_image", length = 500)
private String storeImage;
/**
* 업종
*/
@Column(name = "business_type", nullable = false, length = 100)
@Column(name = "business_type", length = 50)
private String businessType;
/**
* 매장 주소
*/
@Column(name = "address", nullable = false, length = 500)
@Column(name = "address", nullable = false, length = 200)
private String address;
/**
* 매장 전화번호
*/
@Column(name = "phone_number", nullable = false, length = 20)
@Column(name = "phone_number", length = 20)
private String phoneNumber;
/**
* 사업자 번호
*/
@Column(name = "business_number", nullable = false, length = 20)
private String businessNumber;
@Column(name = "business_hours", length = 100)
private String businessHours;
/**
* 인스타그램 계정
*/
@Column(name = "insta_account", length = 100)
private String instaAccount;
/**
* 네이버 블로그 계정
*/
@Column(name = "naver_blog_account", length = 100)
private String naverBlogAccount;
/**
* 오픈 시간
*/
@Column(name = "open_time", length = 10)
private String openTime;
/**
* 마감 시간
*/
@Column(name = "close_time", length = 10)
private String closeTime;
/**
* 휴무일
*/
@Column(name = "closed_days", length = 100)
private String closedDays;
/**
* 좌석 수
*/
@Column(name = "seat_count")
private Integer seatCount;
/**
* 매장 등록 시각
*/
@Column(name = "sns_accounts", length = 500)
private String snsAccounts;
@Column(name = "description", length = 1000)
private String description;
@CreatedDate
@Column(name = "created_at", nullable = false, updatable = false)
private LocalDateTime createdAt;
/**
* 매장 정보 수정 시각
*/
@Column(name = "updated_at", nullable = false)
@LastModifiedDate
@Column(name = "updated_at")
private LocalDateTime updatedAt;
/**
* 엔티티 저장 전 실행되는 메서드
* 생성 시각과 수정 시각을 현재 시각으로 설정
*/
@PrePersist
protected void onCreate() {
createdAt = LocalDateTime.now();
updatedAt = LocalDateTime.now();
}
/**
* 엔티티 업데이트 전 실행되는 메서드
* 수정 시각을 현재 시각으로 갱신
*/
@PreUpdate
protected void onUpdate() {
updatedAt = LocalDateTime.now();
}
/**
* 매장 정보 업데이트 메서드
* 매장 정보 업데이트
*
* @param storeName 매장명
* @param storeImage 매장 이미지
* @param businessType 업종
* @param address 주소
* @param phoneNumber 전화번호
* @param instaAccount 인스타그램 계정
* @param naverBlogAccount 네이버 블로그 계정
* @param openTime 오픈 시간
* @param closeTime 마감 시간
* @param businessHours 영업시간
* @param closedDays 휴무일
* @param seatCount 좌석 수
* @param snsAccounts SNS 계정 정보
* @param description 설명
*/
public void updateStoreInfo(String storeName, String storeImage, String address, String phoneNumber,
String instaAccount, String naverBlogAccount, String openTime, String closeTime,
String closedDays, Integer seatCount) {
this.storeName = storeName;
this.storeImage = storeImage;
this.address = address;
public void updateStore(String storeName, String businessType, String address,
String phoneNumber, String businessHours, String closedDays,
Integer seatCount, String snsAccounts, String description) {
if (storeName != null && !storeName.trim().isEmpty()) {
this.storeName = storeName;
}
if (businessType != null && !businessType.trim().isEmpty()) {
this.businessType = businessType;
}
if (address != null && !address.trim().isEmpty()) {
this.address = address;
}
this.phoneNumber = phoneNumber;
this.instaAccount = instaAccount;
this.naverBlogAccount = naverBlogAccount;
this.openTime = openTime;
this.closeTime = closeTime;
this.businessHours = businessHours;
this.closedDays = closedDays;
this.seatCount = seatCount;
this.snsAccounts = snsAccounts;
this.description = description;
}
}
@@ -14,10 +14,29 @@ import java.util.Optional;
public interface StoreRepository extends JpaRepository<Store, Long> {
/**
* 사용자 ID로 매장 조회
* 회원 ID로 매장 조회
*
* @param userId 사용자 ID
* @return 매장 정보
* @param memberId 회원 ID
* @return 매장 정보 (Optional)
*/
Optional<Store> findByUserId(String userId);
Optional<Store> findByMemberId(Long memberId);
/**
* 회원의 매장 존재 여부 확인
*
* @param memberId 회원 ID
* @return 존재 여부
*/
boolean existsByMemberId(Long memberId);
/**
* 매장명으로 매장 조회
*
* @param storeName 매장명
* @return 매장 목록
*/
Optional<Store> findByStoreName(String storeName);
}
@@ -7,13 +7,13 @@ import com.won.smarketing.store.dto.MenuUpdateRequest;
import java.util.List;
/**
* 메뉴 관리 서비스 인터페이스
* 메뉴 등록, 조회, 수정, 삭제 기능 정의
* 메뉴 서비스 인터페이스
* 메뉴 관리 관련 비즈니스 로직 정의
*/
public interface MenuService {
/**
* 메뉴 정보 등록
* 메뉴 등록
*
* @param request 메뉴 등록 요청 정보
* @return 등록된 메뉴 정보
@@ -31,7 +31,7 @@ public interface MenuService {
/**
* 메뉴 정보 수정
*
* @param menuId 수정할 메뉴 ID
* @param menuId 메뉴 ID
* @param request 메뉴 수정 요청 정보
* @return 수정된 메뉴 정보
*/
@@ -40,7 +40,7 @@ public interface MenuService {
/**
* 메뉴 삭제
*
* @param menuId 삭제할 메뉴 ID
* @param menuId 메뉴 ID
*/
void deleteMenu(Long menuId);
}
@@ -3,15 +3,15 @@ package com.won.smarketing.store.service;
import com.won.smarketing.store.dto.SalesResponse;
/**
* 매출 관리 서비스 인터페이스
* 매출 조회 기능 정의
* 매출 서비스 인터페이스
* 매출 조회 관련 비즈니스 로직 정의
*/
public interface SalesService {
/**
* 매출 정보 조회
*
* @return 매출 정보 (오늘, 월간, 전일 대비)
* @return 매출 정보
*/
SalesResponse getSales();
}
@@ -1,42 +1,60 @@
package com.won.smarketing.store.service;
import com.won.smarketing.store.dto.SalesResponse;
import com.won.smarketing.store.repository.SalesRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.math.RoundingMode;
/**
* 매출 관리 서비스 구현체
* 매출 조회 기능 구현
* 매출 서비스 구현체
* 매출 조회 기능 구현 (현재는 Mock 데이터)
*/
@Slf4j
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class SalesServiceImpl implements SalesService {
private final SalesRepository salesRepository;
/**
* 매출 정보 조회
* 현재는 Mock 데이터를 반환 (실제로는 매출 데이터 조회 로직 필요)
*
* @return 매출 정보 (오늘, 월간, 전일 대비)
* @return 매출 정보
*/
@Override
public SalesResponse getSales() {
// TODO: 현재는 더미 데이터 반환, 실제로는 현재 로그인한 사용자의 매장 ID를 사용해야 함
Long storeId = 1L; // 임시로 설정
log.info("매출 정보 조회");
// Mock 데이터 (실제로는 데이터베이스에서 조회)
BigDecimal todaySales = new BigDecimal("150000");
BigDecimal monthSales = new BigDecimal("4500000");
BigDecimal yesterdaySales = new BigDecimal("125000");
BigDecimal targetSales = new BigDecimal("176000");
// 전일 대비 변화 계산
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;
BigDecimal todaySales = salesRepository.findTodaySalesByStoreId(storeId);
BigDecimal monthSales = salesRepository.findMonthSalesByStoreId(storeId);
BigDecimal previousDayComparison = salesRepository.findPreviousDayComparisonByStoreId(storeId);
return SalesResponse.builder()
.todaySales(todaySales != null ? todaySales : BigDecimal.ZERO)
.monthSales(monthSales != null ? monthSales : BigDecimal.ZERO)
.previousDayComparison(previousDayComparison != null ? previousDayComparison : BigDecimal.ZERO)
.todaySales(todaySales)
.monthSales(monthSales)
.previousDayComparison(previousDayComparison)
.previousDayChangeRate(previousDayChangeRate)
.goalAchievementRate(goalAchievementRate)
.build();
}
}
@@ -5,13 +5,13 @@ import com.won.smarketing.store.dto.StoreResponse;
import com.won.smarketing.store.dto.StoreUpdateRequest;
/**
* 매장 관리 서비스 인터페이스
* 매장 등록, 조회, 수정 기능 정의
* 매장 서비스 인터페이스
* 매장 관리 관련 비즈니스 로직 정의
*/
public interface StoreService {
/**
* 매장 정보 등록
* 매장 등록
*
* @param request 매장 등록 요청 정보
* @return 등록된 매장 정보
@@ -19,9 +19,16 @@ public interface StoreService {
StoreResponse register(StoreCreateRequest request);
/**
* 매장 정보 조회
* 매장 정보 조회 (현재 로그인 사용자)
*
* @param storeId 조회할 매장 ID
* @return 매장 정보
*/
StoreResponse getMyStore();
/**
* 매장 정보 조회 (매장 ID)
*
* @param storeId 매장 ID
* @return 매장 정보
*/
StoreResponse getStore(String storeId);
@@ -29,7 +36,7 @@ public interface StoreService {
/**
* 매장 정보 수정
*
* @param storeId 수정할 매장 ID
* @param storeId 매장 ID
* @param request 매장 수정 요청 정보
* @return 수정된 매장 정보
*/
@@ -8,13 +8,16 @@ import com.won.smarketing.store.dto.StoreUpdateRequest;
import com.won.smarketing.store.entity.Store;
import com.won.smarketing.store.repository.StoreRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* 매장 관리 서비스 구현체
* 매장 서비스 구현체
* 매장 등록, 조회, 수정 기능 구현
*/
@Slf4j
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
@@ -23,7 +26,7 @@ public class StoreServiceImpl implements StoreService {
private final StoreRepository storeRepository;
/**
* 매장 정보 등록
* 매장 등록
*
* @param request 매장 등록 요청 정보
* @return 등록된 매장 정보
@@ -31,50 +34,75 @@ public class StoreServiceImpl implements StoreService {
@Override
@Transactional
public StoreResponse register(StoreCreateRequest request) {
// 사용자별 매장 중복 등록 확인
if (storeRepository.findByUserId(request.getUserId()).isPresent()) {
String currentUserId = getCurrentUserId();
Long memberId = Long.valueOf(currentUserId); // 실제로는 Member ID 조회 필요
log.info("매장 등록 시작: {} (회원: {})", request.getStoreName(), memberId);
// 회원당 하나의 매장만 등록 가능
if (storeRepository.existsByMemberId(memberId)) {
throw new BusinessException(ErrorCode.STORE_ALREADY_EXISTS);
}
// 매장 엔티티 생성 및 저장
Store store = Store.builder()
.userId(request.getUserId())
.memberId(memberId)
.storeName(request.getStoreName())
.storeImage(request.getStoreImage())
.businessType(request.getBusinessType())
.address(request.getAddress())
.phoneNumber(request.getPhoneNumber())
.businessNumber(request.getBusinessNumber())
.instaAccount(request.getInstaAccount())
.naverBlogAccount(request.getNaverBlogAccount())
.openTime(request.getOpenTime())
.closeTime(request.getCloseTime())
.businessHours(request.getBusinessHours())
.closedDays(request.getClosedDays())
.seatCount(request.getSeatCount())
.snsAccounts(request.getSnsAccounts())
.description(request.getDescription())
.build();
Store savedStore = storeRepository.save(store);
log.info("매장 등록 완료: {} (ID: {})", savedStore.getStoreName(), savedStore.getId());
return toStoreResponse(savedStore);
}
/**
* 매장 정보 조회
* 매장 정보 조회 (현재 로그인 사용자)
*
* @param storeId 조회할 매장 ID
* @return 매장 정보
*/
@Override
public StoreResponse getStore(String storeId) {
Store store = storeRepository.findByUserId(storeId)
public StoreResponse getMyStore() {
String currentUserId = getCurrentUserId();
Long memberId = Long.valueOf(currentUserId);
Store store = storeRepository.findByMemberId(memberId)
.orElseThrow(() -> new BusinessException(ErrorCode.STORE_NOT_FOUND));
return toStoreResponse(store);
}
/**
* 매장 정보 조회 (매장 ID)
*
* @param storeId 매장 ID
* @return 매장 정보
*/
@Override
public StoreResponse getStore(String storeId) {
try {
Long id = Long.valueOf(storeId);
Store store = storeRepository.findById(id)
.orElseThrow(() -> new BusinessException(ErrorCode.STORE_NOT_FOUND));
return toStoreResponse(store);
} catch (NumberFormatException e) {
throw new BusinessException(ErrorCode.INVALID_INPUT_VALUE);
}
}
/**
* 매장 정보 수정
*
* @param storeId 수정할 매장 ID
* @param storeId 매장 ID
* @param request 매장 수정 요청 정보
* @return 수정된 매장 정보
*/
@@ -83,22 +111,23 @@ public class StoreServiceImpl implements StoreService {
public StoreResponse updateStore(Long storeId, StoreUpdateRequest request) {
Store store = storeRepository.findById(storeId)
.orElseThrow(() -> new BusinessException(ErrorCode.STORE_NOT_FOUND));
// 매장 정보 업데이트
store.updateStoreInfo(
store.updateStore(
request.getStoreName(),
request.getStoreImage(),
request.getBusinessType(),
request.getAddress(),
request.getPhoneNumber(),
request.getInstaAccount(),
request.getNaverBlogAccount(),
request.getOpenTime(),
request.getCloseTime(),
request.getBusinessHours(),
request.getClosedDays(),
request.getSeatCount()
request.getSeatCount(),
request.getSnsAccounts(),
request.getDescription()
);
Store updatedStore = storeRepository.save(store);
log.info("매장 정보 수정 완료: {} (ID: {})", updatedStore.getStoreName(), updatedStore.getId());
return toStoreResponse(updatedStore);
}
@@ -112,19 +141,25 @@ public class StoreServiceImpl implements StoreService {
return StoreResponse.builder()
.storeId(store.getId())
.storeName(store.getStoreName())
.storeImage(store.getStoreImage())
.businessType(store.getBusinessType())
.address(store.getAddress())
.phoneNumber(store.getPhoneNumber())
.businessNumber(store.getBusinessNumber())
.instaAccount(store.getInstaAccount())
.naverBlogAccount(store.getNaverBlogAccount())
.openTime(store.getOpenTime())
.closeTime(store.getCloseTime())
.businessHours(store.getBusinessHours())
.closedDays(store.getClosedDays())
.seatCount(store.getSeatCount())
.snsAccounts(store.getSnsAccounts())
.description(store.getDescription())
.createdAt(store.getCreatedAt())
.updatedAt(store.getUpdatedAt())
.build();
}
/**
* 현재 로그인된 사용자 ID 조회
*
* @return 사용자 ID
*/
private String getCurrentUserId() {
return SecurityContextHolder.getContext().getAuthentication().getName();
}
}