Skip to content

Commit 311bc75

Browse files
authored
[BACKEND] 견적 생성/수정 로직 개선 및 불필요한 API 제거 (#93)
- 임시 견적 관리를 프론트엔드 로컬 스토리지로 이동 - POST /quotes를 최종 저장용으로 변경 - saved, quantity 필드 제거 - 아이템 추가/삭제/수량 조정 API 제거 - 견적 수정 시 전체 수정 방식으로 변경 - 요청/응답 구조를 표준 배열 형식으로 통일
1 parent 9aa6b19 commit 311bc75

File tree

13 files changed

+162
-297
lines changed

13 files changed

+162
-297
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ public enum ErrorCode {
4040
QUOTE_ITEM_NOT_FOUND(404, "QUOTE-002", "견적 항목을 찾을 수 없습니다."),
4141
QUOTE_ACCESS_DENIED(403, "QUOTE-003", "견적에 대한 접근 권한이 없습니다."),
4242
QUOTE_NAME_REQUIRED(400, "QUOTE-004", "견적 이름이 필요합니다."),
43+
QUOTE_DUPLICATE_CATEGORY(400, "QUOTE-005", "동일한 카테고리의 아이템이 중복되었습니다."),
4344

4445
// 검색 기록
4546
HISTORY_NOT_FOUND(404, "HISTORY-001", "검색 기록을 찾을 수 없습니다."),

backend/src/main/java/com/cmg/comtogether/quote/controller/QuoteController.java

Lines changed: 13 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,10 @@
22

33
import com.cmg.comtogether.common.response.ApiResponse;
44
import com.cmg.comtogether.common.security.CustomUserDetails;
5-
import com.cmg.comtogether.quote.dto.AddQuoteItemRequestDto;
6-
import com.cmg.comtogether.quote.dto.QuoteItemResponseDto;
5+
import com.cmg.comtogether.quote.dto.CreateQuoteRequestDto;
76
import com.cmg.comtogether.quote.dto.QuoteResponseDto;
87
import com.cmg.comtogether.quote.dto.QuoteSummaryDto;
9-
import com.cmg.comtogether.quote.dto.SaveQuoteRequestDto;
10-
import com.cmg.comtogether.quote.dto.UpdateQuoteItemQuantityRequestDto;
8+
import com.cmg.comtogether.quote.dto.UpdateQuoteRequestDto;
119
import com.cmg.comtogether.quote.service.QuoteService;
1210
import com.cmg.comtogether.user.entity.User;
1311
import jakarta.validation.Valid;
@@ -16,7 +14,6 @@
1614
import org.springframework.security.core.annotation.AuthenticationPrincipal;
1715
import org.springframework.web.bind.annotation.DeleteMapping;
1816
import org.springframework.web.bind.annotation.GetMapping;
19-
import org.springframework.web.bind.annotation.PatchMapping;
2017
import org.springframework.web.bind.annotation.PathVariable;
2118
import org.springframework.web.bind.annotation.PostMapping;
2219
import org.springframework.web.bind.annotation.PutMapping;
@@ -34,31 +31,32 @@ public class QuoteController {
3431
private final QuoteService quoteService;
3532

3633
/**
37-
* 새 견적 생성 (초안 상태)
34+
* 새 견적 생성 (최종 저장)
3835
*/
3936
@PostMapping
4037
public ResponseEntity<ApiResponse<QuoteResponseDto>> createQuote(
41-
@AuthenticationPrincipal CustomUserDetails userDetails
38+
@AuthenticationPrincipal CustomUserDetails userDetails,
39+
@Valid @RequestBody CreateQuoteRequestDto requestDto
4240
) {
4341
User user = userDetails.getUser();
44-
QuoteResponseDto responseDto = quoteService.createQuote(user.getUserId());
42+
QuoteResponseDto responseDto = quoteService.createQuote(user.getUserId(), requestDto);
4543
return ResponseEntity.ok(ApiResponse.success(responseDto));
4644
}
4745

4846
/**
49-
* 저장된 견적 목록 조회
47+
* 견적 목록 조회
5048
*/
5149
@GetMapping
5250
public ResponseEntity<ApiResponse<List<QuoteSummaryDto>>> getQuotes(
5351
@AuthenticationPrincipal CustomUserDetails userDetails
5452
) {
5553
User user = userDetails.getUser();
56-
List<QuoteSummaryDto> response = quoteService.getSavedQuotes(user.getUserId());
54+
List<QuoteSummaryDto> response = quoteService.getAllQuotes(user.getUserId());
5755
return ResponseEntity.ok(ApiResponse.success(response));
5856
}
5957

6058
/**
61-
* 저장된 견적 단건 조회
59+
* 견적 단건 조회
6260
*/
6361
@GetMapping("/{quoteId:\\d+}")
6462
public ResponseEntity<ApiResponse<QuoteResponseDto>> getQuote(
@@ -71,72 +69,16 @@ public ResponseEntity<ApiResponse<QuoteResponseDto>> getQuote(
7169
}
7270

7371
/**
74-
* 견적에 상품 추가
75-
*/
76-
@PostMapping("/{quoteId:\\d+}/items")
77-
public ResponseEntity<ApiResponse<QuoteItemResponseDto>> addItem(
78-
@AuthenticationPrincipal CustomUserDetails userDetails,
79-
@PathVariable Long quoteId,
80-
@Valid @RequestBody AddQuoteItemRequestDto requestDto
81-
) {
82-
User user = userDetails.getUser();
83-
QuoteItemResponseDto responseDto = quoteService.addItem(user.getUserId(), quoteId, requestDto);
84-
return ResponseEntity.ok(ApiResponse.success(responseDto));
85-
}
86-
87-
/**
88-
* 견적 상품 수량 수정
89-
*/
90-
@PatchMapping("/{quoteId:\\d+}/items/{quoteItemId}")
91-
public ResponseEntity<ApiResponse<QuoteItemResponseDto>> updateItemQuantity(
92-
@AuthenticationPrincipal CustomUserDetails userDetails,
93-
@PathVariable Long quoteId,
94-
@PathVariable Long quoteItemId,
95-
@Valid @RequestBody UpdateQuoteItemQuantityRequestDto requestDto
96-
) {
97-
User user = userDetails.getUser();
98-
QuoteItemResponseDto responseDto = quoteService.updateItemQuantity(user.getUserId(), quoteId, quoteItemId, requestDto.getQuantity());
99-
return ResponseEntity.ok(ApiResponse.success(responseDto));
100-
}
101-
102-
/**
103-
* 견적 저장 (이름 지정 필수)
72+
* 견적 수정 (전체 수정)
10473
*/
10574
@PutMapping("/{quoteId:\\d+}")
106-
public ResponseEntity<ApiResponse<QuoteResponseDto>> saveQuote(
107-
@AuthenticationPrincipal CustomUserDetails userDetails,
108-
@PathVariable Long quoteId,
109-
@Valid @RequestBody SaveQuoteRequestDto requestDto
110-
) {
111-
User user = userDetails.getUser();
112-
QuoteResponseDto responseDto = quoteService.saveQuote(user.getUserId(), quoteId, requestDto.getName());
113-
return ResponseEntity.ok(ApiResponse.success(responseDto));
114-
}
115-
116-
/**
117-
* 견적에서 특정 상품 삭제
118-
*/
119-
@DeleteMapping("/{quoteId:\\d+}/items/{quoteItemId}")
120-
public ResponseEntity<ApiResponse<QuoteResponseDto>> removeItem(
75+
public ResponseEntity<ApiResponse<QuoteResponseDto>> updateQuote(
12176
@AuthenticationPrincipal CustomUserDetails userDetails,
12277
@PathVariable Long quoteId,
123-
@PathVariable Long quoteItemId
124-
) {
125-
User user = userDetails.getUser();
126-
QuoteResponseDto responseDto = quoteService.removeItem(user.getUserId(), quoteId, quoteItemId);
127-
return ResponseEntity.ok(ApiResponse.success(responseDto));
128-
}
129-
130-
/**
131-
* 견적의 모든 상품 삭제
132-
*/
133-
@DeleteMapping("/{quoteId:\\d+}/items")
134-
public ResponseEntity<ApiResponse<QuoteResponseDto>> clearQuote(
135-
@AuthenticationPrincipal CustomUserDetails userDetails,
136-
@PathVariable Long quoteId
78+
@Valid @RequestBody UpdateQuoteRequestDto requestDto
13779
) {
13880
User user = userDetails.getUser();
139-
QuoteResponseDto responseDto = quoteService.clearQuote(user.getUserId(), quoteId);
81+
QuoteResponseDto responseDto = quoteService.updateQuote(user.getUserId(), quoteId, requestDto);
14082
return ResponseEntity.ok(ApiResponse.success(responseDto));
14183
}
14284

backend/src/main/java/com/cmg/comtogether/quote/dto/AddQuoteItemRequestDto.java

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package com.cmg.comtogether.quote.dto;
22

33
import com.fasterxml.jackson.annotation.JsonProperty;
4-
import jakarta.validation.constraints.Min;
54
import jakarta.validation.constraints.NotBlank;
65
import jakarta.validation.constraints.NotNull;
76
import lombok.AllArgsConstructor;
@@ -50,8 +49,5 @@ public class AddQuoteItemRequestDto {
5049
private String category3;
5150
@JsonProperty("category4")
5251
private String category4;
53-
54-
@Min(value = 1, message = "수량은 1 이상이어야 합니다.")
55-
private Integer quantity = 1;
5652
}
5753

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.cmg.comtogether.quote.dto;
2+
3+
import jakarta.validation.Valid;
4+
import jakarta.validation.constraints.NotBlank;
5+
import jakarta.validation.constraints.NotEmpty;
6+
import lombok.AllArgsConstructor;
7+
import lombok.Getter;
8+
import lombok.NoArgsConstructor;
9+
10+
import java.util.List;
11+
12+
@Getter
13+
@NoArgsConstructor
14+
@AllArgsConstructor
15+
public class CreateQuoteRequestDto {
16+
17+
@NotBlank(message = "견적서 이름은 필수입니다.")
18+
private String name;
19+
20+
@NotEmpty(message = "견적 아이템 목록은 필수입니다.")
21+
@Valid
22+
private List<AddQuoteItemRequestDto> items;
23+
}
24+

backend/src/main/java/com/cmg/comtogether/quote/dto/QuoteItemResponseDto.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,10 @@ public class QuoteItemResponseDto {
3333
@JsonProperty("category2")
3434
private String category2;
3535
@JsonProperty("category3")
36-
private String category3; // 견적 슬롯으로 활용 (예: CPU, 메모리 등)
36+
private String category3; // 견적 카테고리로 활용 (예: CPU, 메모리 등)
3737
@JsonProperty("category4")
3838
private String category4;
3939

40-
private Integer quantity;
41-
42-
4340
private LocalDateTime createdAt;
4441

4542
public static QuoteItemResponseDto from(QuoteItem quoteItem) {
@@ -58,7 +55,6 @@ public static QuoteItemResponseDto from(QuoteItem quoteItem) {
5855
.category2(quoteItem.getCategory2())
5956
.category3(quoteItem.getCategory3())
6057
.category4(quoteItem.getCategory4())
61-
.quantity(quoteItem.getQuantity())
6258
.createdAt(quoteItem.getCreatedAt())
6359
.build();
6460
}

backend/src/main/java/com/cmg/comtogether/quote/dto/QuoteResponseDto.java

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
public class QuoteResponseDto {
1818
private Long quoteId;
1919
private String name;
20-
private boolean saved;
2120
private List<QuoteItemResponseDto> items;
2221
private LocalDateTime createdAt;
2322
private LocalDateTime updatedAt;
@@ -31,17 +30,14 @@ public static QuoteResponseDto from(Quote quote) {
3130

3231
Integer totalPrice = quote.getItems().stream()
3332
.filter(item -> item.getLprice() != null)
34-
.map(item -> item.getLprice() * (item.getQuantity() == null ? 1 : item.getQuantity()))
33+
.map(item -> item.getLprice())
3534
.reduce(0, Integer::sum);
3635

37-
Integer totalQuantity = quote.getItems().stream()
38-
.map(item -> item.getQuantity() == null ? 1 : item.getQuantity())
39-
.reduce(0, Integer::sum);
36+
Integer totalQuantity = quote.getItems().size();
4037

4138
return QuoteResponseDto.builder()
4239
.quoteId(quote.getQuoteId())
4340
.name(quote.getName())
44-
.saved(quote.isSaved())
4541
.items(items)
4642
.createdAt(quote.getCreatedAt())
4743
.updatedAt(quote.getUpdatedAt())

backend/src/main/java/com/cmg/comtogether/quote/dto/QuoteSummaryDto.java

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,30 +16,21 @@ public class QuoteSummaryDto {
1616

1717
private Long quoteId;
1818
private String name;
19-
private boolean saved;
2019
private LocalDateTime createdAt;
2120
private LocalDateTime updatedAt;
2221
private Integer totalQuantity;
2322
private Integer totalPrice;
2423

2524
public static QuoteSummaryDto from(Quote quote) {
26-
int totalQuantity = quote.getItems() == null ? 0 :
27-
quote.getItems().stream()
28-
.map(item -> item.getQuantity() == null ? 1 : item.getQuantity())
29-
.reduce(0, Integer::sum);
25+
int totalQuantity = quote.getItems() == null ? 0 : quote.getItems().size();
3026
int totalPrice = quote.getItems() == null ? 0 :
3127
quote.getItems().stream()
32-
.map(item -> {
33-
int price = item.getLprice() == null ? 0 : item.getLprice();
34-
int quantity = item.getQuantity() == null ? 1 : item.getQuantity();
35-
return price * quantity;
36-
})
28+
.map(item -> item.getLprice() == null ? 0 : item.getLprice())
3729
.reduce(0, Integer::sum);
3830

3931
return QuoteSummaryDto.builder()
4032
.quoteId(quote.getQuoteId())
4133
.name(quote.getName())
42-
.saved(quote.isSaved())
4334
.createdAt(quote.getCreatedAt())
4435
.updatedAt(quote.getUpdatedAt())
4536
.totalQuantity(totalQuantity)
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package com.cmg.comtogether.quote.dto;
2+
3+
import jakarta.validation.Valid;
4+
import jakarta.validation.constraints.NotEmpty;
5+
import lombok.AllArgsConstructor;
6+
import lombok.Getter;
7+
import lombok.NoArgsConstructor;
8+
9+
import java.util.List;
10+
11+
@Getter
12+
@NoArgsConstructor
13+
@AllArgsConstructor
14+
public class UpdateQuoteRequestDto {
15+
16+
private String name; // 선택적: null이면 기존 이름 유지, 값이 있으면 업데이트
17+
18+
@NotEmpty(message = "견적 아이템 목록은 필수입니다.")
19+
@Valid
20+
private List<AddQuoteItemRequestDto> items;
21+
}
22+

backend/src/main/java/com/cmg/comtogether/quote/entity/Quote.java

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,6 @@ public class Quote {
2727
@Column(length = 100)
2828
private String name;
2929

30-
@Builder.Default
31-
@Column(nullable = false)
32-
private boolean saved = false;
33-
3430
@Builder.Default
3531
@Column(nullable = false, updatable = false)
3632
private LocalDateTime createdAt = LocalDateTime.now();
@@ -62,12 +58,6 @@ public void updateName(String name) {
6258
this.updatedAt = LocalDateTime.now();
6359
}
6460

65-
public void markSaved(String name) {
66-
this.name = name;
67-
this.saved = true;
68-
this.updatedAt = LocalDateTime.now();
69-
}
70-
7161
@PreUpdate
7262
public void preUpdate() {
7363
this.updatedAt = LocalDateTime.now();

backend/src/main/java/com/cmg/comtogether/quote/entity/QuoteItem.java

Lines changed: 1 addition & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -52,18 +52,14 @@ public class QuoteItem {
5252
private String category3;
5353
private String category4;
5454

55-
@Builder.Default
56-
@Column(nullable = false)
57-
private Integer quantity = 1;
58-
5955
@Builder.Default
6056
@Column(nullable = false, updatable = false)
6157
private LocalDateTime createdAt = LocalDateTime.now();
6258

6359
public QuoteItem(Quote quote, Long productId, String title, Integer lprice, Integer hprice,
6460
String image, String link, String mallName, String productType,
6561
String maker, String brand, String category1, String category2,
66-
String category3, String category4, Integer quantity) {
62+
String category3, String category4) {
6763
this.quote = quote;
6864
this.productId = productId;
6965
this.title = title;
@@ -79,20 +75,9 @@ public QuoteItem(Quote quote, Long productId, String title, Integer lprice, Inte
7975
this.category2 = category2;
8076
this.category3 = category3;
8177
this.category4 = category4;
82-
this.quantity = quantity == null ? 1 : quantity;
8378
this.createdAt = LocalDateTime.now();
8479
}
8580

86-
public void increaseQuantity(int amount) {
87-
if (amount <= 0) {
88-
return;
89-
}
90-
this.quantity = (this.quantity == null ? 0 : this.quantity) + amount;
91-
if (this.quote != null) {
92-
this.quote.touch();
93-
}
94-
}
95-
9681
public void updateDetails(Long productId, String title, Integer lprice, Integer hprice,
9782
String image, String link, String mallName, String productType,
9883
String maker, String brand, String category1, String category2,
@@ -115,16 +100,5 @@ public void updateDetails(Long productId, String title, Integer lprice, Integer
115100
this.quote.touch();
116101
}
117102
}
118-
119-
public Integer getQuantity() {
120-
return quantity == null ? 1 : quantity;
121-
}
122-
123-
public void setQuantity(Integer quantity) {
124-
this.quantity = quantity == null ? 1 : quantity;
125-
if (this.quote != null) {
126-
this.quote.touch();
127-
}
128-
}
129103
}
130104

0 commit comments

Comments
 (0)