Skip to content

Commit a23b242

Browse files
authored
[BACKEND] 견적 생성/추가/삭제/조회 기능 추가 (#85)
- 견적 생성/추가/삭제/조회 기능 추가 - 견적 서비스단/DTO단 수정 * 여러 견적 저장 및 관리할 수 있게끔 디벨롭 예정
1 parent 981572a commit a23b242

File tree

10 files changed

+543
-1
lines changed

10 files changed

+543
-1
lines changed

backend/src/main/java/com/cmg/comtogether/common/exception/ErrorCode.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,12 @@ public enum ErrorCode {
3333
GUIDE_NOT_FOUND(404, "GUIDE-001", "가이드를 찾을 수 없습니다"),
3434

3535
// 용어사전
36-
WORD_NOT_FOUND(404, "GLOSSARY-001", "해당 용어를 찾을 수 없습니다.");
36+
WORD_NOT_FOUND(404, "GLOSSARY-001", "해당 용어를 찾을 수 없습니다."),
37+
38+
// 견적
39+
QUOTE_NOT_FOUND(404, "QUOTE-001", "견적을 찾을 수 없습니다."),
40+
QUOTE_ITEM_NOT_FOUND(404, "QUOTE-002", "견적 항목을 찾을 수 없습니다."),
41+
QUOTE_ACCESS_DENIED(403, "QUOTE-003", "견적에 대한 접근 권한이 없습니다.");
3742

3843
private final int status;
3944
private final String code;
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package com.cmg.comtogether.quote.controller;
2+
3+
import com.cmg.comtogether.common.response.ApiResponse;
4+
import com.cmg.comtogether.common.security.CustomUserDetails;
5+
import com.cmg.comtogether.quote.dto.AddQuoteItemRequestDto;
6+
import com.cmg.comtogether.quote.dto.QuoteItemResponseDto;
7+
import com.cmg.comtogether.quote.dto.QuoteResponseDto;
8+
import com.cmg.comtogether.quote.service.QuoteService;
9+
import com.cmg.comtogether.user.entity.User;
10+
import jakarta.validation.Valid;
11+
import lombok.RequiredArgsConstructor;
12+
import org.springframework.http.ResponseEntity;
13+
import org.springframework.security.core.annotation.AuthenticationPrincipal;
14+
import org.springframework.web.bind.annotation.*;
15+
16+
@RestController
17+
@RequestMapping("/quotes")
18+
@RequiredArgsConstructor
19+
public class QuoteController {
20+
21+
private final QuoteService quoteService;
22+
23+
/**
24+
* 현재 견적 조회
25+
* GET /quotes
26+
*/
27+
@GetMapping
28+
public ResponseEntity<ApiResponse<QuoteResponseDto>> getCurrentQuote(
29+
@AuthenticationPrincipal CustomUserDetails userDetails
30+
) {
31+
User user = userDetails.getUser();
32+
QuoteResponseDto responseDto = quoteService.getCurrentQuote(user.getUserId());
33+
return ResponseEntity.ok(ApiResponse.success(responseDto));
34+
}
35+
36+
/**
37+
* 견적에 상품 추가
38+
* POST /quotes/items
39+
*/
40+
@PostMapping("/items")
41+
public ResponseEntity<ApiResponse<QuoteItemResponseDto>> addItem(
42+
@AuthenticationPrincipal CustomUserDetails userDetails,
43+
@Valid @RequestBody AddQuoteItemRequestDto requestDto
44+
) {
45+
User user = userDetails.getUser();
46+
QuoteItemResponseDto responseDto = quoteService.addItem(user.getUserId(), requestDto);
47+
return ResponseEntity.ok(ApiResponse.success(responseDto));
48+
}
49+
50+
/**
51+
* 견적에서 상품 삭제
52+
* DELETE /quotes/items/{quoteItemId}
53+
*/
54+
@DeleteMapping("/items/{quoteItemId}")
55+
public ResponseEntity<ApiResponse<QuoteResponseDto>> removeItem(
56+
@AuthenticationPrincipal CustomUserDetails userDetails,
57+
@PathVariable Long quoteItemId
58+
) {
59+
User user = userDetails.getUser();
60+
QuoteResponseDto responseDto = quoteService.removeItem(user.getUserId(), quoteItemId);
61+
return ResponseEntity.ok(ApiResponse.success(responseDto));
62+
}
63+
64+
/**
65+
* 견적 전체 비우기
66+
* DELETE /quotes
67+
*/
68+
@DeleteMapping
69+
public ResponseEntity<ApiResponse<QuoteResponseDto>> clearQuote(
70+
@AuthenticationPrincipal CustomUserDetails userDetails
71+
) {
72+
User user = userDetails.getUser();
73+
QuoteResponseDto responseDto = quoteService.clearQuote(user.getUserId());
74+
return ResponseEntity.ok(ApiResponse.success(responseDto));
75+
}
76+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package com.cmg.comtogether.quote.dto;
2+
3+
import jakarta.validation.constraints.NotBlank;
4+
import jakarta.validation.constraints.NotNull;
5+
import lombok.AllArgsConstructor;
6+
import lombok.Getter;
7+
import lombok.NoArgsConstructor;
8+
9+
@Getter
10+
@NoArgsConstructor
11+
@AllArgsConstructor
12+
public class AddQuoteItemRequestDto {
13+
14+
@NotNull(message = "상품 ID는 필수입니다.")
15+
private Long productId;
16+
17+
@NotBlank(message = "상품명은 필수입니다.")
18+
private String title;
19+
20+
@NotNull(message = "최저가는 필수입니다.")
21+
private Integer lprice;
22+
23+
private Integer hprice;
24+
25+
private String image;
26+
27+
@NotBlank(message = "링크는 필수입니다.")
28+
private String link;
29+
30+
@NotBlank(message = "몰명은 필수입니다.")
31+
private String mallName;
32+
33+
private String productType;
34+
35+
private String maker;
36+
37+
private String brand;
38+
39+
// 네이버 쇼핑 카테고리(대분류~세분류)
40+
private String category1;
41+
private String category2;
42+
private String category3;
43+
private String category4;
44+
}
45+
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package com.cmg.comtogether.quote.dto;
2+
3+
import com.cmg.comtogether.quote.entity.QuoteItem;
4+
import lombok.AllArgsConstructor;
5+
import lombok.Builder;
6+
import lombok.Getter;
7+
import lombok.NoArgsConstructor;
8+
9+
import java.time.LocalDateTime;
10+
11+
@Getter
12+
@NoArgsConstructor
13+
@AllArgsConstructor
14+
@Builder
15+
public class QuoteItemResponseDto {
16+
private Long quoteItemId;
17+
private Long productId;
18+
private String title;
19+
private Integer lprice;
20+
private Integer hprice;
21+
private String image;
22+
private String link;
23+
private String mallName;
24+
private String productType;
25+
private String brand;
26+
private String category1;
27+
private String category2;
28+
private String category3; // 견적 슬롯으로 활용 (예: CPU, 메모리 등)
29+
private String category4;
30+
31+
32+
private LocalDateTime createdAt;
33+
34+
public static QuoteItemResponseDto from(QuoteItem quoteItem) {
35+
return QuoteItemResponseDto.builder()
36+
.quoteItemId(quoteItem.getQuoteItemId())
37+
.productId(quoteItem.getProductId())
38+
.title(quoteItem.getTitle())
39+
.lprice(quoteItem.getLprice())
40+
.hprice(quoteItem.getHprice())
41+
.image(quoteItem.getImage())
42+
.link(quoteItem.getLink())
43+
.mallName(quoteItem.getMallName())
44+
.productType(quoteItem.getProductType())
45+
.brand(quoteItem.getBrand())
46+
.category1(quoteItem.getCategory1())
47+
.category2(quoteItem.getCategory2())
48+
.category3(quoteItem.getCategory3())
49+
.category4(quoteItem.getCategory4())
50+
.createdAt(quoteItem.getCreatedAt())
51+
.build();
52+
}
53+
}
54+
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package com.cmg.comtogether.quote.dto;
2+
3+
import com.cmg.comtogether.quote.entity.Quote;
4+
import com.cmg.comtogether.quote.entity.QuoteItem;
5+
import lombok.AllArgsConstructor;
6+
import lombok.Builder;
7+
import lombok.Getter;
8+
import lombok.NoArgsConstructor;
9+
10+
import java.time.LocalDateTime;
11+
import java.util.List;
12+
import java.util.stream.Collectors;
13+
14+
@Getter
15+
@NoArgsConstructor
16+
@AllArgsConstructor
17+
@Builder
18+
public class QuoteResponseDto {
19+
private Long quoteId;
20+
private List<QuoteItemResponseDto> items;
21+
private LocalDateTime createdAt;
22+
private LocalDateTime updatedAt;
23+
private Integer totalPrice; //최저가(lprice) 합계
24+
private Integer itemCount;
25+
26+
public static QuoteResponseDto from(Quote quote) {
27+
List<QuoteItemResponseDto> items = quote.getItems().stream()
28+
.map(QuoteItemResponseDto::from)
29+
.collect(Collectors.toList());
30+
31+
Integer totalPrice = quote.getItems().stream()
32+
.map(QuoteItem::getLprice)
33+
.filter(price -> price != null)
34+
.reduce(0, Integer::sum);
35+
36+
return QuoteResponseDto.builder()
37+
.quoteId(quote.getQuoteId())
38+
.items(items)
39+
.createdAt(quote.getCreatedAt())
40+
.updatedAt(quote.getUpdatedAt())
41+
.totalPrice(totalPrice)
42+
.itemCount(items.size())
43+
.build();
44+
}
45+
}
46+
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package com.cmg.comtogether.quote.entity;
2+
3+
import com.cmg.comtogether.user.entity.User;
4+
import jakarta.persistence.*;
5+
import lombok.*;
6+
7+
import java.time.LocalDateTime;
8+
import java.util.ArrayList;
9+
import java.util.List;
10+
11+
@Entity
12+
@Table(name = "quotes")
13+
@Getter
14+
@NoArgsConstructor
15+
@AllArgsConstructor
16+
@Builder
17+
public class Quote {
18+
19+
@Id
20+
@GeneratedValue(strategy = GenerationType.IDENTITY)
21+
private Long quoteId;
22+
23+
@ManyToOne(fetch = FetchType.LAZY)
24+
@JoinColumn(name = "user_id", nullable = false)
25+
private User user;
26+
27+
@Builder.Default
28+
@Column(nullable = false, updatable = false)
29+
private LocalDateTime createdAt = LocalDateTime.now();
30+
31+
@Builder.Default
32+
@Column(nullable = false)
33+
private LocalDateTime updatedAt = LocalDateTime.now();
34+
35+
@OneToMany(mappedBy = "quote", cascade = CascadeType.ALL, orphanRemoval = true)
36+
@Builder.Default
37+
private List<QuoteItem> items = new ArrayList<>();
38+
39+
public void addItem(QuoteItem item) {
40+
this.items.add(item);
41+
this.updatedAt = LocalDateTime.now();
42+
}
43+
44+
public void removeItem(QuoteItem item) {
45+
this.items.remove(item);
46+
this.updatedAt = LocalDateTime.now();
47+
}
48+
49+
@PreUpdate
50+
public void preUpdate() {
51+
this.updatedAt = LocalDateTime.now();
52+
}
53+
}
54+
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package com.cmg.comtogether.quote.entity;
2+
3+
import jakarta.persistence.*;
4+
import lombok.*;
5+
6+
import java.time.LocalDateTime;
7+
8+
@Entity
9+
@Table(name = "quote_items")
10+
@Getter
11+
@NoArgsConstructor
12+
@AllArgsConstructor
13+
@Builder
14+
public class QuoteItem {
15+
16+
@Id
17+
@GeneratedValue(strategy = GenerationType.IDENTITY)
18+
private Long quoteItemId;
19+
20+
@ManyToOne(fetch = FetchType.LAZY)
21+
@JoinColumn(name = "quote_id", nullable = false)
22+
private Quote quote;
23+
24+
@Column(nullable = false)
25+
private Long productId;
26+
27+
@Column(nullable = false, length = 500)
28+
private String title;
29+
30+
@Column(nullable = false)
31+
private Integer lprice;
32+
33+
private Integer hprice;
34+
35+
private String image;
36+
37+
@Column(nullable = false, length = 1000)
38+
private String link;
39+
40+
@Column(nullable = false)
41+
private String mallName;
42+
43+
private String productType;
44+
45+
private String maker;
46+
47+
private String brand;
48+
49+
// 네이버 쇼핑 카테고리(대분류~세분류)
50+
private String category1;
51+
private String category2;
52+
private String category3;
53+
private String category4;
54+
55+
@Builder.Default
56+
@Column(nullable = false, updatable = false)
57+
private LocalDateTime createdAt = LocalDateTime.now();
58+
59+
public QuoteItem(Quote quote, Long productId, String title, Integer lprice, Integer hprice,
60+
String image, String link, String mallName, String productType,
61+
String maker, String brand, String category1, String category2,
62+
String category3, String category4) {
63+
this.quote = quote;
64+
this.productId = productId;
65+
this.title = title;
66+
this.lprice = lprice;
67+
this.hprice = hprice;
68+
this.image = image;
69+
this.link = link;
70+
this.mallName = mallName;
71+
this.productType = productType;
72+
this.maker = maker;
73+
this.brand = brand;
74+
this.category1 = category1;
75+
this.category2 = category2;
76+
this.category3 = category3;
77+
this.category4 = category4;
78+
this.createdAt = LocalDateTime.now();
79+
}
80+
}
81+
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.cmg.comtogether.quote.repository;
2+
3+
import com.cmg.comtogether.quote.entity.QuoteItem;
4+
import org.springframework.data.jpa.repository.JpaRepository;
5+
import org.springframework.stereotype.Repository;
6+
7+
@Repository
8+
public interface QuoteItemRepository extends JpaRepository<QuoteItem, Long> {
9+
}

0 commit comments

Comments
 (0)