Skip to content

Commit 72aac69

Browse files
Merge pull request #57 from UdL-EPS-SoftArch/product
feat: add orders relationship to Product and create ProductSummary pr…
2 parents 9e5ddf4 + 0b1a0f3 commit 72aac69

6 files changed

Lines changed: 124 additions & 35 deletions

File tree

src/main/java/cat/udl/eps/softarch/demo/bootstrap/ProductLoader.java

Lines changed: 49 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
package cat.udl.eps.softarch.demo.bootstrap;
22

3+
import cat.udl.eps.softarch.demo.domain.Business;
34
import cat.udl.eps.softarch.demo.domain.Category;
5+
import cat.udl.eps.softarch.demo.domain.Inventory;
46
import cat.udl.eps.softarch.demo.domain.Product;
5-
import cat.udl.eps.softarch.demo.repository.CategoryRepository; // <--- Import nou
7+
import cat.udl.eps.softarch.demo.repository.BusinessRepository;
8+
import cat.udl.eps.softarch.demo.repository.CategoryRepository;
9+
import cat.udl.eps.softarch.demo.repository.InventoryRepository;
610
import cat.udl.eps.softarch.demo.repository.ProductRepository;
711
import lombok.RequiredArgsConstructor;
812
import org.springframework.boot.CommandLineRunner;
@@ -19,7 +23,9 @@
1923
public class ProductLoader implements CommandLineRunner {
2024

2125
private final ProductRepository productRepository;
22-
private final CategoryRepository categoryRepository; // <--- Injectem el repo de categories
26+
private final CategoryRepository categoryRepository;
27+
private final BusinessRepository businessRepository;
28+
private final InventoryRepository inventoryRepository;
2329

2430
@Override
2531
public void run(String... args) throws Exception {
@@ -29,18 +35,41 @@ public void run(String... args) throws Exception {
2935
}
3036

3137
private void loadData() {
32-
// --- 1. CREAR I GUARDAR CATEGORIES ---
38+
39+
40+
Business mainBusiness = new Business();
41+
mainBusiness.setName("Lleida Coffee Shop");
42+
mainBusiness.setEmail("info@lleidacoffee.com");
43+
mainBusiness.setUsername("admin1");
44+
45+
46+
mainBusiness.setPassword("password123"); // Ara té 11 caràcters, ja passarà la validació
47+
mainBusiness.setAddress("Carrer Major, 1, Lleida");
48+
// ------------------------------------------------
49+
50+
businessRepository.save(mainBusiness);
51+
52+
Inventory mainInventory = new Inventory();
53+
mainInventory.setName("Main Warehouse");
54+
mainInventory.setDescription("Magatzem principal de la botiga");
55+
mainInventory.setLocation("Lleida, Rovira Roure 4");
56+
mainInventory.setTotalStock(1000);
57+
mainInventory.setBusiness(mainBusiness);
58+
59+
inventoryRepository.save(mainInventory);
60+
61+
System.out.println("📦 Created Main Inventory: " + mainInventory.getName());
62+
63+
3364
Category catCoffee = Category.builder().name("Specialty Coffee").description("Best beans in town").build();
3465
Category catFood = Category.builder().name("Food & Pastry").description("Freshly baked goods").build();
3566
Category catDrink = Category.builder().name("Drinks").description("Cold and refreshing").build();
3667
Category catAlt = Category.builder().name("Alternatives").description("Plant based options").build();
3768
Category catMerch = Category.builder().name("Merchandise").description("Gifts and accessories").build();
3869

39-
// Guardem les categories primer per tenir ID i poder associar-les
4070
categoryRepository.saveAll(Arrays.asList(catCoffee, catFood, catDrink, catAlt, catMerch));
4171

4272

43-
// --- 2. CREAR PRODUCTES AMB LA CATEGORIA ASSIGNADA ---
4473

4574
Product cafeEtiopia = Product.builder()
4675
.name("Ethiopia Yirgacheffe (250g)")
@@ -49,7 +78,8 @@ private void loadData() {
4978
.stock(20)
5079
.isAvailable(true)
5180
.brand("Nomad Coffee")
52-
.category(catCoffee) // <--- Assignem la categoria
81+
.category(catCoffee)
82+
.inventory(mainInventory)
5383
.barcode("8410000000010")
5484
.kcal(2)
5585
.ingredients(Set.of("100% Washed Arabica Coffee"))
@@ -66,7 +96,8 @@ private void loadData() {
6696
.stock(15)
6797
.isAvailable(true)
6898
.brand("Local Bakery")
69-
.category(catFood) // <--- Assignem la categoria
99+
.category(catFood)
100+
.inventory(mainInventory)
70101
.barcode("8410000000020")
71102
.kcal(280)
72103
.carbs(30)
@@ -86,7 +117,8 @@ private void loadData() {
86117
.stock(3)
87118
.isAvailable(true)
88119
.brand("MyCoffee House")
89-
.category(catDrink) // <--- Assignem la categoria
120+
.category(catDrink)
121+
.inventory(mainInventory)
90122
.barcode("8410000000030")
91123
.kcal(5)
92124
.ingredients(Set.of("Filtered water", "Coffee"))
@@ -103,7 +135,8 @@ private void loadData() {
103135
.stock(0)
104136
.isAvailable(true)
105137
.brand("Local Bakery")
106-
.category(catFood) // <--- Assignem la categoria
138+
.category(catFood)
139+
.inventory(mainInventory)
107140
.barcode("8410000000040")
108141
.kcal(350)
109142
.allergens(Set.of("Gluten", "Nuts", "Soy"))
@@ -118,7 +151,8 @@ private void loadData() {
118151
.stock(50)
119152
.isAvailable(true)
120153
.brand("Oatly")
121-
.category(catAlt) // <--- Assignem la categoria
154+
.category(catAlt)
155+
.inventory(mainInventory)
122156
.barcode("8410000000050")
123157
.kcal(60)
124158
.ingredients(Set.of("Water", "Oats", "Rapeseed oil"))
@@ -136,7 +170,8 @@ private void loadData() {
136170
.stock(10)
137171
.isAvailable(true)
138172
.brand("MyCoffee House")
139-
.category(catMerch) // <--- Assignem la categoria
173+
.category(catMerch)
174+
.inventory(mainInventory)
140175
.barcode("8410000000060")
141176
.rating(5.0)
142177
.isPartOfLoyaltyProgram(true)
@@ -146,8 +181,8 @@ private void loadData() {
146181

147182
productRepository.saveAll(Arrays.asList(cafeEtiopia, croissant, coldBrew, cookieXoc, oatMilk, giftPack));
148183

149-
System.out.println("------------------------------------------------");
150-
System.out.println("☕ COFFEE SHOP MENU LOADED WITH CATEGORIES ☕");
151-
System.out.println("------------------------------------------------");
184+
System.out.println("-------------------------------------------------------");
185+
System.out.println("☕ COFFEE SHOP MENU LOADED WITH CATEGORIES & INVENTORY ☕");
186+
System.out.println("-------------------------------------------------------");
152187
}
153188
}

src/main/java/cat/udl/eps/softarch/demo/controller/ProductController.java

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import cat.udl.eps.softarch.demo.domain.Product;
55
import cat.udl.eps.softarch.demo.repository.BusinessRepository;
66
import cat.udl.eps.softarch.demo.repository.ProductRepository;
7+
import org.springframework.data.rest.webmvc.RepositoryRestController;
78
import org.springframework.http.HttpStatus;
89
import org.springframework.http.ResponseEntity;
910
import org.springframework.security.access.AccessDeniedException;
@@ -16,8 +17,9 @@
1617
import java.util.List;
1718
import java.util.Map;
1819

19-
@RestController
20-
@RequestMapping("/products")
20+
// 1. Mantenim @RepositoryRestController
21+
@RepositoryRestController
22+
// 2. IMPORTANT: HEM ESBORRAT @RequestMapping("/products") D'AQUÍ
2123
public class ProductController {
2224

2325
private final ProductRepository productRepository;
@@ -28,7 +30,10 @@ public ProductController(ProductRepository productRepository, BusinessRepository
2830
this.businessRepository = businessRepository;
2931
}
3032

31-
@PutMapping("/{id}/stock")
33+
// 3. Afegim "/products" al path de cada mètode
34+
35+
@PutMapping("/products/{id}/stock") // <--- Canviat
36+
@ResponseBody
3237
public ResponseEntity<?> updateStock(@PathVariable Long id, @RequestBody Map<String, Integer> request) {
3338
Product product = productRepository.findById(id)
3439
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Product not found"));
@@ -54,14 +59,14 @@ public ResponseEntity<?> updateStock(@PathVariable Long id, @RequestBody Map<Str
5459
));
5560
}
5661

57-
@PutMapping("/{id}/toggle-availability")
62+
@PutMapping("/products/{id}/toggle-availability") // <--- Canviat
63+
@ResponseBody
5864
public ResponseEntity<?> toggleAvailability(@PathVariable Long id) {
5965
Product product = productRepository.findById(id)
6066
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Product not found"));
6167

6268
validateProductOwnership(product);
6369

64-
// Check if we can make it available
6570
if (!product.isAvailable() && product.getStock() == 0) {
6671
return ResponseEntity.badRequest().body("Cannot make product available with 0 stock");
6772
}
@@ -76,7 +81,8 @@ public ResponseEntity<?> toggleAvailability(@PathVariable Long id) {
7681
));
7782
}
7883

79-
@GetMapping("/low-stock")
84+
@GetMapping("/products/low-stock") // <--- Canviat
85+
@ResponseBody
8086
public ResponseEntity<List<Product>> getLowStockProducts(
8187
@RequestParam(defaultValue = "10") int threshold) {
8288

@@ -89,7 +95,8 @@ public ResponseEntity<List<Product>> getLowStockProducts(
8995
return ResponseEntity.ok(lowStockProducts);
9096
}
9197

92-
@GetMapping("/by-business/{businessId}")
98+
@GetMapping("/products/by-business/{businessId}") // <--- Canviat
99+
@ResponseBody
93100
public ResponseEntity<List<Product>> getProductsByBusiness(@PathVariable String businessId) {
94101
Business business = businessRepository.findById(businessId)
95102
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Business not found"));
@@ -125,4 +132,4 @@ private void validateProductOwnership(Product product) {
125132
throw new AccessDeniedException("You can only modify products from your own business");
126133
}
127134
}
128-
}
135+
}

src/main/java/cat/udl/eps/softarch/demo/domain/Category.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package cat.udl.eps.softarch.demo.domain;
22

3+
import com.fasterxml.jackson.annotation.JsonIgnore; // <--- Importa això
34
import jakarta.persistence.*;
45
import jakarta.validation.constraints.NotBlank;
56
import lombok.*;
67
import org.hibernate.validator.constraints.Length;
8+
import java.util.Set;
79

810
@EqualsAndHashCode(callSuper = true)
911
@Entity(name = "Category")
@@ -26,4 +28,10 @@ public class Category extends UriEntity<Long> {
2628
@Column(length = 255)
2729
private String description;
2830

29-
}
31+
// --- AFEGIR AIXÒ PER EVITAR BUCLES INFINITS I ERRORS ---
32+
@OneToMany(mappedBy = "category")
33+
@JsonIgnore
34+
@ToString.Exclude
35+
@EqualsAndHashCode.Exclude
36+
private Set<Product> products;
37+
}

src/main/java/cat/udl/eps/softarch/demo/domain/Product.java

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,13 @@ public class Product extends UriEntity<Long>{
5757
@PositiveOrZero private int proteins;
5858
@PositiveOrZero private int fats;
5959

60+
// IMPORTAT: Excloure col·leccions del HashCode/Equals per evitar problemes amb Hibernate
6061
@ElementCollection
62+
@EqualsAndHashCode.Exclude
6163
private Set<String> ingredients;
64+
6265
@ElementCollection
66+
@EqualsAndHashCode.Exclude
6367
private Set<String> allergens;
6468

6569
@DecimalMin(value = "0")
@@ -68,10 +72,10 @@ public class Product extends UriEntity<Long>{
6872

6973
// Loyalty program related fields
7074
@PositiveOrZero
71-
private Integer pointsGiven; // Points given when purchasing this product
72-
75+
private Integer pointsGiven;
76+
7377
@PositiveOrZero
74-
private Integer pointsCost; // Points needed to redeem this product
78+
private Integer pointsCost;
7579

7680
@JsonProperty("partOfLoyaltyProgram")
7781
private boolean isPartOfLoyaltyProgram;
@@ -80,22 +84,36 @@ public class Product extends UriEntity<Long>{
8084
@Lob
8185
@JsonIgnore
8286
@Column(length = 5242880) // 5MB max
87+
@ToString.Exclude
88+
@EqualsAndHashCode.Exclude
8389
private byte[] image;
8490

8591
private String imageContentType;
8692

87-
//TODO
88-
// @ManyToMany(mappedBy = "products")
89-
// private Set<Order> orders;
93+
// --- CORRECCIONS PRINCIPALS AQUÍ ---
94+
95+
@ManyToMany(mappedBy = "products")
96+
@JsonIgnore
97+
@ToString.Exclude
98+
@EqualsAndHashCode.Exclude // <--- VITAL: Evita que Hibernate cridi hashCode mentre gestiona la col·lecció
99+
private Set<Order> orders;
90100

91101
@ManyToOne
92102
private Category category;
93103

94104
@ManyToMany
105+
@JsonIgnore
106+
@ToString.Exclude
107+
@EqualsAndHashCode.Exclude // <--- VITAL
95108
private Set<Basket> baskets;
109+
// -----------------------------------
96110

97111
@ManyToOne
98112
private Inventory inventory;
99113

114+
@JsonProperty("categoryName")
115+
public String getCategoryName() {
116+
return category != null ? category.getName() : "General";
117+
}
100118

101-
}
119+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package cat.udl.eps.softarch.demo.domain.projection;
2+
3+
import cat.udl.eps.softarch.demo.domain.Category;
4+
import cat.udl.eps.softarch.demo.domain.Product;
5+
import org.springframework.data.rest.core.config.Projection;
6+
import java.math.BigDecimal;
7+
8+
// Aquesta projecció només la farem servir per la LLISTA
9+
@Projection(name = "productSummary", types = { Product.class })
10+
public interface ProductSummary {
11+
Long getId();
12+
String getName();
13+
BigDecimal getPrice();
14+
int getStock();
15+
boolean getIsAvailable();
16+
String getDescription(); // Per la descripció curta de la card
17+
18+
// Aquí podem agafar l'objecte sencer o fer servir el truc del categoryName
19+
// Si vols l'objecte sencer per la llista:
20+
Category getCategory();
21+
}

src/test/java/cat/udl/eps/softarch/demo/steps/OrderStepsDefs.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -222,10 +222,10 @@ public void i_request_my_list_of_orders(String customerName) throws Exception {
222222
Customer customer = customerRepository.findById(customerName).orElseThrow();
223223

224224
stepDefs.result = stepDefs.mockMvc.perform(
225-
get("/orders/search/findByCustomer?customer={customerUri}", customer.getUri())
226-
.accept(MediaType.APPLICATION_JSON)
227-
.characterEncoding(StandardCharsets.UTF_8)
228-
.with(AuthenticationStepDefs.authenticate()))
225+
get("/orders/search/findByCustomer?customer={customerUri}", customer.getUri())
226+
.accept(MediaType.APPLICATION_JSON)
227+
.characterEncoding(StandardCharsets.UTF_8)
228+
.with(AuthenticationStepDefs.authenticate()))
229229
.andDo(print());
230230
}
231231

@@ -262,4 +262,4 @@ public void the_response_should_contain_orders(Integer expectedCount) throws Exc
262262
stepDefs.result.andExpect(jsonPath("$._embedded.orders").isArray());
263263
stepDefs.result.andExpect(jsonPath("$._embedded.orders.length()").value(expectedCount));
264264
}
265-
}
265+
}

0 commit comments

Comments
 (0)