Skip to content
This repository was archived by the owner on Apr 26, 2023. It is now read-only.

Commit 73a5bb6

Browse files
author
Félix Carpena
committed
discount codes (step3)
1 parent 01f077c commit 73a5bb6

File tree

9 files changed

+168
-10
lines changed

9 files changed

+168
-10
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package group.rohlik.application;
2+
3+
import group.rohlik.entity.Cart;
4+
import group.rohlik.entity.CartRepository;
5+
import group.rohlik.entity.Discount;
6+
import group.rohlik.entity.DiscountRepository;
7+
import lombok.AllArgsConstructor;
8+
import org.springframework.stereotype.Service;
9+
import org.springframework.transaction.annotation.Transactional;
10+
11+
@AllArgsConstructor
12+
@Service
13+
@Transactional
14+
public class CartDiscountAdder {
15+
16+
private final CartRepository cartRepository;
17+
private final DiscountRepository discountRepository;
18+
19+
public void add(long cartId, String code) {
20+
Cart cart = cartRepository.findById(cartId).orElseThrow();
21+
Discount discount = discountRepository.findByCode(code).orElseThrow();
22+
cart.applyDiscount(discount);
23+
24+
cartRepository.save(cart);
25+
}
26+
}

src/main/java/group/rohlik/application/CartLineAdder.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public void add(long cartId, String sku, int quantity) {
2222
Cart cart = cartRepository.findById(cartId).orElseThrow();
2323
Product product = productRepository.findById(sku).orElseThrow();
2424
cart.addLine(product, quantity);
25-
cart.recalculateDiscounts(discountRepository.findAll());
25+
cart.recalculateAutomaticDiscounts(discountRepository.findAllAutomatics());
2626

2727
cartRepository.save(cart);
2828
}

src/main/java/group/rohlik/controller/CartController.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package group.rohlik.controller;
22

33
import com.fasterxml.jackson.databind.JsonNode;
4+
import group.rohlik.application.CartDiscountAdder;
45
import group.rohlik.application.CartLineAdder;
56
import lombok.AllArgsConstructor;
67
import org.springframework.http.HttpStatus;
@@ -15,6 +16,7 @@
1516
public class CartController {
1617

1718
private final CartLineAdder cartLineAdder;
19+
private final CartDiscountAdder cartDiscountAdder;
1820

1921
@PostMapping(path = "/carts/{id}/lines", consumes = "application/json", produces = "application/json")
2022
public ResponseEntity addLine(@PathVariable long id, @RequestBody JsonNode payload) {
@@ -25,4 +27,13 @@ public ResponseEntity addLine(@PathVariable long id, @RequestBody JsonNode paylo
2527

2628
return new ResponseEntity<>(HttpStatus.OK);
2729
}
30+
31+
@PostMapping(path = "/carts/{id}/discounts", consumes = "application/json", produces = "application/json")
32+
public ResponseEntity addDiscount(@PathVariable long id, @RequestBody JsonNode payload) {
33+
String code = payload.get("code").textValue();
34+
35+
cartDiscountAdder.add(id, code);
36+
37+
return new ResponseEntity<>(HttpStatus.OK);
38+
}
2839
}

src/main/java/group/rohlik/entity/Cart.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,12 +71,19 @@ private double totalLinesPrice() {
7171
.sum();
7272
}
7373

74-
public void recalculateDiscounts(List<Discount> discounts) {
75-
this.discounts.clear();
74+
public void recalculateAutomaticDiscounts(List<Discount> discounts) {
75+
this.discounts.stream().filter(Discount::isAutomatic).forEach(this.discounts::remove);
7676
double price = totalLinesPrice();
7777
discounts
7878
.stream()
79+
.filter(Discount::isAutomatic)
7980
.filter(discount -> discount.getMinPrice() < price)
8081
.forEach(this.discounts::add);
8182
}
83+
public void applyDiscount(Discount discount) {
84+
Assert.isTrue(!discount.isAutomatic(), "Discount can not be of type automatic");
85+
Assert.isTrue(!discounts.contains(discount), "Discount already applied");
86+
87+
discounts.add(discount);
88+
}
8289
}

src/main/java/group/rohlik/entity/Discount.java

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package group.rohlik.entity;
22

3-
import lombok.Data;
3+
import lombok.Getter;
44
import lombok.NoArgsConstructor;
55

66
import javax.persistence.Column;
@@ -10,9 +10,10 @@
1010
import javax.persistence.Id;
1111

1212
@Entity(name = "discounts")
13-
@Data
13+
@Getter
1414
@NoArgsConstructor
1515
public class Discount {
16+
1617
@Id
1718
@GeneratedValue(strategy = GenerationType.IDENTITY)
1819
private long id;
@@ -22,4 +23,37 @@ public class Discount {
2223
private float minPrice;
2324
@Column(nullable = false)
2425
private float amount;
26+
@Column
27+
private String code;
28+
private DiscountType type;
29+
30+
public enum DiscountType {
31+
CODE, MIN_PRICE
32+
}
33+
34+
public static Discount minimumPriceDiscount(String name, float amount, float minPrice) {
35+
Discount discount = new Discount();
36+
discount.name = name;
37+
discount.amount = amount;
38+
discount.minPrice = minPrice;
39+
discount.code = null;
40+
discount.type = DiscountType.MIN_PRICE;
41+
42+
return discount;
43+
}
44+
45+
public static Discount codeDiscount(String name, float amount, String code) {
46+
Discount discount = new Discount();
47+
discount.name = name;
48+
discount.amount = amount;
49+
discount.minPrice = 0;
50+
discount.code = code;
51+
discount.type = DiscountType.CODE;
52+
53+
return discount;
54+
}
55+
56+
public boolean isAutomatic() {
57+
return !type.equals(DiscountType.CODE);
58+
}
2559
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
package group.rohlik.entity;
22

33
import org.springframework.data.jpa.repository.JpaRepository;
4+
import org.springframework.data.jpa.repository.Query;
5+
6+
import java.util.List;
7+
import java.util.Optional;
48

59
public interface DiscountRepository extends JpaRepository<Discount, Long> {
10+
11+
Optional<Discount> findByCode(String code);
12+
13+
@Query("SELECT d FROM discounts d WHERE d.type > 0")
14+
List<Discount> findAllAutomatics();
615
}

src/test/java/group/rohlik/acceptance/steps/CartSteps.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,12 +109,38 @@ public void thereShouldBeDiscountInCart(long discountId) {
109109
Assertions.assertNotNull(discount, String.format("Discount %s should be present", discountId));
110110
}
111111

112+
@Then("there should be discount {string} in my cart")
113+
public void thereShouldBeDiscountByNameInCart(String name) {
114+
Cart cart = currentCart();
115+
Discount discount = cart.getDiscounts()
116+
.stream()
117+
.filter(currentDiscount -> currentDiscount.getName().equals(name))
118+
.findFirst()
119+
.orElse(null);
120+
Assertions.assertNotNull(discount, String.format("Discount %s should be present", name));
121+
}
122+
112123
@Then("there shouldn't be discounts in my cart")
113124
public void thereShouldNotBeDiscountsInCart() {
114125
Cart cart = currentCart();
115126
Assertions.assertEquals(0, cart.getDiscounts().size());
116127
}
117128

129+
@When("I apply {string} discount to my cart")
130+
public void iApplyDiscountToMyCart(String code) {
131+
JsonObject body = new JsonObject();
132+
body.add("code", code);
133+
HttpHeaders headers = new HttpHeaders();
134+
headers.setContentType(MediaType.APPLICATION_JSON);
135+
136+
template.exchange(
137+
String.format("/carts/%d/discounts", currentCartId),
138+
HttpMethod.POST,
139+
new HttpEntity<>(body.toString(), headers),
140+
String.class
141+
);
142+
}
143+
118144
private Cart currentCart() {
119145
return cartRepository.findById(currentCartId).orElseThrow();
120146
}

src/test/java/group/rohlik/acceptance/steps/DiscountSteps.java

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,26 @@
22

33
import group.rohlik.entity.Discount;
44
import group.rohlik.entity.DiscountRepository;
5-
import io.cucumber.java.en.And;
65
import io.cucumber.java.en.Given;
76
import lombok.AllArgsConstructor;
87
import org.springframework.transaction.annotation.Transactional;
98

109
@AllArgsConstructor
1110
public class DiscountSteps {
11+
1212
private final DiscountRepository discountRepository;
1313

1414
@Given("there is a cart discount {string} for {float} euros with a minimum total price of {float} euros")
1515
@Transactional
1616
public void thereIsACartDiscountWithAMinimumTotalPrice(String name, float amount, float minPrice) {
17-
Discount discount = new Discount();
18-
discount.setName(name);
19-
discount.setAmount(amount);
20-
discount.setMinPrice(minPrice);
17+
Discount discount = Discount.minimumPriceDiscount(name, amount, minPrice);
18+
discountRepository.save(discount);
19+
}
20+
21+
@Given("there is a cart discount {string} for {float} euros with code {string}")
22+
@Transactional
23+
public void thereIsACartDiscountWithCode(String name, float amount, String code) {
24+
Discount discount = Discount.codeDiscount(name, amount, code);
2125
discountRepository.save(discount);
2226
}
2327
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
Feature: Add discount to cart
2+
As a Rohlik customer
3+
I want to apply discounts to my cart
4+
so that I can save some money
5+
6+
Background:
7+
Given the following products exist:
8+
| sku | name | price |
9+
| 001 | potatoes | 2.5 |
10+
| 002 | water | 0.95 |
11+
And there is a cart discount "free shipping" for 1 euros with code "FREE-SHIPPING"
12+
And there is a cart discount "Amazing discount" for 1 euros with a minimum total price of 6 euros
13+
And I have a cart
14+
15+
Scenario: Add discount
16+
Given I add 2 units of product "001" to my cart
17+
When I apply "FREE-SHIPPING" discount to my cart
18+
Then the cart's total cost should be 4.0 euros
19+
And there should be discount "free shipping" in my cart
20+
21+
Scenario: Add discount only apply once
22+
Given I add 2 units of product "001" to my cart
23+
And I apply "FREE-SHIPPING" discount to my cart
24+
When I apply "FREE-SHIPPING" discount to my cart
25+
Then the cart's total cost should be 4.0 euros
26+
And there should be discount "free shipping" in my cart
27+
28+
Scenario: Mix automatic and code discounts
29+
Given I add 3 units of product "001" to my cart
30+
When I apply "FREE-SHIPPING" discount to my cart
31+
Then the cart's total cost should be 5.5 euros
32+
And there should be discount "free shipping" in my cart
33+
And there should be discount "Amazing discount" in my cart
34+
35+
Scenario: Code discounts are no recalculated
36+
Given I add 2 units of product "001" to my cart
37+
And I add 2 units of product "002" to my cart
38+
And I apply "FREE-SHIPPING" discount to my cart
39+
When I remove product "002" of my cart
40+
Then the cart's total cost should be 4.0 euros
41+
And there should be discount "free shipping" in my cart

0 commit comments

Comments
 (0)