From b7edb4dbe03793bbebe09183b8192bbb6d4de12e Mon Sep 17 00:00:00 2001 From: AhmadHoseiny Date: Sat, 17 May 2025 04:00:59 +0200 Subject: [PATCH 01/10] refactor: added manual builder Required --- .../courier/CourierAverageRatingResponse.java | 38 +++++++++++++++- .../api/dtos/product/TopSellerResponse.java | 44 ++++++++++++++++++- .../dtos/revenue/RevenueSummaryResponse.java | 28 +++++++++++- 3 files changed, 104 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/Podzilla/analytics/api/dtos/courier/CourierAverageRatingResponse.java b/src/main/java/com/Podzilla/analytics/api/dtos/courier/CourierAverageRatingResponse.java index e3bce5d..e4a20c5 100644 --- a/src/main/java/com/Podzilla/analytics/api/dtos/courier/CourierAverageRatingResponse.java +++ b/src/main/java/com/Podzilla/analytics/api/dtos/courier/CourierAverageRatingResponse.java @@ -3,12 +3,12 @@ import java.math.BigDecimal; import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; -import lombok.Builder; +// import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; @Data -@Builder +// @Builder @NoArgsConstructor @AllArgsConstructor public class CourierAverageRatingResponse { @@ -21,4 +21,38 @@ public class CourierAverageRatingResponse { @Schema(description = "Average rating of the courier", example = "4.6") private BigDecimal averageRating; + + public static Builder builder() { + return new Builder(); + } + public static class Builder { + private Long courierId; + private String courierName; + private BigDecimal averageRating; + + public Builder() { } + + public Builder courierId(final Long courierId) { + this.courierId = courierId; + return this; + } + + public Builder courierName(final String courierName) { + this.courierName = courierName; + return this; + } + + public Builder averageRating(final BigDecimal averageRating) { + this.averageRating = averageRating; + return this; + } + + public CourierAverageRatingResponse build() { + return new CourierAverageRatingResponse( + courierId, + courierName, + averageRating + ); + } + } } diff --git a/src/main/java/com/Podzilla/analytics/api/dtos/product/TopSellerResponse.java b/src/main/java/com/Podzilla/analytics/api/dtos/product/TopSellerResponse.java index 18e38fe..a27fca3 100644 --- a/src/main/java/com/Podzilla/analytics/api/dtos/product/TopSellerResponse.java +++ b/src/main/java/com/Podzilla/analytics/api/dtos/product/TopSellerResponse.java @@ -3,7 +3,7 @@ import java.math.BigDecimal; import lombok.AllArgsConstructor; -import lombok.Builder; +// import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; @@ -12,7 +12,7 @@ @Data @NoArgsConstructor @AllArgsConstructor -@Builder +// @Builder public class TopSellerResponse { @Schema(description = "Product ID", example = "101") private Long productId; @@ -22,4 +22,44 @@ public class TopSellerResponse { private String category; @Schema(description = "Total value sold", example = "2500.75") private BigDecimal value; + + public static Builder builder() { + return new Builder(); + } + public static class Builder { + private Long productId; + private String productName; + private String category; + private BigDecimal value; + + public Builder productId(final Long productId) { + this.productId = productId; + return this; + } + + public Builder productName(final String productName) { + this.productName = productName; + return this; + } + + public Builder category(final String category) { + this.category = category; + return this; + } + + public Builder value(final BigDecimal value) { + this.value = value; + return this; + } + + public TopSellerResponse build() { + return new TopSellerResponse( + productId, + productName, + category, + value + ); + } + } + } diff --git a/src/main/java/com/Podzilla/analytics/api/dtos/revenue/RevenueSummaryResponse.java b/src/main/java/com/Podzilla/analytics/api/dtos/revenue/RevenueSummaryResponse.java index 74b88cd..227ef85 100644 --- a/src/main/java/com/Podzilla/analytics/api/dtos/revenue/RevenueSummaryResponse.java +++ b/src/main/java/com/Podzilla/analytics/api/dtos/revenue/RevenueSummaryResponse.java @@ -4,7 +4,7 @@ import java.time.LocalDate; import lombok.AllArgsConstructor; -import lombok.Builder; +// import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; @@ -13,7 +13,7 @@ @Data @NoArgsConstructor @AllArgsConstructor -@Builder +// @Builder public class RevenueSummaryResponse { @Schema(description = "Start date of the period for the revenue summary", example = "2023-01-01") @@ -22,5 +22,29 @@ public class RevenueSummaryResponse { @Schema(description = "Total revenue for the specified period", example = "12345.67") private BigDecimal totalRevenue; + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private LocalDate periodStartDate; + private BigDecimal totalRevenue; + public Builder() { } + + public Builder periodStartDate(final LocalDate periodStartDate) { + this.periodStartDate = periodStartDate; + return this; + } + + public Builder totalRevenue(final BigDecimal totalRevenue) { + this.totalRevenue = totalRevenue; + return this; + } + + public RevenueSummaryResponse build() { + return new RevenueSummaryResponse(periodStartDate, totalRevenue); + } + } } From 222487cefa03f9dd4759789c50d9978862c58ac5 Mon Sep 17 00:00:00 2001 From: AhmadHoseiny Date: Sat, 17 May 2025 14:41:12 +0200 Subject: [PATCH 02/10] added builder manually to all models Required --- .../Podzilla/analytics/models/Courier.java | 34 ++++- .../Podzilla/analytics/models/Customer.java | 28 +++- .../analytics/models/InventorySnapshot.java | 41 +++++- .../com/Podzilla/analytics/models/Order.java | 124 +++++++++++++++++- .../Podzilla/analytics/models/Product.java | 47 ++++++- .../com/Podzilla/analytics/models/Region.java | 47 ++++++- .../analytics/models/SalesLineItem.java | 53 +++++++- 7 files changed, 360 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/Podzilla/analytics/models/Courier.java b/src/main/java/com/Podzilla/analytics/models/Courier.java index 0e50fd0..53c82d1 100644 --- a/src/main/java/com/Podzilla/analytics/models/Courier.java +++ b/src/main/java/com/Podzilla/analytics/models/Courier.java @@ -8,14 +8,14 @@ import jakarta.persistence.Id; import jakarta.persistence.Table; import lombok.AllArgsConstructor; -import lombok.Builder; +// import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; @Entity @Table(name = "couriers") @Data -@Builder +// @Builder @NoArgsConstructor @AllArgsConstructor public class Courier { @@ -32,4 +32,34 @@ public enum CourierStatus { INACTIVE, SUSPENDED } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private Long id; + private String name; + private CourierStatus status; + + public Builder() { } + public Builder id(final Long id) { + this.id = id; + return this; + } + + public Builder name(final String name) { + this.name = name; + return this; + } + + public Builder status(final CourierStatus status) { + this.status = status; + return this; + } + + public Courier build() { + return new Courier(id, name, status); + } + } } diff --git a/src/main/java/com/Podzilla/analytics/models/Customer.java b/src/main/java/com/Podzilla/analytics/models/Customer.java index f63cbc9..a71690f 100644 --- a/src/main/java/com/Podzilla/analytics/models/Customer.java +++ b/src/main/java/com/Podzilla/analytics/models/Customer.java @@ -6,14 +6,14 @@ import jakarta.persistence.Id; import jakarta.persistence.Table; import lombok.AllArgsConstructor; -import lombok.Builder; +// import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; @Entity @Table(name = "customers") @Data -@Builder +// @Builder @NoArgsConstructor @AllArgsConstructor public class Customer { @@ -21,4 +21,28 @@ public class Customer { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; + + public static Builder builder() { + return new Builder(); + } + public static class Builder { + private Long id; + private String name; + + public Builder() { } + + public Builder id(final Long id) { + this.id = id; + return this; + } + + public Builder name(final String name) { + this.name = name; + return this; + } + + public Customer build() { + return new Customer(id, name); + } + } } diff --git a/src/main/java/com/Podzilla/analytics/models/InventorySnapshot.java b/src/main/java/com/Podzilla/analytics/models/InventorySnapshot.java index f5fd12d..8e19cb3 100644 --- a/src/main/java/com/Podzilla/analytics/models/InventorySnapshot.java +++ b/src/main/java/com/Podzilla/analytics/models/InventorySnapshot.java @@ -9,14 +9,14 @@ import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; import lombok.AllArgsConstructor; -import lombok.Builder; +// import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; @Entity @Table(name = "inventory_snapshots") @Data -@Builder +// @Builder @NoArgsConstructor @AllArgsConstructor public class InventorySnapshot { @@ -31,4 +31,41 @@ public class InventorySnapshot { private Product product; private int quantity; + + public static Builder builder() { + return new Builder(); + } + public static class Builder { + private Long id; + private LocalDateTime timestamp; + private Product product; + private int quantity; + + public Builder() { } + + public Builder id(final Long id) { + this.id = id; + return this; + } + + public Builder timestamp(final LocalDateTime timestamp) { + this.timestamp = timestamp; + return this; + } + + public Builder product(final Product product) { + this.product = product; + return this; + } + + public Builder quantity(final int quantity) { + this.quantity = quantity; + return this; + } + + public InventorySnapshot build() { + return new InventorySnapshot(id, timestamp, product, quantity); + } + } + } diff --git a/src/main/java/com/Podzilla/analytics/models/Order.java b/src/main/java/com/Podzilla/analytics/models/Order.java index f3ec9b4..92fa0c7 100644 --- a/src/main/java/com/Podzilla/analytics/models/Order.java +++ b/src/main/java/com/Podzilla/analytics/models/Order.java @@ -17,14 +17,14 @@ import jakarta.persistence.OneToMany; import jakarta.persistence.Table; import lombok.AllArgsConstructor; -import lombok.Builder; +// import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; @Entity @Table(name = "orders") @Data -@Builder +// @Builder @NoArgsConstructor @AllArgsConstructor public class Order { @@ -69,4 +69,124 @@ public enum OrderStatus { COMPLETED, FAILED } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private Long id; + private BigDecimal totalAmount; + private LocalDateTime orderPlacedTimestamp; + private LocalDateTime shippedTimestamp; + private LocalDateTime deliveredTimestamp; + private LocalDateTime finalStatusTimestamp; + private OrderStatus status; + private String failureReason; + private int numberOfItems; + private BigDecimal courierRating; + private Customer customer; + private Courier courier; + private Region region; + private List salesLineItems; + + public Builder() { } + + public Builder id(final Long id) { + this.id = id; + return this; + } + + public Builder totalAmount(final BigDecimal totalAmount) { + this.totalAmount = totalAmount; + return this; + } + + public Builder orderPlacedTimestamp( + final LocalDateTime orderPlacedTimestamp + ) { + this.orderPlacedTimestamp = orderPlacedTimestamp; + return this; + } + + public Builder shippedTimestamp(final LocalDateTime shippedTimestamp) { + this.shippedTimestamp = shippedTimestamp; + return this; + } + + public Builder deliveredTimestamp( + final LocalDateTime deliveredTimestamp + ) { + this.deliveredTimestamp = deliveredTimestamp; + return this; + } + + public Builder finalStatusTimestamp( + final LocalDateTime finalStatusTimestamp + ) { + this.finalStatusTimestamp = finalStatusTimestamp; + return this; + } + + public Builder status(final OrderStatus status) { + this.status = status; + return this; + } + + public Builder failureReason(final String failureReason) { + this.failureReason = failureReason; + return this; + } + + public Builder numberOfItems(final int numberOfItems) { + this.numberOfItems = numberOfItems; + return this; + } + + public Builder courierRating(final BigDecimal courierRating) { + this.courierRating = courierRating; + return this; + } + + public Builder customer(final Customer customer) { + this.customer = customer; + return this; + } + + public Builder courier(final Courier courier) { + this.courier = courier; + return this; + } + + public Builder region(final Region region) { + this.region = region; + return this; + } + + public Builder salesLineItems( + final List salesLineItems + ) { + this.salesLineItems = salesLineItems; + return this; + } + + public Order build() { + return new Order( + id, + totalAmount, + orderPlacedTimestamp, + shippedTimestamp, + deliveredTimestamp, + finalStatusTimestamp, + status, + failureReason, + numberOfItems, + courierRating, + customer, + courier, + region, + salesLineItems + ); + } + } } diff --git a/src/main/java/com/Podzilla/analytics/models/Product.java b/src/main/java/com/Podzilla/analytics/models/Product.java index 30f73ae..5dd6ae6 100644 --- a/src/main/java/com/Podzilla/analytics/models/Product.java +++ b/src/main/java/com/Podzilla/analytics/models/Product.java @@ -8,14 +8,14 @@ import jakarta.persistence.Id; import jakarta.persistence.Table; import lombok.AllArgsConstructor; -import lombok.Builder; +// import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; @Entity @Table(name = "products") @Data -@Builder +// @Builder @NoArgsConstructor @AllArgsConstructor public class Product { @@ -26,4 +26,47 @@ public class Product { private String category; private BigDecimal cost; private int lowStockThreshold; + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private Long id; + private String name; + private String category; + private BigDecimal cost; + private int lowStockThreshold; + + public Builder() { } + + public Builder id(final Long id) { + this.id = id; + return this; + } + + public Builder name(final String name) { + this.name = name; + return this; + } + + public Builder category(final String category) { + this.category = category; + return this; + } + + public Builder cost(final BigDecimal cost) { + this.cost = cost; + return this; + } + + public Builder lowStockThreshold(final int lowStockThreshold) { + this.lowStockThreshold = lowStockThreshold; + return this; + } + + public Product build() { + return new Product(id, name, category, cost, lowStockThreshold); + } + } } diff --git a/src/main/java/com/Podzilla/analytics/models/Region.java b/src/main/java/com/Podzilla/analytics/models/Region.java index 01945d0..7ce52bf 100644 --- a/src/main/java/com/Podzilla/analytics/models/Region.java +++ b/src/main/java/com/Podzilla/analytics/models/Region.java @@ -6,14 +6,14 @@ import jakarta.persistence.Id; import jakarta.persistence.Table; import lombok.AllArgsConstructor; -import lombok.Builder; +// import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; @Entity @Table(name = "regions") @Data -@Builder +// @Builder @NoArgsConstructor @AllArgsConstructor public class Region { @@ -24,4 +24,47 @@ public class Region { private String state; private String country; private String postalCode; + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private Long id; + private String city; + private String state; + private String country; + private String postalCode; + + public Builder() { } + + public Builder id(final Long id) { + this.id = id; + return this; + } + + public Builder city(final String city) { + this.city = city; + return this; + } + + public Builder state(final String state) { + this.state = state; + return this; + } + + public Builder country(final String country) { + this.country = country; + return this; + } + + public Builder postalCode(final String postalCode) { + this.postalCode = postalCode; + return this; + } + + public Region build() { + return new Region(id, city, state, country, postalCode); + } + } } diff --git a/src/main/java/com/Podzilla/analytics/models/SalesLineItem.java b/src/main/java/com/Podzilla/analytics/models/SalesLineItem.java index d9e1212..d9dc908 100644 --- a/src/main/java/com/Podzilla/analytics/models/SalesLineItem.java +++ b/src/main/java/com/Podzilla/analytics/models/SalesLineItem.java @@ -9,14 +9,14 @@ import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; import lombok.AllArgsConstructor; -import lombok.Builder; +// import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; @Entity @Table(name = "sales_line_items") @Data -@Builder +// @Builder @NoArgsConstructor @AllArgsConstructor public class SalesLineItem { @@ -34,4 +34,53 @@ public class SalesLineItem { @ManyToOne @JoinColumn(name = "order_id", nullable = false) private Order order; + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private Long id; + private int quantity; + private BigDecimal pricePerUnit; + private Product product; + private Order order; + + public Builder() { } + + public Builder id(final Long id) { + this.id = id; + return this; + } + + public Builder quantity(final int quantity) { + this.quantity = quantity; + return this; + } + + public Builder pricePerUnit(final BigDecimal pricePerUnit) { + this.pricePerUnit = pricePerUnit; + return this; + } + + public Builder product(final Product product) { + this.product = product; + return this; + } + + public Builder order(final Order order) { + this.order = order; + return this; + } + + public SalesLineItem build() { + return new SalesLineItem( + id, + quantity, + pricePerUnit, + product, + order + ); + } + } } From aa66b780f82be02dfae63dcecc09460a6df19400 Mon Sep 17 00:00:00 2001 From: AhmadHoseiny Date: Sat, 17 May 2025 16:47:08 +0200 Subject: [PATCH 03/10] feat: added configuration for rabbit listeners employed command design pattern --- .../controllers/RabbitTesterController.java | 111 ++++++++++++++++++ .../messaging/AnalyticsRabbitListener.java | 39 ++++++ .../messaging/InvokerDispatcher.java | 40 +++++++ .../messaging/InvokerDispatcherConfig.java | 99 ++++++++++++++++ .../analytics/messaging/RabbitListener.java | 11 -- .../analytics/messaging/commands/Command.java | 5 + .../analytics/messaging/invokers/Invoker.java | 5 + .../inventory/InventoryUpdatedInvoker.java | 15 +++ .../inventory/ProductCreatedInvoker.java | 14 +++ .../order/OrderAssignedToCourierInvoker.java | 14 +++ .../invokers/order/OrderCancelledInvoker.java | 14 +++ .../invokers/order/OrderDeliveredInvoker.java | 15 +++ .../order/OrderDeliveryFailedInvoker.java | 15 +++ .../order/OrderOutForDeliveryInvoker.java | 14 +++ .../invokers/order/OrderPlacedInvoker.java | 15 +++ .../user/CourierRegisteredInvoker.java | 14 +++ .../user/CustomerRegisteredInvoker.java | 16 +++ 17 files changed, 445 insertions(+), 11 deletions(-) create mode 100644 src/main/java/com/Podzilla/analytics/api/controllers/RabbitTesterController.java create mode 100644 src/main/java/com/Podzilla/analytics/messaging/AnalyticsRabbitListener.java create mode 100644 src/main/java/com/Podzilla/analytics/messaging/InvokerDispatcher.java create mode 100644 src/main/java/com/Podzilla/analytics/messaging/InvokerDispatcherConfig.java delete mode 100644 src/main/java/com/Podzilla/analytics/messaging/RabbitListener.java create mode 100644 src/main/java/com/Podzilla/analytics/messaging/commands/Command.java create mode 100644 src/main/java/com/Podzilla/analytics/messaging/invokers/Invoker.java create mode 100644 src/main/java/com/Podzilla/analytics/messaging/invokers/inventory/InventoryUpdatedInvoker.java create mode 100644 src/main/java/com/Podzilla/analytics/messaging/invokers/inventory/ProductCreatedInvoker.java create mode 100644 src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderAssignedToCourierInvoker.java create mode 100644 src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderCancelledInvoker.java create mode 100644 src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderDeliveredInvoker.java create mode 100644 src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderDeliveryFailedInvoker.java create mode 100644 src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderOutForDeliveryInvoker.java create mode 100644 src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderPlacedInvoker.java create mode 100644 src/main/java/com/Podzilla/analytics/messaging/invokers/user/CourierRegisteredInvoker.java create mode 100644 src/main/java/com/Podzilla/analytics/messaging/invokers/user/CustomerRegisteredInvoker.java diff --git a/src/main/java/com/Podzilla/analytics/api/controllers/RabbitTesterController.java b/src/main/java/com/Podzilla/analytics/api/controllers/RabbitTesterController.java new file mode 100644 index 0000000..590c8e0 --- /dev/null +++ b/src/main/java/com/Podzilla/analytics/api/controllers/RabbitTesterController.java @@ -0,0 +1,111 @@ +package com.Podzilla.analytics.api.controllers; + +import java.math.BigDecimal; +import java.util.ArrayList; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.Podzilla.analytics.messaging.AnalyticsRabbitListener; +import com.podzilla.mq.events.BaseEvent; +import com.podzilla.mq.events.CourierRegisteredEvent; +import com.podzilla.mq.events.CustomerRegisteredEvent; +import com.podzilla.mq.events.DeliveryAddress; +import com.podzilla.mq.events.InventoryUpdatedEvent; +import com.podzilla.mq.events.OrderAssignedToCourierEvent; +import com.podzilla.mq.events.OrderCancelledEvent; +import com.podzilla.mq.events.OrderDeliveredEvent; +import com.podzilla.mq.events.OrderDeliveryFailedEvent; +import com.podzilla.mq.events.OrderOutForDeliveryEvent; +import com.podzilla.mq.events.OrderPlacedEvent; +import com.podzilla.mq.events.ProductCreatedEvent; + +@RestController +@RequestMapping("/rabbit-tester") +public class RabbitTesterController { + + @Autowired + private AnalyticsRabbitListener listener; + + @GetMapping("/courier-registered-event") + public void testCourierRegisteredEvent() { + BaseEvent event = new CourierRegisteredEvent("1", "ahmad", "010"); + listener.handleUserEvents(event); + } + + @GetMapping("/customer-registered-event") + public void testCustomerRegisteredEvent() { + BaseEvent event = new CustomerRegisteredEvent("1", "ahmad"); + listener.handleUserEvents(event); + } + + @GetMapping("/order-assigned-to-courier-event") + public void testOrderAssignedToCourierEvent() { + BaseEvent event = new OrderAssignedToCourierEvent("1", "2"); + listener.handleOrderEvents(event); + } + + @GetMapping("/order-cancelled-event") + public void testOrderCancelledEvent() { + BaseEvent event = new OrderCancelledEvent("1", "2", "some reason"); + listener.handleOrderEvents(event); + } + + @GetMapping("/order-delivered-event") + public void testOrderDeliveredEvent() { + BaseEvent event = new OrderDeliveredEvent("1", "2", + new BigDecimal("10.0")); + listener.handleOrderEvents(event); + } + + @GetMapping("/order-delivery-failed-event") + public void testOrderDeliveryFailedEvent() { + BaseEvent event = new OrderDeliveryFailedEvent("1", "2", "some reason"); + listener.handleOrderEvents(event); + } + + @GetMapping("/order-out-for-delivery-event") + public void testOrderOutForDeliveryEvent() { + BaseEvent event = new OrderOutForDeliveryEvent("1", "2"); + listener.handleOrderEvents(event); + } + + @GetMapping("/order-placed-event") + public void testOrderPlacedEvent() { + BaseEvent event = new OrderPlacedEvent( + "1", + "2", + new BigDecimal("10.0"), + new ArrayList<>(), + new DeliveryAddress( + "some street", + "some city", + "some state", + "some country", + "some postal code" + ) + ); + listener.handleOrderEvents(event); + } + + @GetMapping("inventory-updated-event") + public void testInventoryUpdatedEvent() { + BaseEvent event = new InventoryUpdatedEvent("1", Integer.valueOf(1)); + listener.handleInventoryEvents(event); + } + + @GetMapping("product-created-event") + public void testProductCreatedEvent() { + BaseEvent event = new ProductCreatedEvent( + "1", + "some name", + "some category", + new BigDecimal("10.0"), + Integer.valueOf(1) + ); + listener.handleInventoryEvents(event); + } + +} diff --git a/src/main/java/com/Podzilla/analytics/messaging/AnalyticsRabbitListener.java b/src/main/java/com/Podzilla/analytics/messaging/AnalyticsRabbitListener.java new file mode 100644 index 0000000..78a49bd --- /dev/null +++ b/src/main/java/com/Podzilla/analytics/messaging/AnalyticsRabbitListener.java @@ -0,0 +1,39 @@ +package com.Podzilla.analytics.messaging; + +import org.springframework.amqp.rabbit.annotation.RabbitListener; +import org.springframework.beans.factory.annotation.Autowired; + +import com.podzilla.mq.EventsConstants; +import com.podzilla.mq.events.BaseEvent; + +import org.springframework.stereotype.Service; + + + +@Service +public class AnalyticsRabbitListener { + + @Autowired + private InvokerDispatcher dispatcher; + + @RabbitListener( + queues = EventsConstants.ANALYTICS_USER_EVENT_QUEUE + ) + public void handleUserEvents(final BaseEvent userEvent) { + dispatcher.dispatch(userEvent); + } + + @RabbitListener( + queues = EventsConstants.ANALYTICS_ORDER_EVENT_QUEUE + ) + public void handleOrderEvents(final BaseEvent orderEvent) { + dispatcher.dispatch(orderEvent); + } + + @RabbitListener( + queues = EventsConstants.ANALYTICS_INVENTORY_EVENT_QUEUE + ) + public void handleInventoryEvents(final BaseEvent inventoryEvent) { + dispatcher.dispatch(inventoryEvent); + } +} diff --git a/src/main/java/com/Podzilla/analytics/messaging/InvokerDispatcher.java b/src/main/java/com/Podzilla/analytics/messaging/InvokerDispatcher.java new file mode 100644 index 0000000..00cb016 --- /dev/null +++ b/src/main/java/com/Podzilla/analytics/messaging/InvokerDispatcher.java @@ -0,0 +1,40 @@ +package com.Podzilla.analytics.messaging; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import com.Podzilla.analytics.messaging.invokers.Invoker; + +public class InvokerDispatcher { + private final Map, Invoker> invokers; + + public InvokerDispatcher() { + this.invokers = new ConcurrentHashMap<>(); + } + + public void registerInvoker( + final Class event, final Invoker invoker + ) { + if (event == null || invoker == null) { + throw new IllegalArgumentException( + "Event and Invoker cannot be null" + ); + } + invokers.put(event, invoker); + } + + @SuppressWarnings("unchecked") + public void dispatch(final T event) { + if (event == null) { + throw new IllegalArgumentException("Event cannot be null"); + } + + Invoker invoker = (Invoker) invokers.get(event.getClass()); + if (invoker != null) { + invoker.invoke(event); + } else { + throw new RuntimeException("No invoker found for: " + + event.getClass()); + } + } +} diff --git a/src/main/java/com/Podzilla/analytics/messaging/InvokerDispatcherConfig.java b/src/main/java/com/Podzilla/analytics/messaging/InvokerDispatcherConfig.java new file mode 100644 index 0000000..2cd24e3 --- /dev/null +++ b/src/main/java/com/Podzilla/analytics/messaging/InvokerDispatcherConfig.java @@ -0,0 +1,99 @@ +package com.Podzilla.analytics.messaging; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import com.Podzilla.analytics.messaging.invokers.user.CourierRegisteredInvoker; +import com.Podzilla.analytics.messaging.invokers.user.CustomerRegisteredInvoker; +import com.Podzilla.analytics.messaging.invokers.order.OrderAssignedToCourierInvoker; +import com.Podzilla.analytics.messaging.invokers.order.OrderDeliveryFailedInvoker; +import com.Podzilla.analytics.messaging.invokers.order.OrderPlacedInvoker; +import com.Podzilla.analytics.messaging.invokers.order.OrderCancelledInvoker; +import com.Podzilla.analytics.messaging.invokers.order.OrderDeliveredInvoker; +import com.Podzilla.analytics.messaging.invokers.order.OrderOutForDeliveryInvoker; +import com.Podzilla.analytics.messaging.invokers.inventory.InventoryUpdatedInvoker; +import com.Podzilla.analytics.messaging.invokers.inventory.ProductCreatedInvoker; + +import com.podzilla.mq.events.CourierRegisteredEvent; +import com.podzilla.mq.events.CustomerRegisteredEvent; +import com.podzilla.mq.events.OrderAssignedToCourierEvent; +import com.podzilla.mq.events.OrderDeliveryFailedEvent; +import com.podzilla.mq.events.OrderPlacedEvent; +import com.podzilla.mq.events.OrderCancelledEvent; +import com.podzilla.mq.events.OrderDeliveredEvent; +import com.podzilla.mq.events.OrderOutForDeliveryEvent; +import com.podzilla.mq.events.InventoryUpdatedEvent; +import com.podzilla.mq.events.ProductCreatedEvent; + + +@Configuration +public class InvokerDispatcherConfig { + + @Bean + public InvokerDispatcher invokerDispatcher() { + InvokerDispatcher dispatcher = new InvokerDispatcher(); + + registerUserInvokers(dispatcher); + registerOrderInvokers(dispatcher); + registerInventoryInvokers(dispatcher); + + return dispatcher; + } + + private void registerUserInvokers( + final InvokerDispatcher dispatcher + ) { + dispatcher.registerInvoker( + CourierRegisteredEvent.class, + new CourierRegisteredInvoker() + ); + + dispatcher.registerInvoker( + CustomerRegisteredEvent.class, + new CustomerRegisteredInvoker() + ); + } + + private void registerOrderInvokers( + final InvokerDispatcher dispatcher + ) { + dispatcher.registerInvoker( + OrderAssignedToCourierEvent.class, + new OrderAssignedToCourierInvoker() + ); + dispatcher.registerInvoker( + OrderCancelledEvent.class, + new OrderCancelledInvoker() + ); + dispatcher.registerInvoker( + OrderDeliveredEvent.class, + new OrderDeliveredInvoker() + ); + dispatcher.registerInvoker( + OrderDeliveryFailedEvent.class, + new OrderDeliveryFailedInvoker() + ); + dispatcher.registerInvoker( + OrderOutForDeliveryEvent.class, + new OrderOutForDeliveryInvoker() + ); + dispatcher.registerInvoker( + OrderPlacedEvent.class, + new OrderPlacedInvoker() + ); + } + + private void registerInventoryInvokers( + final InvokerDispatcher dispatcher + ) { + dispatcher.registerInvoker( + InventoryUpdatedEvent.class, + new InventoryUpdatedInvoker() + ); + dispatcher.registerInvoker( + ProductCreatedEvent.class, + new ProductCreatedInvoker() + ); + } + +} diff --git a/src/main/java/com/Podzilla/analytics/messaging/RabbitListener.java b/src/main/java/com/Podzilla/analytics/messaging/RabbitListener.java deleted file mode 100644 index afd38a2..0000000 --- a/src/main/java/com/Podzilla/analytics/messaging/RabbitListener.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.Podzilla.analytics.messaging; - -import org.springframework.beans.factory.annotation.Autowired; - -import com.Podzilla.analytics.eventhandler.EventHandlerDispatcher; - -public class RabbitListener { - - @Autowired - private EventHandlerDispatcher dispatcher; -} diff --git a/src/main/java/com/Podzilla/analytics/messaging/commands/Command.java b/src/main/java/com/Podzilla/analytics/messaging/commands/Command.java new file mode 100644 index 0000000..3fb749f --- /dev/null +++ b/src/main/java/com/Podzilla/analytics/messaging/commands/Command.java @@ -0,0 +1,5 @@ +package com.Podzilla.analytics.messaging.commands; + +public interface Command { + void execute(); +} diff --git a/src/main/java/com/Podzilla/analytics/messaging/invokers/Invoker.java b/src/main/java/com/Podzilla/analytics/messaging/invokers/Invoker.java new file mode 100644 index 0000000..8d01d14 --- /dev/null +++ b/src/main/java/com/Podzilla/analytics/messaging/invokers/Invoker.java @@ -0,0 +1,5 @@ +package com.Podzilla.analytics.messaging.invokers; + +public interface Invoker { // T should be the BaseEvent subclass of the event + void invoke(T event); +} diff --git a/src/main/java/com/Podzilla/analytics/messaging/invokers/inventory/InventoryUpdatedInvoker.java b/src/main/java/com/Podzilla/analytics/messaging/invokers/inventory/InventoryUpdatedInvoker.java new file mode 100644 index 0000000..cdc981a --- /dev/null +++ b/src/main/java/com/Podzilla/analytics/messaging/invokers/inventory/InventoryUpdatedInvoker.java @@ -0,0 +1,15 @@ +package com.Podzilla.analytics.messaging.invokers.inventory; + +import com.Podzilla.analytics.messaging.invokers.Invoker; +import com.podzilla.mq.events.InventoryUpdatedEvent; + +public class InventoryUpdatedInvoker + implements Invoker { + + @Override + public void invoke(final InventoryUpdatedEvent event) { + // create a command and call its execute method + System.out.println("Inventory Updated Event Invoked: " + event); + } + +} diff --git a/src/main/java/com/Podzilla/analytics/messaging/invokers/inventory/ProductCreatedInvoker.java b/src/main/java/com/Podzilla/analytics/messaging/invokers/inventory/ProductCreatedInvoker.java new file mode 100644 index 0000000..f84fd12 --- /dev/null +++ b/src/main/java/com/Podzilla/analytics/messaging/invokers/inventory/ProductCreatedInvoker.java @@ -0,0 +1,14 @@ +package com.Podzilla.analytics.messaging.invokers.inventory; + +import com.Podzilla.analytics.messaging.invokers.Invoker; +import com.podzilla.mq.events.ProductCreatedEvent; + +public class ProductCreatedInvoker + implements Invoker { + + @Override + public void invoke(final ProductCreatedEvent event) { + // create a command and call its execute method + System.out.println("Product Created Event Invoked: " + event); + } +} diff --git a/src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderAssignedToCourierInvoker.java b/src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderAssignedToCourierInvoker.java new file mode 100644 index 0000000..a68e932 --- /dev/null +++ b/src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderAssignedToCourierInvoker.java @@ -0,0 +1,14 @@ +package com.Podzilla.analytics.messaging.invokers.order; + +import com.Podzilla.analytics.messaging.invokers.Invoker; +import com.podzilla.mq.events.OrderAssignedToCourierEvent; + +public class OrderAssignedToCourierInvoker + implements Invoker { + + @Override + public void invoke(final OrderAssignedToCourierEvent event) { + // create a command and call its execute method + System.out.println("Order Assigned To Courier Event Invoked: " + event); + } +} diff --git a/src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderCancelledInvoker.java b/src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderCancelledInvoker.java new file mode 100644 index 0000000..61860d8 --- /dev/null +++ b/src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderCancelledInvoker.java @@ -0,0 +1,14 @@ +package com.Podzilla.analytics.messaging.invokers.order; + +import com.Podzilla.analytics.messaging.invokers.Invoker; +import com.podzilla.mq.events.OrderCancelledEvent; + +public class OrderCancelledInvoker + implements Invoker { + + @Override + public void invoke(final OrderCancelledEvent event) { + // create a command and call its execute method + System.out.println("Order Cancelled Event Invoked: " + event); + } +} diff --git a/src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderDeliveredInvoker.java b/src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderDeliveredInvoker.java new file mode 100644 index 0000000..befda6a --- /dev/null +++ b/src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderDeliveredInvoker.java @@ -0,0 +1,15 @@ +package com.Podzilla.analytics.messaging.invokers.order; + +import com.Podzilla.analytics.messaging.invokers.Invoker; +import com.podzilla.mq.events.OrderDeliveredEvent; + +public class OrderDeliveredInvoker + implements Invoker { + + @Override + public void invoke(final OrderDeliveredEvent event) { + // create a command and call its execute method + System.out.println("Order Delivered Event Invoked: " + event); + } + +} diff --git a/src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderDeliveryFailedInvoker.java b/src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderDeliveryFailedInvoker.java new file mode 100644 index 0000000..031b369 --- /dev/null +++ b/src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderDeliveryFailedInvoker.java @@ -0,0 +1,15 @@ +package com.Podzilla.analytics.messaging.invokers.order; + +import com.Podzilla.analytics.messaging.invokers.Invoker; +import com.podzilla.mq.events.OrderDeliveryFailedEvent; + +public class OrderDeliveryFailedInvoker + implements Invoker { + + @Override + public void invoke(final OrderDeliveryFailedEvent event) { + // create a command and call its execute method + System.out.println("Order Delivery Failed Event Invoked: " + event); + } + +} diff --git a/src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderOutForDeliveryInvoker.java b/src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderOutForDeliveryInvoker.java new file mode 100644 index 0000000..bb1d681 --- /dev/null +++ b/src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderOutForDeliveryInvoker.java @@ -0,0 +1,14 @@ +package com.Podzilla.analytics.messaging.invokers.order; + +import com.Podzilla.analytics.messaging.invokers.Invoker; +import com.podzilla.mq.events.OrderOutForDeliveryEvent; + +public class OrderOutForDeliveryInvoker + implements Invoker { + + @Override + public void invoke(final OrderOutForDeliveryEvent event) { + // create a command and call its execute method + System.out.println("Order Out For Delivery Event Invoked: " + event); + } +} diff --git a/src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderPlacedInvoker.java b/src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderPlacedInvoker.java new file mode 100644 index 0000000..3bca1dd --- /dev/null +++ b/src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderPlacedInvoker.java @@ -0,0 +1,15 @@ +package com.Podzilla.analytics.messaging.invokers.order; + +import com.Podzilla.analytics.messaging.invokers.Invoker; +import com.podzilla.mq.events.OrderPlacedEvent; + +public class OrderPlacedInvoker + implements Invoker { + + @Override + public void invoke(final OrderPlacedEvent event) { + // create a command and call its execute method + System.out.println("Order Placed Event Invoked: " + event); + } + +} diff --git a/src/main/java/com/Podzilla/analytics/messaging/invokers/user/CourierRegisteredInvoker.java b/src/main/java/com/Podzilla/analytics/messaging/invokers/user/CourierRegisteredInvoker.java new file mode 100644 index 0000000..5aa734e --- /dev/null +++ b/src/main/java/com/Podzilla/analytics/messaging/invokers/user/CourierRegisteredInvoker.java @@ -0,0 +1,14 @@ +package com.Podzilla.analytics.messaging.invokers.user; + +import com.Podzilla.analytics.messaging.invokers.Invoker; +import com.podzilla.mq.events.CourierRegisteredEvent; + +public class CourierRegisteredInvoker + implements Invoker { + + @Override + public void invoke(final CourierRegisteredEvent event) { + // create a command and call its execute method + System.out.println("Courier Registered Event Invoked: " + event); + } +} diff --git a/src/main/java/com/Podzilla/analytics/messaging/invokers/user/CustomerRegisteredInvoker.java b/src/main/java/com/Podzilla/analytics/messaging/invokers/user/CustomerRegisteredInvoker.java new file mode 100644 index 0000000..61b743f --- /dev/null +++ b/src/main/java/com/Podzilla/analytics/messaging/invokers/user/CustomerRegisteredInvoker.java @@ -0,0 +1,16 @@ +package com.Podzilla.analytics.messaging.invokers.user; + +import com.Podzilla.analytics.messaging.invokers.Invoker; +import com.podzilla.mq.events.CustomerRegisteredEvent; + +public class CustomerRegisteredInvoker + implements Invoker { + + @Override + public void invoke(final CustomerRegisteredEvent event) { + // create a command and call its execute method + System.out.println("Customer Registered Event Invoked: " + event); + } + + +} From 397a4f79bb5b420e394a318e24d1e74ede179c42 Mon Sep 17 00:00:00 2001 From: AhmadHoseiny Date: Sun, 18 May 2025 01:50:51 +0200 Subject: [PATCH 04/10] refactor: changes ids from long to uuid added first command, fixed validation issues, and tests need fixing --- .../controllers/CustomerReportController.java | 3 +- .../FulfillmentReportController.java | 3 +- .../InventoryReportController.java | 3 +- .../controllers/RabbitTesterController.java | 5 +- .../api/dtos/DateRangePaginationRequest.java | 24 +- .../analytics/api/dtos/DateRangeRequest.java | 2 +- .../analytics/api/dtos/IDateRangeRequest.java | 8 + .../api/dtos/IPaginationRequest.java | 6 + .../analytics/api/dtos/PaginationRequest.java | 6 +- .../courier/CourierAverageRatingResponse.java | 7 +- .../courier/CourierDeliveryCountResponse.java | 3 +- .../CourierPerformanceReportResponse.java | 4 +- .../courier/CourierSuccessRateResponse.java | 3 +- .../CustomersTopSpendersResponse.java | 3 +- .../FulfillmentPlaceToShipRequest.java | 3 +- .../FulfillmentShipToDeliverRequest.java | 3 +- .../inventory/LowStockProductResponse.java | 3 +- .../api/dtos/order/OrderRegionResponse.java | 7 +- .../api/dtos/product/TopSellerRequest.java | 4 +- .../api/dtos/product/TopSellerResponse.java | 13 +- .../revenue/RevenueByCategoryRequest.java | 3 +- .../dtos/revenue/RevenueSummaryRequest.java | 3 +- .../courier/CourierPerformanceProjection.java | 3 +- .../CustomersTopSpendersProjection.java | 3 +- .../inventory/LowStockProductProjection.java | 4 +- .../order/OrderRegionProjection.java | 3 +- .../product/TopSellingProductProjection.java | 3 +- .../analytics/config/DatabaseSeeder.java | 53 +- .../messaging/AnalyticsRabbitListener.java | 22 +- .../messaging/InvokerDispatcherConfig.java | 12 +- .../messaging/commands/CommandFactory.java | 26 + .../user/RegisterCustomerCommand.java | 23 + .../messaging/invokers/InvokerFactory.java | 22 + .../user/CustomerRegisteredInvoker.java | 19 +- .../Podzilla/analytics/models/Courier.java | 10 +- .../Podzilla/analytics/models/Customer.java | 10 +- .../analytics/models/InventorySnapshot.java | 10 +- .../com/Podzilla/analytics/models/Order.java | 10 +- .../Podzilla/analytics/models/Product.java | 10 +- .../com/Podzilla/analytics/models/Region.java | 10 +- .../analytics/models/SalesLineItem.java | 10 +- .../repositories/OrderRepository.java | 20 +- .../services/CourierAnalyticsService.java | 6 +- .../services/CustomerAnalyticsService.java | 29 +- .../services/OrderAnalyticsService.java | 4 - .../services/ProductAnalyticsService.java | 3 +- .../analytics/util/StringToUUIDParser.java | 14 + .../annotations/ValidPagination.java | 25 + .../validators/DateRangeValidator.java | 6 +- .../validators/PaginationValidator.java | 17 + .../CourierAnalyticsControllerTest.java | 653 ++++---- .../FulfillmentReportControllerTest.java | 678 ++++----- ...roductAnalyticsServiceIntegrationTest.java | 1333 +++++++++-------- .../RevenueReportServiceIntegrationTest.java | 319 ++-- .../services/CourierAnalyticsServiceTest.java | 700 ++++----- .../services/ProductAnalyticsServiceTest.java | 465 +++--- .../services/RevenueReportServiceTest.java | 422 +++--- 57 files changed, 2688 insertions(+), 2388 deletions(-) create mode 100644 src/main/java/com/Podzilla/analytics/api/dtos/IDateRangeRequest.java create mode 100644 src/main/java/com/Podzilla/analytics/api/dtos/IPaginationRequest.java create mode 100644 src/main/java/com/Podzilla/analytics/messaging/commands/CommandFactory.java create mode 100644 src/main/java/com/Podzilla/analytics/messaging/commands/user/RegisterCustomerCommand.java create mode 100644 src/main/java/com/Podzilla/analytics/messaging/invokers/InvokerFactory.java create mode 100644 src/main/java/com/Podzilla/analytics/util/StringToUUIDParser.java create mode 100644 src/main/java/com/Podzilla/analytics/validation/annotations/ValidPagination.java create mode 100644 src/main/java/com/Podzilla/analytics/validation/validators/PaginationValidator.java diff --git a/src/main/java/com/Podzilla/analytics/api/controllers/CustomerReportController.java b/src/main/java/com/Podzilla/analytics/api/controllers/CustomerReportController.java index 417f46a..8c167f7 100644 --- a/src/main/java/com/Podzilla/analytics/api/controllers/CustomerReportController.java +++ b/src/main/java/com/Podzilla/analytics/api/controllers/CustomerReportController.java @@ -34,6 +34,7 @@ public List getTopSpenders( request.getStartDate(), request.getEndDate(), request.getPage(), - request.getSize()); + request.getSize() + ); } } diff --git a/src/main/java/com/Podzilla/analytics/api/controllers/FulfillmentReportController.java b/src/main/java/com/Podzilla/analytics/api/controllers/FulfillmentReportController.java index f0dd97f..a737aba 100644 --- a/src/main/java/com/Podzilla/analytics/api/controllers/FulfillmentReportController.java +++ b/src/main/java/com/Podzilla/analytics/api/controllers/FulfillmentReportController.java @@ -39,7 +39,8 @@ public ResponseEntity> getPlaceToShipTime( .getPlaceToShipTimeResponse( req.getStartDate(), req.getEndDate(), - req.getGroupBy()); + req.getGroupBy() + ); return ResponseEntity.ok(reportData); } diff --git a/src/main/java/com/Podzilla/analytics/api/controllers/InventoryReportController.java b/src/main/java/com/Podzilla/analytics/api/controllers/InventoryReportController.java index e0a11c9..8820458 100644 --- a/src/main/java/com/Podzilla/analytics/api/controllers/InventoryReportController.java +++ b/src/main/java/com/Podzilla/analytics/api/controllers/InventoryReportController.java @@ -31,7 +31,8 @@ public class InventoryReportController { + "the total value of inventory " + "grouped by product categories") @GetMapping("/value/by-category") - public List getInventoryValueByCategor() { + public List + getInventoryValueByCategory() { return inventoryAnalyticsService.getInventoryValueByCategory(); } diff --git a/src/main/java/com/Podzilla/analytics/api/controllers/RabbitTesterController.java b/src/main/java/com/Podzilla/analytics/api/controllers/RabbitTesterController.java index 590c8e0..7bf1e97 100644 --- a/src/main/java/com/Podzilla/analytics/api/controllers/RabbitTesterController.java +++ b/src/main/java/com/Podzilla/analytics/api/controllers/RabbitTesterController.java @@ -37,7 +37,10 @@ public void testCourierRegisteredEvent() { @GetMapping("/customer-registered-event") public void testCustomerRegisteredEvent() { - BaseEvent event = new CustomerRegisteredEvent("1", "ahmad"); + BaseEvent event = new CustomerRegisteredEvent( + "27f7f5ca-6729-461e-882a-0c5123889bec", + "7amada" + ); listener.handleUserEvents(event); } diff --git a/src/main/java/com/Podzilla/analytics/api/dtos/DateRangePaginationRequest.java b/src/main/java/com/Podzilla/analytics/api/dtos/DateRangePaginationRequest.java index 580803e..7bede34 100644 --- a/src/main/java/com/Podzilla/analytics/api/dtos/DateRangePaginationRequest.java +++ b/src/main/java/com/Podzilla/analytics/api/dtos/DateRangePaginationRequest.java @@ -1,8 +1,10 @@ package com.Podzilla.analytics.api.dtos; -import java.time.LocalDateTime; -import org.springframework.format.annotation.DateTimeFormat; +import java.time.LocalDate; + import com.Podzilla.analytics.validation.annotations.ValidDateRange; +import com.Podzilla.analytics.validation.annotations.ValidPagination; + import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; @@ -10,21 +12,21 @@ import io.swagger.v3.oas.annotations.media.Schema; @ValidDateRange +@ValidPagination @Getter @AllArgsConstructor -public class DateRangePaginationRequest { +public class DateRangePaginationRequest + implements IDateRangeRequest, IPaginationRequest { @NotNull(message = "startDate is required") - @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) - @Schema(description = "Start date and time of the range " - + "(inclusive)", example = "2024-01-01T00:00:00", required = true) - private LocalDateTime startDate; + @Schema(description = "Start date of the range " + + "(inclusive)", example = "2024-01-01", required = true) + private LocalDate startDate; @NotNull(message = "endDate is required") - @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) - @Schema(description = "End date and time of the range " - + "(inclusive)", example = "2024-01-31T23:59:59", required = true) - private LocalDateTime endDate; + @Schema(description = "End date of the range " + + "(inclusive)", example = "2024-01-31", required = true) + private LocalDate endDate; @Min(value = 0, message = "Page " + "number must be greater than or equal to 0") diff --git a/src/main/java/com/Podzilla/analytics/api/dtos/DateRangeRequest.java b/src/main/java/com/Podzilla/analytics/api/dtos/DateRangeRequest.java index 084b895..586c69f 100644 --- a/src/main/java/com/Podzilla/analytics/api/dtos/DateRangeRequest.java +++ b/src/main/java/com/Podzilla/analytics/api/dtos/DateRangeRequest.java @@ -14,7 +14,7 @@ @ValidDateRange @Getter @AllArgsConstructor -public class DateRangeRequest { +public class DateRangeRequest implements IDateRangeRequest { @NotNull(message = "startDate is required") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) diff --git a/src/main/java/com/Podzilla/analytics/api/dtos/IDateRangeRequest.java b/src/main/java/com/Podzilla/analytics/api/dtos/IDateRangeRequest.java new file mode 100644 index 0000000..114cbd8 --- /dev/null +++ b/src/main/java/com/Podzilla/analytics/api/dtos/IDateRangeRequest.java @@ -0,0 +1,8 @@ +package com.Podzilla.analytics.api.dtos; +import java.time.LocalDate; + + +public interface IDateRangeRequest { + LocalDate getStartDate(); + LocalDate getEndDate(); +} diff --git a/src/main/java/com/Podzilla/analytics/api/dtos/IPaginationRequest.java b/src/main/java/com/Podzilla/analytics/api/dtos/IPaginationRequest.java new file mode 100644 index 0000000..52655c4 --- /dev/null +++ b/src/main/java/com/Podzilla/analytics/api/dtos/IPaginationRequest.java @@ -0,0 +1,6 @@ +package com.Podzilla.analytics.api.dtos; + +public interface IPaginationRequest { + int getPage(); + int getSize(); +} diff --git a/src/main/java/com/Podzilla/analytics/api/dtos/PaginationRequest.java b/src/main/java/com/Podzilla/analytics/api/dtos/PaginationRequest.java index fc650e8..8cb6f79 100644 --- a/src/main/java/com/Podzilla/analytics/api/dtos/PaginationRequest.java +++ b/src/main/java/com/Podzilla/analytics/api/dtos/PaginationRequest.java @@ -2,11 +2,15 @@ import jakarta.validation.constraints.Min; import lombok.AllArgsConstructor; import lombok.Getter; + +import com.Podzilla.analytics.validation.annotations.ValidPagination; + import io.swagger.v3.oas.annotations.media.Schema; +@ValidPagination @Getter @AllArgsConstructor -public class PaginationRequest { +public class PaginationRequest implements IPaginationRequest { @Min(value = 0, message = "Page number " + "must be greater than or equal to 0") diff --git a/src/main/java/com/Podzilla/analytics/api/dtos/courier/CourierAverageRatingResponse.java b/src/main/java/com/Podzilla/analytics/api/dtos/courier/CourierAverageRatingResponse.java index e4a20c5..3a3b506 100644 --- a/src/main/java/com/Podzilla/analytics/api/dtos/courier/CourierAverageRatingResponse.java +++ b/src/main/java/com/Podzilla/analytics/api/dtos/courier/CourierAverageRatingResponse.java @@ -6,6 +6,7 @@ // import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +import java.util.UUID; @Data // @Builder @@ -14,7 +15,7 @@ public class CourierAverageRatingResponse { @Schema(description = "ID of the courier", example = "101") - private Long courierId; + private UUID courierId; @Schema(description = "Full name of the courier", example = "John Doe") private String courierName; @@ -26,13 +27,13 @@ public static Builder builder() { return new Builder(); } public static class Builder { - private Long courierId; + private UUID courierId; private String courierName; private BigDecimal averageRating; public Builder() { } - public Builder courierId(final Long courierId) { + public Builder courierId(final UUID courierId) { this.courierId = courierId; return this; } diff --git a/src/main/java/com/Podzilla/analytics/api/dtos/courier/CourierDeliveryCountResponse.java b/src/main/java/com/Podzilla/analytics/api/dtos/courier/CourierDeliveryCountResponse.java index 1aa5e88..ebe79b6 100644 --- a/src/main/java/com/Podzilla/analytics/api/dtos/courier/CourierDeliveryCountResponse.java +++ b/src/main/java/com/Podzilla/analytics/api/dtos/courier/CourierDeliveryCountResponse.java @@ -5,6 +5,7 @@ import lombok.Data; import lombok.NoArgsConstructor; import io.swagger.v3.oas.annotations.media.Schema; +import java.util.UUID; @Data @Builder @@ -13,7 +14,7 @@ public class CourierDeliveryCountResponse { @Schema(description = "ID of the courier", example = "101") - private Long courierId; + private UUID courierId; @Schema(description = "Full name of the courier", example = "Jane Smith") private String courierName; diff --git a/src/main/java/com/Podzilla/analytics/api/dtos/courier/CourierPerformanceReportResponse.java b/src/main/java/com/Podzilla/analytics/api/dtos/courier/CourierPerformanceReportResponse.java index 9b91740..cfa58fc 100644 --- a/src/main/java/com/Podzilla/analytics/api/dtos/courier/CourierPerformanceReportResponse.java +++ b/src/main/java/com/Podzilla/analytics/api/dtos/courier/CourierPerformanceReportResponse.java @@ -8,7 +8,7 @@ import lombok.NoArgsConstructor; import io.swagger.v3.oas.annotations.media.Schema; - +import java.util.UUID; @Data @Builder @NoArgsConstructor @@ -16,7 +16,7 @@ public class CourierPerformanceReportResponse { @Schema(description = "ID of the courier", example = "105") - private Long courierId; + private UUID courierId; @Schema(description = "Full name of the courier", example = "Ali Hassan") private String courierName; diff --git a/src/main/java/com/Podzilla/analytics/api/dtos/courier/CourierSuccessRateResponse.java b/src/main/java/com/Podzilla/analytics/api/dtos/courier/CourierSuccessRateResponse.java index ffa0758..2c8a8fa 100644 --- a/src/main/java/com/Podzilla/analytics/api/dtos/courier/CourierSuccessRateResponse.java +++ b/src/main/java/com/Podzilla/analytics/api/dtos/courier/CourierSuccessRateResponse.java @@ -6,6 +6,7 @@ import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +import java.util.UUID; @Data @Builder @@ -14,7 +15,7 @@ public class CourierSuccessRateResponse { @Schema(description = "ID of the courier", example = "103") - private Long courierId; + private UUID courierId; @Schema(description = "Full name of the courier", example = "Fatima Ahmed") private String courierName; diff --git a/src/main/java/com/Podzilla/analytics/api/dtos/customer/CustomersTopSpendersResponse.java b/src/main/java/com/Podzilla/analytics/api/dtos/customer/CustomersTopSpendersResponse.java index dd2a4db..e427be1 100644 --- a/src/main/java/com/Podzilla/analytics/api/dtos/customer/CustomersTopSpendersResponse.java +++ b/src/main/java/com/Podzilla/analytics/api/dtos/customer/CustomersTopSpendersResponse.java @@ -5,13 +5,14 @@ import lombok.Data; import lombok.NoArgsConstructor; import lombok.AllArgsConstructor; +import java.util.UUID; @Data @Builder @NoArgsConstructor @AllArgsConstructor public class CustomersTopSpendersResponse { - private Long customerId; + private UUID customerId; private String customerName; private BigDecimal totalSpending; } diff --git a/src/main/java/com/Podzilla/analytics/api/dtos/fulfillment/FulfillmentPlaceToShipRequest.java b/src/main/java/com/Podzilla/analytics/api/dtos/fulfillment/FulfillmentPlaceToShipRequest.java index 6af9a21..3c170d5 100644 --- a/src/main/java/com/Podzilla/analytics/api/dtos/fulfillment/FulfillmentPlaceToShipRequest.java +++ b/src/main/java/com/Podzilla/analytics/api/dtos/fulfillment/FulfillmentPlaceToShipRequest.java @@ -4,6 +4,7 @@ import org.springframework.format.annotation.DateTimeFormat; +import com.Podzilla.analytics.api.dtos.IDateRangeRequest; import com.Podzilla.analytics.validation.annotations.ValidDateRange; import io.swagger.v3.oas.annotations.media.Schema; @@ -16,7 +17,7 @@ @NoArgsConstructor @AllArgsConstructor @ValidDateRange -public class FulfillmentPlaceToShipRequest { +public class FulfillmentPlaceToShipRequest implements IDateRangeRequest { /** * Enum for grouping options in place-to-ship analytics. diff --git a/src/main/java/com/Podzilla/analytics/api/dtos/fulfillment/FulfillmentShipToDeliverRequest.java b/src/main/java/com/Podzilla/analytics/api/dtos/fulfillment/FulfillmentShipToDeliverRequest.java index bb368bc..7e0df6c 100644 --- a/src/main/java/com/Podzilla/analytics/api/dtos/fulfillment/FulfillmentShipToDeliverRequest.java +++ b/src/main/java/com/Podzilla/analytics/api/dtos/fulfillment/FulfillmentShipToDeliverRequest.java @@ -5,6 +5,7 @@ import org.springframework.format.annotation.DateTimeFormat; import com.Podzilla.analytics.validation.annotations.ValidDateRange; +import com.Podzilla.analytics.api.dtos.IDateRangeRequest; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; @@ -20,7 +21,7 @@ @NoArgsConstructor @AllArgsConstructor @ValidDateRange -public class FulfillmentShipToDeliverRequest { +public class FulfillmentShipToDeliverRequest implements IDateRangeRequest { /** * Enum for grouping options in ship-to-deliver analytics. diff --git a/src/main/java/com/Podzilla/analytics/api/dtos/inventory/LowStockProductResponse.java b/src/main/java/com/Podzilla/analytics/api/dtos/inventory/LowStockProductResponse.java index aa1596f..d4715cc 100644 --- a/src/main/java/com/Podzilla/analytics/api/dtos/inventory/LowStockProductResponse.java +++ b/src/main/java/com/Podzilla/analytics/api/dtos/inventory/LowStockProductResponse.java @@ -4,13 +4,14 @@ import lombok.Data; import lombok.NoArgsConstructor; import lombok.AllArgsConstructor; +import java.util.UUID; @Data @Builder @NoArgsConstructor @AllArgsConstructor public class LowStockProductResponse { - private Long productId; + private UUID productId; private String productName; private Long currentQuantity; private Long threshold; diff --git a/src/main/java/com/Podzilla/analytics/api/dtos/order/OrderRegionResponse.java b/src/main/java/com/Podzilla/analytics/api/dtos/order/OrderRegionResponse.java index 4069630..fc924fc 100644 --- a/src/main/java/com/Podzilla/analytics/api/dtos/order/OrderRegionResponse.java +++ b/src/main/java/com/Podzilla/analytics/api/dtos/order/OrderRegionResponse.java @@ -6,7 +6,7 @@ import java.math.BigDecimal; import io.swagger.v3.oas.annotations.media.Schema; - +import java.util.UUID; @Data @Builder @@ -14,8 +14,9 @@ @AllArgsConstructor public class OrderRegionResponse { - @Schema(description = "Region ID", example = "12345") - private Long regionId; + @Schema(description = "Region ID", + example = "4731e9e0-c627-43f9-808a-7e8637abb912") + private UUID regionId; @Schema(description = "city name", example = "Metropolis") private String city; diff --git a/src/main/java/com/Podzilla/analytics/api/dtos/product/TopSellerRequest.java b/src/main/java/com/Podzilla/analytics/api/dtos/product/TopSellerRequest.java index 582737b..db46713 100644 --- a/src/main/java/com/Podzilla/analytics/api/dtos/product/TopSellerRequest.java +++ b/src/main/java/com/Podzilla/analytics/api/dtos/product/TopSellerRequest.java @@ -5,7 +5,9 @@ import org.jetbrains.annotations.NotNull; import org.springframework.format.annotation.DateTimeFormat; + import com.Podzilla.analytics.validation.annotations.ValidDateRange; +import com.Podzilla.analytics.api.dtos.IDateRangeRequest; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.Positive; @@ -19,7 +21,7 @@ @NoArgsConstructor @AllArgsConstructor @Builder -public class TopSellerRequest { +public class TopSellerRequest implements IDateRangeRequest { @NotNull @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) @Schema(description = "Start date for the report (inclusive)", diff --git a/src/main/java/com/Podzilla/analytics/api/dtos/product/TopSellerResponse.java b/src/main/java/com/Podzilla/analytics/api/dtos/product/TopSellerResponse.java index a27fca3..46d5ae4 100644 --- a/src/main/java/com/Podzilla/analytics/api/dtos/product/TopSellerResponse.java +++ b/src/main/java/com/Podzilla/analytics/api/dtos/product/TopSellerResponse.java @@ -8,14 +8,17 @@ import lombok.NoArgsConstructor; import io.swagger.v3.oas.annotations.media.Schema; - +import java.util.UUID; @Data @NoArgsConstructor @AllArgsConstructor // @Builder public class TopSellerResponse { - @Schema(description = "Product ID", example = "101") - private Long productId; + @Schema( + description = "Product ID", + example = "550e8400-e29b-41d4-a716-446655440000" + ) + private UUID productId; @Schema(description = "Product name", example = "Wireless Mouse") private String productName; @Schema(description = "Product category", example = "Electronics") @@ -27,12 +30,12 @@ public static Builder builder() { return new Builder(); } public static class Builder { - private Long productId; + private UUID productId; private String productName; private String category; private BigDecimal value; - public Builder productId(final Long productId) { + public Builder productId(final UUID productId) { this.productId = productId; return this; } diff --git a/src/main/java/com/Podzilla/analytics/api/dtos/revenue/RevenueByCategoryRequest.java b/src/main/java/com/Podzilla/analytics/api/dtos/revenue/RevenueByCategoryRequest.java index 6eaf06e..0d558d1 100644 --- a/src/main/java/com/Podzilla/analytics/api/dtos/revenue/RevenueByCategoryRequest.java +++ b/src/main/java/com/Podzilla/analytics/api/dtos/revenue/RevenueByCategoryRequest.java @@ -5,6 +5,7 @@ import org.springframework.format.annotation.DateTimeFormat; import com.Podzilla.analytics.validation.annotations.ValidDateRange; +import com.Podzilla.analytics.api.dtos.IDateRangeRequest; import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; @@ -18,7 +19,7 @@ @AllArgsConstructor @Builder @Schema(description = "Request parameters for fetching revenue by category") -public class RevenueByCategoryRequest { +public class RevenueByCategoryRequest implements IDateRangeRequest { @NotNull(message = "Start date is required") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) diff --git a/src/main/java/com/Podzilla/analytics/api/dtos/revenue/RevenueSummaryRequest.java b/src/main/java/com/Podzilla/analytics/api/dtos/revenue/RevenueSummaryRequest.java index fb20cde..094fac5 100644 --- a/src/main/java/com/Podzilla/analytics/api/dtos/revenue/RevenueSummaryRequest.java +++ b/src/main/java/com/Podzilla/analytics/api/dtos/revenue/RevenueSummaryRequest.java @@ -5,6 +5,7 @@ import jakarta.validation.constraints.NotNull; import org.springframework.format.annotation.DateTimeFormat; +import com.Podzilla.analytics.api.dtos.IDateRangeRequest; import com.Podzilla.analytics.validation.annotations.ValidDateRange; import io.swagger.v3.oas.annotations.media.Schema; @@ -19,7 +20,7 @@ @AllArgsConstructor @Builder @Schema(description = "Request parameters for revenue summary") -public class RevenueSummaryRequest { +public class RevenueSummaryRequest implements IDateRangeRequest { @NotNull(message = "Start date is required") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) diff --git a/src/main/java/com/Podzilla/analytics/api/projections/courier/CourierPerformanceProjection.java b/src/main/java/com/Podzilla/analytics/api/projections/courier/CourierPerformanceProjection.java index 2c7a4be..904176f 100644 --- a/src/main/java/com/Podzilla/analytics/api/projections/courier/CourierPerformanceProjection.java +++ b/src/main/java/com/Podzilla/analytics/api/projections/courier/CourierPerformanceProjection.java @@ -1,9 +1,10 @@ package com.Podzilla.analytics.api.projections.courier; import java.math.BigDecimal; +import java.util.UUID; public interface CourierPerformanceProjection { - Long getCourierId(); + UUID getCourierId(); String getCourierName(); diff --git a/src/main/java/com/Podzilla/analytics/api/projections/customer/CustomersTopSpendersProjection.java b/src/main/java/com/Podzilla/analytics/api/projections/customer/CustomersTopSpendersProjection.java index 00933ea..27da2a7 100644 --- a/src/main/java/com/Podzilla/analytics/api/projections/customer/CustomersTopSpendersProjection.java +++ b/src/main/java/com/Podzilla/analytics/api/projections/customer/CustomersTopSpendersProjection.java @@ -1,9 +1,10 @@ package com.Podzilla.analytics.api.projections.customer; import java.math.BigDecimal; +import java.util.UUID; public interface CustomersTopSpendersProjection { - Long getCustomerId(); + UUID getCustomerId(); String getCustomerName(); diff --git a/src/main/java/com/Podzilla/analytics/api/projections/inventory/LowStockProductProjection.java b/src/main/java/com/Podzilla/analytics/api/projections/inventory/LowStockProductProjection.java index 23e73c4..afd24f1 100644 --- a/src/main/java/com/Podzilla/analytics/api/projections/inventory/LowStockProductProjection.java +++ b/src/main/java/com/Podzilla/analytics/api/projections/inventory/LowStockProductProjection.java @@ -1,8 +1,8 @@ package com.Podzilla.analytics.api.projections.inventory; - +import java.util.UUID; public interface LowStockProductProjection { - Long getProductId(); + UUID getProductId(); String getProductName(); diff --git a/src/main/java/com/Podzilla/analytics/api/projections/order/OrderRegionProjection.java b/src/main/java/com/Podzilla/analytics/api/projections/order/OrderRegionProjection.java index 8b7816f..b15e7c3 100644 --- a/src/main/java/com/Podzilla/analytics/api/projections/order/OrderRegionProjection.java +++ b/src/main/java/com/Podzilla/analytics/api/projections/order/OrderRegionProjection.java @@ -1,9 +1,10 @@ package com.Podzilla.analytics.api.projections.order; import java.math.BigDecimal; +import java.util.UUID; public interface OrderRegionProjection { - Long getRegionId(); + UUID getRegionId(); String getCity(); String getCountry(); Long getOrderCount(); diff --git a/src/main/java/com/Podzilla/analytics/api/projections/product/TopSellingProductProjection.java b/src/main/java/com/Podzilla/analytics/api/projections/product/TopSellingProductProjection.java index 9a6c165..184fee1 100644 --- a/src/main/java/com/Podzilla/analytics/api/projections/product/TopSellingProductProjection.java +++ b/src/main/java/com/Podzilla/analytics/api/projections/product/TopSellingProductProjection.java @@ -1,9 +1,10 @@ package com.Podzilla.analytics.api.projections.product; import java.math.BigDecimal; +import java.util.UUID; public interface TopSellingProductProjection { - Long getId(); + UUID getId(); String getName(); String getCategory(); BigDecimal getTotalRevenue(); diff --git a/src/main/java/com/Podzilla/analytics/config/DatabaseSeeder.java b/src/main/java/com/Podzilla/analytics/config/DatabaseSeeder.java index 632b1f0..9fed9ad 100644 --- a/src/main/java/com/Podzilla/analytics/config/DatabaseSeeder.java +++ b/src/main/java/com/Podzilla/analytics/config/DatabaseSeeder.java @@ -24,6 +24,7 @@ import java.util.Arrays; import java.util.List; import java.util.Random; +import java.util.UUID; @Component @@ -131,15 +132,21 @@ public void run(final String... args) { private List seedRegions() { Region region1 = regionRepository.save( - Region.builder().city("Metropolis").state("NY") + Region.builder() + .id(UUID.randomUUID()) + .city("Metropolis").state("NY") .country("USA").postalCode("10001") .build()); Region region2 = regionRepository.save( - Region.builder().city("Gotham").state("NJ") + Region.builder() + .id(UUID.randomUUID()) + .city("Gotham").state("NJ") .country("USA").postalCode("07001") .build()); Region region3 = regionRepository.save( - Region.builder().city("Star City").state("CA") + Region.builder() + .id(UUID.randomUUID()) + .city("Star City").state("CA") .country("USA").postalCode("90210") .build()); return Arrays.asList(region1, region2, region3); @@ -147,18 +154,22 @@ private List seedRegions() { private List seedProducts() { Product prod1 = productRepository.save(Product.builder() + .id(UUID.randomUUID()) .name("Podzilla Pro").category("Electronics") .cost(PRICE_PROD1) .lowStockThreshold(LOW_STOCK_PROD1).build()); Product prod2 = productRepository.save(Product.builder() + .id(UUID.randomUUID()) .name("Podzilla Mini").category("Electronics") .cost(PRICE_PROD2) .lowStockThreshold(LOW_STOCK_PROD2).build()); Product prod3 = productRepository.save(Product.builder() + .id(UUID.randomUUID()) .name("Charging Case").category("Accessories") .cost(PRICE_PROD3) .lowStockThreshold(LOW_STOCK_PROD3).build()); Product prod4 = productRepository.save(Product.builder() + .id(UUID.randomUUID()) .name("Podzilla Cover").category("Accessories") .cost(PRICE_PROD4) .lowStockThreshold(LOW_STOCK_PROD4).build()); @@ -167,24 +178,36 @@ private List seedProducts() { private List seedCouriers() { Courier courier1 = courierRepository.save( - Courier.builder().name("Speedy Delivery Inc.") + Courier.builder() + .id(UUID.randomUUID()) + .name("Speedy Delivery Inc.") .status(Courier.CourierStatus.ACTIVE).build()); Courier courier2 = courierRepository.save( - Courier.builder().name("Reliable Couriers Co.") + Courier.builder() + .id(UUID.randomUUID()) + .name("Reliable Couriers Co.") .status(Courier.CourierStatus.ACTIVE).build()); Courier courier3 = courierRepository.save( - Courier.builder().name("Overnight Express") + Courier.builder() + .id(UUID.randomUUID()) + .name("Overnight Express") .status(Courier.CourierStatus.INACTIVE).build()); return Arrays.asList(courier1, courier2, courier3); } private List seedCustomers() { Customer cust1 = customerRepository.save( - Customer.builder().name("Alice Smith").build()); + Customer.builder() + .id(UUID.randomUUID()) + .name("Alice Smith").build()); Customer cust2 = customerRepository.save( - Customer.builder().name("Bob Johnson").build()); + Customer.builder() + .id(UUID.randomUUID()) + .name("Bob Johnson").build()); Customer cust3 = customerRepository.save( - Customer.builder().name("Charlie Brown").build()); + Customer.builder() + .id(UUID.randomUUID()) + .name("Charlie Brown").build()); return Arrays.asList(cust1, cust2, cust3); } @@ -199,6 +222,7 @@ private void seedOrders( LocalDateTime placed1 = today.minusDays(ORDER_1_DAYS_PRIOR) .atTime(ORDER_1_HOUR, ORDER_1_MINUTE); Order order1 = Order.builder() + .id(UUID.randomUUID()) .customer(customers.get(0)).courier(couriers.get(0)) .region(regions.get(0)) .status(Order.OrderStatus.COMPLETED) @@ -213,9 +237,11 @@ private void seedOrders( .courierRating(RATING_GOOD) .build(); SalesLineItem itemFirstOrderFirst = SalesLineItem.builder() + .id(UUID.randomUUID()) .order(order1).product(products.get(0)).quantity(1) .pricePerUnit(PRICE_PROD1).build(); SalesLineItem itemFirstOrderSecond = SalesLineItem.builder() + .id(UUID.randomUUID()) .order(order1).product(products.get(2)).quantity(2) .pricePerUnit(PRICE_PROD3).build(); order1.setSalesLineItems(Arrays.asList(itemFirstOrderFirst, @@ -234,6 +260,7 @@ private void seedOrders( LocalDateTime placed2 = today.minusDays(ORDER_2_DAYS_PRIOR) .atTime(ORDER_2_HOUR, ORDER_2_MINUTE); Order order2 = Order.builder() + .id(UUID.randomUUID()) .customer(customers.get(1)).courier(couriers.get(1)) .region(regions.get(1)) .status(Order.OrderStatus.SHIPPED) @@ -245,6 +272,7 @@ private void seedOrders( .courierRating(null).failureReason(null) .build(); SalesLineItem itemSecondOrderFirst = SalesLineItem.builder() + .id(UUID.randomUUID()) .order(order2).product(products.get(1)).quantity(1) .pricePerUnit(PRICE_PROD2).build(); order2.setSalesLineItems(List.of(itemSecondOrderFirst)); @@ -259,6 +287,7 @@ private void seedOrders( LocalDateTime placed3 = today.minusDays(ORDER_3_DAYS_PRIOR) .atTime(ORDER_3_HOUR, ORDER_3_MINUTE); Order order3 = Order.builder() + .id(UUID.randomUUID()) .customer(customers.get(0)).courier(couriers.get(0)) .region(regions.get(2)) .status(Order.OrderStatus.FAILED) @@ -272,6 +301,7 @@ private void seedOrders( .courierRating(RATING_POOR) .build(); SalesLineItem itemThirdOrderFirst = SalesLineItem.builder() + .id(UUID.randomUUID()) .order(order3).product(products.get(INDEX_THREE)).quantity(1) .pricePerUnit(PRICE_PROD4).build(); order3.setSalesLineItems(List.of(itemThirdOrderFirst)); @@ -285,6 +315,7 @@ private void seedOrders( LocalDateTime placed4 = today.minusDays(ORDER_4_DAYS_PRIOR) .atTime(ORDER_4_HOUR, ORDER_4_MINUTE); Order order4 = Order.builder() + .id(UUID.randomUUID()) .customer(customers.get(2)).courier(couriers.get(1)) .region(regions.get(0)) .status(Order.OrderStatus.COMPLETED) @@ -297,9 +328,11 @@ private void seedOrders( .courierRating(RATING_EXCELLENT) .build(); SalesLineItem itemFourthOrderFirst = SalesLineItem.builder() + .id(UUID.randomUUID()) .order(order4).product(products.get(0)).quantity(1) .pricePerUnit(PRICE_PROD1).build(); SalesLineItem itemFourthOrderSecond = SalesLineItem.builder() + .id(UUID.randomUUID()) .order(order4).product(products.get(INDEX_THREE)).quantity(1) .pricePerUnit(PRICE_PROD4).build(); order4.setSalesLineItems(Arrays.asList(itemFourthOrderFirst, @@ -331,6 +364,7 @@ private void seedInventorySnapshot( final Product product, final int range, final int quantity) { inventorySnapshotRepository.save( InventorySnapshot.builder() + .id(UUID.randomUUID()) .product(product) .quantity(random.nextInt(range) + product.getLowStockThreshold()) @@ -339,6 +373,7 @@ private void seedInventorySnapshot( .build()); inventorySnapshotRepository.save( InventorySnapshot.builder() + .id(UUID.randomUUID()) .product(product) .quantity(random.nextInt(quantity) + product.getLowStockThreshold()) diff --git a/src/main/java/com/Podzilla/analytics/messaging/AnalyticsRabbitListener.java b/src/main/java/com/Podzilla/analytics/messaging/AnalyticsRabbitListener.java index 78a49bd..d8409ad 100644 --- a/src/main/java/com/Podzilla/analytics/messaging/AnalyticsRabbitListener.java +++ b/src/main/java/com/Podzilla/analytics/messaging/AnalyticsRabbitListener.java @@ -1,9 +1,9 @@ package com.Podzilla.analytics.messaging; -import org.springframework.amqp.rabbit.annotation.RabbitListener; +// import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.beans.factory.annotation.Autowired; -import com.podzilla.mq.EventsConstants; +// import com.podzilla.mq.EventsConstants; import com.podzilla.mq.events.BaseEvent; import org.springframework.stereotype.Service; @@ -16,23 +16,23 @@ public class AnalyticsRabbitListener { @Autowired private InvokerDispatcher dispatcher; - @RabbitListener( - queues = EventsConstants.ANALYTICS_USER_EVENT_QUEUE - ) + // @RabbitListener( + // queues = EventsConstants.ANALYTICS_USER_EVENT_QUEUE + // ) public void handleUserEvents(final BaseEvent userEvent) { dispatcher.dispatch(userEvent); } - @RabbitListener( - queues = EventsConstants.ANALYTICS_ORDER_EVENT_QUEUE - ) + // @RabbitListener( + // queues = EventsConstants.ANALYTICS_ORDER_EVENT_QUEUE + // ) public void handleOrderEvents(final BaseEvent orderEvent) { dispatcher.dispatch(orderEvent); } - @RabbitListener( - queues = EventsConstants.ANALYTICS_INVENTORY_EVENT_QUEUE - ) + // @RabbitListener( + // queues = EventsConstants.ANALYTICS_INVENTORY_EVENT_QUEUE + // ) public void handleInventoryEvents(final BaseEvent inventoryEvent) { dispatcher.dispatch(inventoryEvent); } diff --git a/src/main/java/com/Podzilla/analytics/messaging/InvokerDispatcherConfig.java b/src/main/java/com/Podzilla/analytics/messaging/InvokerDispatcherConfig.java index 2cd24e3..759ff81 100644 --- a/src/main/java/com/Podzilla/analytics/messaging/InvokerDispatcherConfig.java +++ b/src/main/java/com/Podzilla/analytics/messaging/InvokerDispatcherConfig.java @@ -1,16 +1,17 @@ package com.Podzilla.analytics.messaging; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import com.Podzilla.analytics.messaging.invokers.user.CourierRegisteredInvoker; -import com.Podzilla.analytics.messaging.invokers.user.CustomerRegisteredInvoker; import com.Podzilla.analytics.messaging.invokers.order.OrderAssignedToCourierInvoker; import com.Podzilla.analytics.messaging.invokers.order.OrderDeliveryFailedInvoker; import com.Podzilla.analytics.messaging.invokers.order.OrderPlacedInvoker; import com.Podzilla.analytics.messaging.invokers.order.OrderCancelledInvoker; import com.Podzilla.analytics.messaging.invokers.order.OrderDeliveredInvoker; import com.Podzilla.analytics.messaging.invokers.order.OrderOutForDeliveryInvoker; +import com.Podzilla.analytics.messaging.invokers.InvokerFactory; import com.Podzilla.analytics.messaging.invokers.inventory.InventoryUpdatedInvoker; import com.Podzilla.analytics.messaging.invokers.inventory.ProductCreatedInvoker; @@ -29,6 +30,13 @@ @Configuration public class InvokerDispatcherConfig { + @Autowired + private final InvokerFactory invokerFactory; + + public InvokerDispatcherConfig(final InvokerFactory invokerFactory) { + this.invokerFactory = invokerFactory; + } + @Bean public InvokerDispatcher invokerDispatcher() { InvokerDispatcher dispatcher = new InvokerDispatcher(); @@ -50,7 +58,7 @@ private void registerUserInvokers( dispatcher.registerInvoker( CustomerRegisteredEvent.class, - new CustomerRegisteredInvoker() + invokerFactory.createRegisterCustomerInvoker() ); } diff --git a/src/main/java/com/Podzilla/analytics/messaging/commands/CommandFactory.java b/src/main/java/com/Podzilla/analytics/messaging/commands/CommandFactory.java new file mode 100644 index 0000000..23fc0ba --- /dev/null +++ b/src/main/java/com/Podzilla/analytics/messaging/commands/CommandFactory.java @@ -0,0 +1,26 @@ +package com.Podzilla.analytics.messaging.commands; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import com.Podzilla.analytics.services.CustomerAnalyticsService; +import com.Podzilla.analytics.messaging.commands.user.RegisterCustomerCommand; + +@Component +public class CommandFactory { + + @Autowired + private CustomerAnalyticsService customerAnalyticsService; + + public RegisterCustomerCommand createRegisterCustomerCommand( + final String customerId, + final String customerName + ) { + return RegisterCustomerCommand.builder() + .customerAnalyticsService(customerAnalyticsService) + .customerId(customerId) + .customerName(customerName) + .build(); + } + +} diff --git a/src/main/java/com/Podzilla/analytics/messaging/commands/user/RegisterCustomerCommand.java b/src/main/java/com/Podzilla/analytics/messaging/commands/user/RegisterCustomerCommand.java new file mode 100644 index 0000000..6d05d5b --- /dev/null +++ b/src/main/java/com/Podzilla/analytics/messaging/commands/user/RegisterCustomerCommand.java @@ -0,0 +1,23 @@ +package com.Podzilla.analytics.messaging.commands.user; + +import com.Podzilla.analytics.messaging.commands.Command; +import com.Podzilla.analytics.services.CustomerAnalyticsService; + +import lombok.Builder; + +@Builder +public class RegisterCustomerCommand implements Command { + + private CustomerAnalyticsService customerAnalyticsService; + private String customerId; + private String customerName; + + @Override + public void execute() { + customerAnalyticsService.saveCustomer( + customerId, + customerName + ); + } + +} diff --git a/src/main/java/com/Podzilla/analytics/messaging/invokers/InvokerFactory.java b/src/main/java/com/Podzilla/analytics/messaging/invokers/InvokerFactory.java new file mode 100644 index 0000000..c9d6901 --- /dev/null +++ b/src/main/java/com/Podzilla/analytics/messaging/invokers/InvokerFactory.java @@ -0,0 +1,22 @@ +package com.Podzilla.analytics.messaging.invokers; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import com.Podzilla.analytics.messaging.commands.CommandFactory; +import com.Podzilla.analytics.messaging.invokers.user.CustomerRegisteredInvoker; + +@Component +public class InvokerFactory { + + @Autowired + private final CommandFactory commandFactory; + + public InvokerFactory(final CommandFactory commandFactory) { + this.commandFactory = commandFactory; + } + + public CustomerRegisteredInvoker createRegisterCustomerInvoker() { + return new CustomerRegisteredInvoker(commandFactory); + } +} diff --git a/src/main/java/com/Podzilla/analytics/messaging/invokers/user/CustomerRegisteredInvoker.java b/src/main/java/com/Podzilla/analytics/messaging/invokers/user/CustomerRegisteredInvoker.java index 61b743f..0c0ca4a 100644 --- a/src/main/java/com/Podzilla/analytics/messaging/invokers/user/CustomerRegisteredInvoker.java +++ b/src/main/java/com/Podzilla/analytics/messaging/invokers/user/CustomerRegisteredInvoker.java @@ -1,16 +1,29 @@ package com.Podzilla.analytics.messaging.invokers.user; +import org.springframework.beans.factory.annotation.Autowired; + +import com.Podzilla.analytics.messaging.commands.CommandFactory; import com.Podzilla.analytics.messaging.invokers.Invoker; import com.podzilla.mq.events.CustomerRegisteredEvent; +import com.Podzilla.analytics.messaging.commands.user.RegisterCustomerCommand; public class CustomerRegisteredInvoker implements Invoker { + @Autowired + private final CommandFactory commandFactory; + public CustomerRegisteredInvoker(final CommandFactory commandFactory) { + this.commandFactory = commandFactory; + } + @Override public void invoke(final CustomerRegisteredEvent event) { - // create a command and call its execute method - System.out.println("Customer Registered Event Invoked: " + event); + RegisterCustomerCommand command = commandFactory + .createRegisterCustomerCommand( + event.getCustomerId(), + event.getName() + ); + command.execute(); } - } diff --git a/src/main/java/com/Podzilla/analytics/models/Courier.java b/src/main/java/com/Podzilla/analytics/models/Courier.java index 53c82d1..9bb70da 100644 --- a/src/main/java/com/Podzilla/analytics/models/Courier.java +++ b/src/main/java/com/Podzilla/analytics/models/Courier.java @@ -3,14 +3,13 @@ import jakarta.persistence.Entity; import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.Table; import lombok.AllArgsConstructor; // import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +import java.util.UUID; @Entity @Table(name = "couriers") @@ -20,8 +19,7 @@ @AllArgsConstructor public class Courier { @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; + private UUID id; private String name; @Enumerated(EnumType.STRING) @@ -38,12 +36,12 @@ public static Builder builder() { } public static class Builder { - private Long id; + private UUID id; private String name; private CourierStatus status; public Builder() { } - public Builder id(final Long id) { + public Builder id(final UUID id) { this.id = id; return this; } diff --git a/src/main/java/com/Podzilla/analytics/models/Customer.java b/src/main/java/com/Podzilla/analytics/models/Customer.java index a71690f..19839c5 100644 --- a/src/main/java/com/Podzilla/analytics/models/Customer.java +++ b/src/main/java/com/Podzilla/analytics/models/Customer.java @@ -1,14 +1,13 @@ package com.Podzilla.analytics.models; import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.Table; import lombok.AllArgsConstructor; // import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +import java.util.UUID; @Entity @Table(name = "customers") @@ -18,20 +17,19 @@ @AllArgsConstructor public class Customer { @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; + private UUID id; private String name; public static Builder builder() { return new Builder(); } public static class Builder { - private Long id; + private UUID id; private String name; public Builder() { } - public Builder id(final Long id) { + public Builder id(final UUID id) { this.id = id; return this; } diff --git a/src/main/java/com/Podzilla/analytics/models/InventorySnapshot.java b/src/main/java/com/Podzilla/analytics/models/InventorySnapshot.java index 8e19cb3..3546f72 100644 --- a/src/main/java/com/Podzilla/analytics/models/InventorySnapshot.java +++ b/src/main/java/com/Podzilla/analytics/models/InventorySnapshot.java @@ -2,8 +2,6 @@ import java.time.LocalDateTime; import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; @@ -12,6 +10,7 @@ // import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +import java.util.UUID; @Entity @Table(name = "inventory_snapshots") @@ -21,8 +20,7 @@ @AllArgsConstructor public class InventorySnapshot { @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; + private UUID id; private LocalDateTime timestamp; @@ -36,14 +34,14 @@ public static Builder builder() { return new Builder(); } public static class Builder { - private Long id; + private UUID id; private LocalDateTime timestamp; private Product product; private int quantity; public Builder() { } - public Builder id(final Long id) { + public Builder id(final UUID id) { this.id = id; return this; } diff --git a/src/main/java/com/Podzilla/analytics/models/Order.java b/src/main/java/com/Podzilla/analytics/models/Order.java index 92fa0c7..6929a9b 100644 --- a/src/main/java/com/Podzilla/analytics/models/Order.java +++ b/src/main/java/com/Podzilla/analytics/models/Order.java @@ -9,8 +9,6 @@ import jakarta.persistence.Entity; import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; @@ -20,6 +18,7 @@ // import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +import java.util.UUID; @Entity @Table(name = "orders") @@ -29,8 +28,7 @@ @AllArgsConstructor public class Order { @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; + private UUID id; private BigDecimal totalAmount; private LocalDateTime orderPlacedTimestamp; @@ -75,7 +73,7 @@ public static Builder builder() { } public static class Builder { - private Long id; + private UUID id; private BigDecimal totalAmount; private LocalDateTime orderPlacedTimestamp; private LocalDateTime shippedTimestamp; @@ -92,7 +90,7 @@ public static class Builder { public Builder() { } - public Builder id(final Long id) { + public Builder id(final UUID id) { this.id = id; return this; } diff --git a/src/main/java/com/Podzilla/analytics/models/Product.java b/src/main/java/com/Podzilla/analytics/models/Product.java index 5dd6ae6..c5fe59c 100644 --- a/src/main/java/com/Podzilla/analytics/models/Product.java +++ b/src/main/java/com/Podzilla/analytics/models/Product.java @@ -3,14 +3,13 @@ import java.math.BigDecimal; import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.Table; import lombok.AllArgsConstructor; // import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +import java.util.UUID; @Entity @Table(name = "products") @@ -20,8 +19,7 @@ @AllArgsConstructor public class Product { @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; + private UUID id; private String name; private String category; private BigDecimal cost; @@ -32,7 +30,7 @@ public static Builder builder() { } public static class Builder { - private Long id; + private UUID id; private String name; private String category; private BigDecimal cost; @@ -40,7 +38,7 @@ public static class Builder { public Builder() { } - public Builder id(final Long id) { + public Builder id(final UUID id) { this.id = id; return this; } diff --git a/src/main/java/com/Podzilla/analytics/models/Region.java b/src/main/java/com/Podzilla/analytics/models/Region.java index 7ce52bf..5d17373 100644 --- a/src/main/java/com/Podzilla/analytics/models/Region.java +++ b/src/main/java/com/Podzilla/analytics/models/Region.java @@ -1,14 +1,13 @@ package com.Podzilla.analytics.models; import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.Table; import lombok.AllArgsConstructor; // import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +import java.util.UUID; @Entity @Table(name = "regions") @@ -18,8 +17,7 @@ @AllArgsConstructor public class Region { @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; + private UUID id; private String city; private String state; private String country; @@ -30,7 +28,7 @@ public static Builder builder() { } public static class Builder { - private Long id; + private UUID id; private String city; private String state; private String country; @@ -38,7 +36,7 @@ public static class Builder { public Builder() { } - public Builder id(final Long id) { + public Builder id(final UUID id) { this.id = id; return this; } diff --git a/src/main/java/com/Podzilla/analytics/models/SalesLineItem.java b/src/main/java/com/Podzilla/analytics/models/SalesLineItem.java index d9dc908..e7efea8 100644 --- a/src/main/java/com/Podzilla/analytics/models/SalesLineItem.java +++ b/src/main/java/com/Podzilla/analytics/models/SalesLineItem.java @@ -2,8 +2,6 @@ import java.math.BigDecimal; import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; @@ -12,6 +10,7 @@ // import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +import java.util.UUID; @Entity @Table(name = "sales_line_items") @@ -21,8 +20,7 @@ @AllArgsConstructor public class SalesLineItem { @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; + private UUID id; private int quantity; private BigDecimal pricePerUnit; @@ -40,7 +38,7 @@ public static Builder builder() { } public static class Builder { - private Long id; + private UUID id; private int quantity; private BigDecimal pricePerUnit; private Product product; @@ -48,7 +46,7 @@ public static class Builder { public Builder() { } - public Builder id(final Long id) { + public Builder id(final UUID id) { this.id = id; return this; } diff --git a/src/main/java/com/Podzilla/analytics/repositories/OrderRepository.java b/src/main/java/com/Podzilla/analytics/repositories/OrderRepository.java index ae6118b..ef7b506 100644 --- a/src/main/java/com/Podzilla/analytics/repositories/OrderRepository.java +++ b/src/main/java/com/Podzilla/analytics/repositories/OrderRepository.java @@ -20,8 +20,8 @@ public interface OrderRepository extends JpaRepository { @Query(value = "SELECT 'OVERALL' as groupByValue, " - + "AVG(TIMESTAMPDIFF(SECOND, o.order_placed_timestamp, " - + "o.shipped_timestamp)) as averageDuration " + + "AVG(EXTRACT(EPOCH FROM (o.shipped_timestamp - " + + "o.order_placed_timestamp)) / 86400) as averageDuration " + "FROM orders o " + "WHERE o.order_placed_timestamp BETWEEN :startDate AND :endDate " + "AND o.shipped_timestamp IS NOT NULL", nativeQuery = true) @@ -30,8 +30,8 @@ FulfillmentTimeProjection findPlaceToShipTimeOverall( @Param("endDate") LocalDateTime endDate); @Query(value = "SELECT CONCAT('RegionID_', o.region_id) as groupByValue, " - + "AVG(TIMESTAMPDIFF(SECOND, o.order_placed_timestamp, " - + "o.shipped_timestamp)) as averageDuration " + + "AVG(EXTRACT(EPOCH FROM (o.shipped_timestamp - " + + "o.order_placed_timestamp)) / 86400) as averageDuration " + "FROM orders o " + "WHERE o.order_placed_timestamp BETWEEN :startDate AND :endDate " + "AND o.shipped_timestamp IS NOT NULL " @@ -41,8 +41,8 @@ List findPlaceToShipTimeByRegion( @Param("endDate") LocalDateTime endDate); @Query(value = "SELECT 'OVERALL' as groupByValue, " - + "AVG(TIMESTAMPDIFF(SECOND, o.shipped_timestamp, " - + "o.delivered_timestamp)) as averageDuration " + + "AVG(EXTRACT(EPOCH FROM (o.delivered_timestamp " + + "- o.shipped_timestamp)) / 86400) as averageDuration " + "FROM orders o " + "WHERE o.shipped_timestamp BETWEEN :startDate AND :endDate " + "AND o.delivered_timestamp IS NOT NULL " @@ -52,8 +52,8 @@ FulfillmentTimeProjection findShipToDeliverTimeOverall( @Param("endDate") LocalDateTime endDate); @Query(value = "SELECT CONCAT('RegionID_', o.region_id) as groupByValue, " - + "AVG(TIMESTAMPDIFF(SECOND, o.shipped_timestamp, " - + "o.delivered_timestamp)) as averageDuration " + + "AVG(EXTRACT(EPOCH FROM (o.delivered_timestamp - " + + "o.shipped_timestamp)) / 86400) as averageDuration " + "FROM orders o " + "WHERE o.shipped_timestamp BETWEEN :startDate AND :endDate " + "AND o.delivered_timestamp IS NOT NULL " @@ -64,8 +64,8 @@ List findShipToDeliverTimeByRegion( @Param("endDate") LocalDateTime endDate); @Query(value = "SELECT CONCAT('CourierID_', o.courier_id) as groupByValue, " - + "AVG(TIMESTAMPDIFF(SECOND, o.shipped_timestamp, " - + "o.delivered_timestamp)) as averageDuration " + + "AVG(EXTRACT(EPOCH FROM (o.delivered_timestamp - " + + "o.shipped_timestamp)) / 86400) as averageDuration " + "FROM orders o " + "WHERE o.shipped_timestamp BETWEEN :startDate AND :endDate " + "AND o.delivered_timestamp IS NOT NULL " diff --git a/src/main/java/com/Podzilla/analytics/services/CourierAnalyticsService.java b/src/main/java/com/Podzilla/analytics/services/CourierAnalyticsService.java index 8376613..7da075b 100644 --- a/src/main/java/com/Podzilla/analytics/services/CourierAnalyticsService.java +++ b/src/main/java/com/Podzilla/analytics/services/CourierAnalyticsService.java @@ -24,13 +24,15 @@ public class CourierAnalyticsService { private List getCourierPerformanceData( final LocalDate startDate, - final LocalDate endDate) { + final LocalDate endDate +) { LocalDateTime startDateTime = startDate.atStartOfDay(); LocalDateTime endDateTime = endDate.atTime(LocalTime.MAX); return courierRepository.findCourierPerformanceBetweenDates( startDateTime, - endDateTime); + endDateTime + ); } public List getCourierDeliveryCounts( diff --git a/src/main/java/com/Podzilla/analytics/services/CustomerAnalyticsService.java b/src/main/java/com/Podzilla/analytics/services/CustomerAnalyticsService.java index aab2d88..0afeebb 100644 --- a/src/main/java/com/Podzilla/analytics/services/CustomerAnalyticsService.java +++ b/src/main/java/com/Podzilla/analytics/services/CustomerAnalyticsService.java @@ -5,11 +5,16 @@ import com.Podzilla.analytics.api.dtos.customer.CustomersTopSpendersResponse; import com.Podzilla.analytics.repositories.CustomerRepository; +import com.Podzilla.analytics.util.DatetimeFormatter; +import com.Podzilla.analytics.util.StringToUUIDParser; +import com.Podzilla.analytics.models.Customer; import lombok.RequiredArgsConstructor; import java.time.LocalDateTime; +import java.time.LocalDate; import java.util.List; +import java.util.UUID; @Service @RequiredArgsConstructor @@ -17,13 +22,17 @@ public class CustomerAnalyticsService { private final CustomerRepository customerRepository; public List getTopSpenders( - final LocalDateTime startDate, - final LocalDateTime endDate, + final LocalDate startDate, + final LocalDate endDate, final int page, final int size) { + LocalDateTime startDateTime = DatetimeFormatter + .convertStartDateToDatetime(startDate); + LocalDateTime endDateTime = DatetimeFormatter + .convertEndDateToDatetime(endDate); PageRequest pageRequest = PageRequest.of(page, size); List topSpenders = customerRepository -.findTopSpenders(startDate, endDate, pageRequest) +.findTopSpenders(startDateTime, endDateTime, pageRequest) .stream() .map(row -> CustomersTopSpendersResponse.builder() .customerId(row.getCustomerId()) @@ -33,4 +42,18 @@ public List getTopSpenders( .toList(); return topSpenders; } + + public void saveCustomer( + final String customerId, + final String customerName + ) { + UUID id = StringToUUIDParser.parseStringToUUID(customerId); + Customer customer = Customer.builder() + .id(id) + .name(customerName) + .build(); + System.out.println("Customer object created: " + + customer.getName() + " with ID: " + customer.getId()); + customerRepository.save(customer); + } } diff --git a/src/main/java/com/Podzilla/analytics/services/OrderAnalyticsService.java b/src/main/java/com/Podzilla/analytics/services/OrderAnalyticsService.java index 9af3233..b064d0f 100644 --- a/src/main/java/com/Podzilla/analytics/services/OrderAnalyticsService.java +++ b/src/main/java/com/Podzilla/analytics/services/OrderAnalyticsService.java @@ -33,12 +33,8 @@ public List getOrdersByRegion( DatetimeFormatter.convertStartDateToDatetime(startDate); LocalDateTime endDateTime = DatetimeFormatter.convertEndDateToDatetime(endDate); - System.out.println("Start date a1a1: " + startDate); - System.out.println("End date b1b1: " + endDate); List ordersByRegion = orderRepository.findOrdersByRegion(startDateTime, endDateTime); - System.out.println("Start date a2a2: " + startDate); - System.out.println("End date b2b2: " + endDate); return ordersByRegion.stream() .map(data -> OrderRegionResponse.builder() .regionId(data.getRegionId()) diff --git a/src/main/java/com/Podzilla/analytics/services/ProductAnalyticsService.java b/src/main/java/com/Podzilla/analytics/services/ProductAnalyticsService.java index 3cb64ba..da64c69 100644 --- a/src/main/java/com/Podzilla/analytics/services/ProductAnalyticsService.java +++ b/src/main/java/com/Podzilla/analytics/services/ProductAnalyticsService.java @@ -36,7 +36,8 @@ public List getTopSellers( final LocalDate startDate, final LocalDate endDate, final Integer limit, - final SortBy sortBy) { + final SortBy sortBy +) { final String sortByString = sortBy != null ? sortBy.name() : SortBy.REVENUE.name(); diff --git a/src/main/java/com/Podzilla/analytics/util/StringToUUIDParser.java b/src/main/java/com/Podzilla/analytics/util/StringToUUIDParser.java new file mode 100644 index 0000000..678cbb8 --- /dev/null +++ b/src/main/java/com/Podzilla/analytics/util/StringToUUIDParser.java @@ -0,0 +1,14 @@ +package com.Podzilla.analytics.util; + +import java.util.UUID; + +public class StringToUUIDParser { + public static UUID parseStringToUUID(final String str) { + try { + return UUID.fromString(str); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Invalid input: " + str, e); + } + } + +} diff --git a/src/main/java/com/Podzilla/analytics/validation/annotations/ValidPagination.java b/src/main/java/com/Podzilla/analytics/validation/annotations/ValidPagination.java new file mode 100644 index 0000000..3f0b745 --- /dev/null +++ b/src/main/java/com/Podzilla/analytics/validation/annotations/ValidPagination.java @@ -0,0 +1,25 @@ +package com.Podzilla.analytics.validation.annotations; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import com.Podzilla.analytics.validation.validators.PaginationValidator; + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; + +@Documented +@Constraint(validatedBy = PaginationValidator.class) +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface ValidPagination { + String message() default "Page must be greater than or equal to 0 " + + "and size must be greater than 0"; + + Class[] groups() default {}; + + Class[] payload() default {}; +} diff --git a/src/main/java/com/Podzilla/analytics/validation/validators/DateRangeValidator.java b/src/main/java/com/Podzilla/analytics/validation/validators/DateRangeValidator.java index 079bd20..eddcd1e 100644 --- a/src/main/java/com/Podzilla/analytics/validation/validators/DateRangeValidator.java +++ b/src/main/java/com/Podzilla/analytics/validation/validators/DateRangeValidator.java @@ -1,15 +1,15 @@ package com.Podzilla.analytics.validation.validators; -import com.Podzilla.analytics.api.dtos.DateRangeRequest; +import com.Podzilla.analytics.api.dtos.IDateRangeRequest; import com.Podzilla.analytics.validation.annotations.ValidDateRange; import jakarta.validation.ConstraintValidator; import jakarta.validation.ConstraintValidatorContext; public final class DateRangeValidator implements - ConstraintValidator { + ConstraintValidator { @Override - public boolean isValid(final DateRangeRequest request, + public boolean isValid(final IDateRangeRequest request, final ConstraintValidatorContext context) { if (request.getStartDate() == null || request.getEndDate() == null) { return true; diff --git a/src/main/java/com/Podzilla/analytics/validation/validators/PaginationValidator.java b/src/main/java/com/Podzilla/analytics/validation/validators/PaginationValidator.java new file mode 100644 index 0000000..a9bad9f --- /dev/null +++ b/src/main/java/com/Podzilla/analytics/validation/validators/PaginationValidator.java @@ -0,0 +1,17 @@ +package com.Podzilla.analytics.validation.validators; + +import com.Podzilla.analytics.api.dtos.IPaginationRequest; +import com.Podzilla.analytics.validation.annotations.ValidPagination; +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; + +public final class PaginationValidator implements + ConstraintValidator { + + @Override + public boolean isValid(final IPaginationRequest request, + final ConstraintValidatorContext context) { + return request.getPage() >= 0 && request.getSize() > 0; + } + +} diff --git a/src/test/java/com/Podzilla/analytics/controllers/CourierAnalyticsControllerTest.java b/src/test/java/com/Podzilla/analytics/controllers/CourierAnalyticsControllerTest.java index f383c56..8cb17a8 100644 --- a/src/test/java/com/Podzilla/analytics/controllers/CourierAnalyticsControllerTest.java +++ b/src/test/java/com/Podzilla/analytics/controllers/CourierAnalyticsControllerTest.java @@ -1,321 +1,332 @@ -package com.Podzilla.analytics.controllers; - -import java.math.BigDecimal; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; - -import jakarta.persistence.EntityManager; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.result.MockMvcResultHandlers; -import org.springframework.transaction.annotation.Transactional; - -import com.Podzilla.analytics.models.Courier; -import com.Podzilla.analytics.models.Customer; -import com.Podzilla.analytics.models.Order; -import com.Podzilla.analytics.models.Region; -import com.Podzilla.analytics.repositories.CourierRepository; -import com.Podzilla.analytics.repositories.CustomerRepository; -import com.Podzilla.analytics.repositories.OrderRepository; -import com.Podzilla.analytics.repositories.RegionRepository; - -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; -import static org.hamcrest.Matchers.*; - -@SpringBootTest -@AutoConfigureMockMvc -@Transactional -@ActiveProfiles("test") -public class CourierAnalyticsControllerTest { - - @Autowired - private MockMvc mockMvc; - - @Autowired - private CourierRepository courierRepository; - @Autowired - private CustomerRepository customerRepository; - @Autowired - private RegionRepository regionRepository; - @Autowired - private OrderRepository orderRepository; - - @Autowired - private EntityManager entityManager; - - private static final DateTimeFormatter ISO_LOCAL_DATE = DateTimeFormatter.ISO_LOCAL_DATE; - - private Customer customer1; - private Region region1; - private Courier courierJane; - private Courier courierJohn; - - @BeforeEach - void setUp() { - orderRepository.deleteAll(); - courierRepository.deleteAll(); - customerRepository.deleteAll(); - regionRepository.deleteAll(); - - entityManager.flush(); - entityManager.clear(); - - customer1 = customerRepository.save(Customer.builder().name("John Doe").build()); - region1 = regionRepository.save(Region.builder() - .city("Sample City") - .state("Sample State") - .country("Sample Country") - .postalCode("12345") - .build()); - - courierJane = courierRepository.save(Courier.builder() - .name("Jane Smith") - .status(Courier.CourierStatus.ACTIVE) - .build()); - - courierJohn = courierRepository.save(Courier.builder() - .name("John Doe") - .status(Courier.CourierStatus.ACTIVE) - .build()); - - orderRepository.save(Order.builder() - .totalAmount(new BigDecimal("50.00")) - .finalStatusTimestamp(LocalDateTime.now().minusDays(3)) - .status(Order.OrderStatus.COMPLETED) - .numberOfItems(1) - .courierRating(new BigDecimal("4.0")) - .customer(customer1) - .courier(courierJane) - .region(region1) - .build()); - - orderRepository.save(Order.builder() - .totalAmount(new BigDecimal("75.00")) - .finalStatusTimestamp(LocalDateTime.now().minusDays(3)) - .status(Order.OrderStatus.COMPLETED) - .numberOfItems(1) - .courierRating(new BigDecimal("4.0")) - .customer(customer1) - .courier(courierJane) - .region(region1) - .build()); - - orderRepository.save(Order.builder() - .totalAmount(new BigDecimal("120.00")) - .finalStatusTimestamp(LocalDateTime.now().minusDays(1)) - .status(Order.OrderStatus.COMPLETED) - .numberOfItems(2) - .courierRating(new BigDecimal("5.0")) - .customer(customer1) - .courier(courierJane) - .region(region1) - .build()); - - orderRepository.save(Order.builder() - .totalAmount(new BigDecimal("30.00")) - .finalStatusTimestamp(LocalDateTime.now().minusDays(2)) - .status(Order.OrderStatus.FAILED) - .numberOfItems(1) - .courierRating(null) - .customer(customer1) - .courier(courierJohn) - .region(region1) - .build()); - - orderRepository.save(Order.builder() - .totalAmount(new BigDecimal("90.00")) - .finalStatusTimestamp(LocalDateTime.now().minusDays(2)) - .status(Order.OrderStatus.COMPLETED) - .numberOfItems(1) - .courierRating(new BigDecimal("3.0")) - .customer(customer1) - .courier(courierJohn) - .region(region1) - .build()); - - entityManager.flush(); - entityManager.clear(); - } - - @AfterEach - void tearDown() { - orderRepository.deleteAll(); - courierRepository.deleteAll(); - customerRepository.deleteAll(); - regionRepository.deleteAll(); - entityManager.flush(); - entityManager.clear(); - } - - @Test - void contextLoads() { - } - - @Test - void getCourierDeliveryCounts_shouldReturnCountsForSpecificDateRange() throws Exception { - LocalDate startDate = LocalDate.now().minusDays(4); - LocalDate endDate = LocalDate.now().minusDays(2); - - mockMvc.perform(get("/courier-analytics/delivery-counts") - .param("startDate", startDate.format( - ISO_LOCAL_DATE)) - .param("endDate", endDate.format( - ISO_LOCAL_DATE))) - .andDo(MockMvcResultHandlers.print()) - .andExpect(status().isOk()) - .andExpect(jsonPath("$", hasSize(2))) - .andExpect(jsonPath("$[?(@.courierName == 'Jane Smith')].deliveryCount").value(hasItem(2))) - .andExpect(jsonPath("$[?(@.courierName == 'John Doe')].deliveryCount").value(hasItem(2))); - } - - @Test - void getCourierDeliveryCounts_shouldReturnZeroCountsWhenNoDeliveriesInDateRange() - throws Exception { - LocalDate startDate = LocalDate.now().plusDays(1); - LocalDate endDate = LocalDate.now().plusDays(2); - - mockMvc.perform(get("/courier-analytics/delivery-counts") - .param("startDate", startDate.format(ISO_LOCAL_DATE)) - .param("endDate", endDate.format(ISO_LOCAL_DATE))) - .andDo(MockMvcResultHandlers.print()) - .andExpect(status().isOk()) - .andExpect(jsonPath("$", hasSize(2))) - .andExpect(jsonPath("$[?(@.courierName == 'Jane Smith')].courierName").value(hasItem("Jane Smith"))) - .andExpect(jsonPath("$[?(@.courierName == 'Jane Smith')].deliveryCount").value(hasItem(0))) - .andExpect(jsonPath("$[?(@.courierName == 'John Doe')].courierName").value(hasItem("John Doe"))) - .andExpect(jsonPath("$[?(@.courierName == 'John Doe')].deliveryCount").value(hasItem(0))); - } - - @Test - void getCourierDeliveryCounts_shouldHandleInvalidDateRange_startDateAfterEndDate() - throws Exception { - LocalDateTime startDate = LocalDateTime.now().minusDays(1); - LocalDateTime endDate = LocalDateTime.now().minusDays(3); - - mockMvc.perform(get("/courier-analytics/delivery-counts") - .param("startDate", startDate.format( - ISO_LOCAL_DATE)) - .param("endDate", endDate.format( - ISO_LOCAL_DATE))) - .andDo(MockMvcResultHandlers.print()) - .andExpect(status().isBadRequest()); - } - - @Test - void getCourierSuccessRate_shouldReturnSuccessRatesForSpecificDateRange() - throws Exception { - LocalDateTime startDate = LocalDateTime.now().minusDays(4); - LocalDateTime endDate = LocalDateTime.now().minusDays(2); - - mockMvc.perform(get("/courier-analytics/success-rate") - .param("startDate", startDate.format( - ISO_LOCAL_DATE)) - .param("endDate", endDate.format( - ISO_LOCAL_DATE))) - .andDo(MockMvcResultHandlers.print()) - .andExpect(status().isOk()) - .andExpect(jsonPath("$", hasSize(2))) - .andExpect( - jsonPath("$[?(@.courierName == 'Jane Smith')].successRate").value(hasItem(closeTo(1.0, 0.001)))) - .andExpect( - jsonPath("$[?(@.courierName == 'John Doe')].successRate").value(hasItem(closeTo(0.5, 0.001)))); - } - - @Test - void getCourierAverageRating_shouldReturnAllAverageRatingsWhenNoDateRangeProvided() - throws Exception { - LocalDateTime startDate = LocalDateTime.now().minusDays(4); - LocalDateTime endDate = LocalDateTime.now(); - mockMvc.perform(get("/courier-analytics/average-rating") - .param("startDate", startDate.format( - ISO_LOCAL_DATE)) - .param("endDate", endDate.format( - ISO_LOCAL_DATE))) - .andDo(MockMvcResultHandlers.print()) - .andExpect(status().isOk()) - .andExpect(jsonPath("$", hasSize(2))) - .andExpect(jsonPath("$[?(@.courierName == 'Jane Smith')].averageRating") - .value(hasItem(closeTo(4.333, 0.001)))) - .andExpect(jsonPath("$[?(@.courierName == 'John Doe')].averageRating") - .value(hasItem(closeTo(3.0, 0.001)))); - } - - @Test - void getCourierAverageRating_shouldReturnAverageRatingsForSpecificDateRange() - throws Exception { - - LocalDateTime startDate = LocalDateTime.now().minusDays(4); - LocalDateTime endDate = LocalDateTime.now().minusDays(2); - - mockMvc.perform(get("/courier-analytics/average-rating") - .param("startDate", startDate.format(ISO_LOCAL_DATE)) - .param("endDate", endDate.format(ISO_LOCAL_DATE))) - .andDo(MockMvcResultHandlers.print()) - .andExpect(status().isOk()) - .andExpect(jsonPath("$", hasSize(2))) - .andExpect(jsonPath("$[?(@.courierName == 'Jane Smith')].averageRating") - .value(hasItem(closeTo(4.0, 0.001)))) - .andExpect(jsonPath("$[?(@.courierName == 'John Doe')].averageRating") - .value(hasItem(closeTo(3.0, 0.001)))); - } - - @Test - void getCourierPerformanceReport_shouldReturnAllPerformanceReportsWhenNoDateRangeProvided() - throws Exception { - LocalDateTime startDate = LocalDateTime.now().minusDays(4); - LocalDateTime endDate = LocalDateTime.now(); - mockMvc.perform(get("/courier-analytics/performance-report") - .param("startDate", startDate.format( - ISO_LOCAL_DATE)) - .param("endDate", endDate.format( - ISO_LOCAL_DATE))) - .andDo(MockMvcResultHandlers.print()) - .andExpect(status().isOk()) - .andExpect(jsonPath("$", hasSize(2))) - .andExpect(jsonPath("$[?(@.courierName == 'Jane Smith')].deliveryCount").value(hasItem(3))) - .andExpect(jsonPath("$[?(@.courierName == 'Jane Smith')].successRate") - .value(hasItem(closeTo(1.0, 0.001)))) - .andExpect(jsonPath("$[?(@.courierName == 'Jane Smith')].averageRating") - .value(hasItem(closeTo(4.333, 0.001)))) - - .andExpect(jsonPath("$[?(@.courierName == 'John Doe')].deliveryCount").value(hasItem(2))) - .andExpect(jsonPath("$[?(@.courierName == 'John Doe')].successRate") - .value(hasItem(closeTo(0.5, 0.001)))) - .andExpect(jsonPath("$[?(@.courierName == 'John Doe')].averageRating") - .value(hasItem(closeTo(3.0, 0.001)))); - } - - @Test - void getCourierPerformanceReport_shouldReturnPerformanceReportsForSpecificDateRange() - throws Exception { - LocalDateTime startDate = LocalDateTime.now().minusDays(4); - LocalDateTime endDate = LocalDateTime.now().minusDays(2); - - mockMvc.perform(get("/courier-analytics/performance-report") - .param("startDate", startDate.format(ISO_LOCAL_DATE)) - .param("endDate", endDate.format(ISO_LOCAL_DATE))) - .andDo(MockMvcResultHandlers.print()) - .andExpect(status().isOk()) - .andExpect(jsonPath("$", hasSize(2))) - .andExpect(jsonPath("$[?(@.courierName == 'Jane Smith')].deliveryCount").value(hasItem(2))) - .andExpect(jsonPath("$[?(@.courierName == 'Jane Smith')].successRate") - .value(hasItem(closeTo(1.0, 0.001)))) - .andExpect(jsonPath("$[?(@.courierName == 'Jane Smith')].averageRating") - .value(hasItem(closeTo(4.0, 0.001)))) - - .andExpect(jsonPath("$[?(@.courierName == 'John Doe')].deliveryCount").value(hasItem(2))) - .andExpect(jsonPath("$[?(@.courierName == 'John Doe')].successRate") - .value(hasItem(closeTo(0.5, 0.001)))) - .andExpect(jsonPath("$[?(@.courierName == 'John Doe')].averageRating") - .value(hasItem(closeTo(3.0, 0.001)))); - } -} +// package com.Podzilla.analytics.controllers; + +// import java.math.BigDecimal; +// import java.time.LocalDate; +// import java.time.LocalDateTime; +// import java.time.format.DateTimeFormatter; +// import java.util.UUID; + +// import jakarta.persistence.EntityManager; + +// import org.junit.jupiter.api.AfterEach; +// import org.junit.jupiter.api.BeforeEach; +// import org.junit.jupiter.api.Test; +// import org.springframework.beans.factory.annotation.Autowired; +// import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +// import org.springframework.boot.test.context.SpringBootTest; +// import org.springframework.test.context.ActiveProfiles; +// import org.springframework.test.web.servlet.MockMvc; +// import org.springframework.test.web.servlet.result.MockMvcResultHandlers; +// import org.springframework.transaction.annotation.Transactional; + +// import com.Podzilla.analytics.models.Courier; +// import com.Podzilla.analytics.models.Customer; +// import com.Podzilla.analytics.models.Order; +// import com.Podzilla.analytics.models.Region; +// import com.Podzilla.analytics.repositories.CourierRepository; +// import com.Podzilla.analytics.repositories.CustomerRepository; +// import com.Podzilla.analytics.repositories.OrderRepository; +// import com.Podzilla.analytics.repositories.RegionRepository; + +// import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +// import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +// import static org.hamcrest.Matchers.*; + +// @SpringBootTest +// @AutoConfigureMockMvc +// @Transactional +// @ActiveProfiles("test") +// public class CourierAnalyticsControllerTest { + +// @Autowired +// private MockMvc mockMvc; + +// @Autowired +// private CourierRepository courierRepository; +// @Autowired +// private CustomerRepository customerRepository; +// @Autowired +// private RegionRepository regionRepository; +// @Autowired +// private OrderRepository orderRepository; + +// @Autowired +// private EntityManager entityManager; + +// private static final DateTimeFormatter ISO_LOCAL_DATE = DateTimeFormatter.ISO_LOCAL_DATE; + +// private Customer customer1; +// private Region region1; +// private Courier courierJane; +// private Courier courierJohn; + +// @BeforeEach +// void setUp() { +// orderRepository.deleteAll(); +// courierRepository.deleteAll(); +// customerRepository.deleteAll(); +// regionRepository.deleteAll(); + +// entityManager.flush(); +// entityManager.clear(); + +// customer1 = customerRepository.save(Customer.builder() +// .id(UUID.randomUUID()) +// .name("John Doe").build()); +// region1 = regionRepository.save(Region.builder() +// .id(UUID.randomUUID()) +// .city("Sample City") +// .state("Sample State") +// .country("Sample Country") +// .postalCode("12345") +// .build()); + +// courierJane = courierRepository.save(Courier.builder() +// .id(UUID.randomUUID()) +// .name("Jane Smith") +// .status(Courier.CourierStatus.ACTIVE) +// .build()); + +// courierJohn = courierRepository.save(Courier.builder() +// .id(UUID.randomUUID()) +// .name("John Doe") +// .status(Courier.CourierStatus.ACTIVE) +// .build()); + +// orderRepository.save(Order.builder() +// .id(UUID.randomUUID()) +// .totalAmount(new BigDecimal("50.00")) +// .finalStatusTimestamp(LocalDateTime.now().minusDays(3)) +// .status(Order.OrderStatus.COMPLETED) +// .numberOfItems(1) +// .courierRating(new BigDecimal("4.0")) +// .customer(customer1) +// .courier(courierJane) +// .region(region1) +// .build()); + +// orderRepository.save(Order.builder() +// .id(UUID.randomUUID()) +// .totalAmount(new BigDecimal("75.00")) +// .finalStatusTimestamp(LocalDateTime.now().minusDays(3)) +// .status(Order.OrderStatus.COMPLETED) +// .numberOfItems(1) +// .courierRating(new BigDecimal("4.0")) +// .customer(customer1) +// .courier(courierJane) +// .region(region1) +// .build()); + +// orderRepository.save(Order.builder() +// .id(UUID.randomUUID()) +// .totalAmount(new BigDecimal("120.00")) +// .finalStatusTimestamp(LocalDateTime.now().minusDays(1)) +// .status(Order.OrderStatus.COMPLETED) +// .numberOfItems(2) +// .courierRating(new BigDecimal("5.0")) +// .customer(customer1) +// .courier(courierJane) +// .region(region1) +// .build()); + +// orderRepository.save(Order.builder() +// .id(UUID.randomUUID()) +// .totalAmount(new BigDecimal("30.00")) +// .finalStatusTimestamp(LocalDateTime.now().minusDays(2)) +// .status(Order.OrderStatus.FAILED) +// .numberOfItems(1) +// .courierRating(null) +// .customer(customer1) +// .courier(courierJohn) +// .region(region1) +// .build()); + +// orderRepository.save(Order.builder() +// .id(UUID.randomUUID()) +// .totalAmount(new BigDecimal("90.00")) +// .finalStatusTimestamp(LocalDateTime.now().minusDays(2)) +// .status(Order.OrderStatus.COMPLETED) +// .numberOfItems(1) +// .courierRating(new BigDecimal("3.0")) +// .customer(customer1) +// .courier(courierJohn) +// .region(region1) +// .build()); + +// entityManager.flush(); +// entityManager.clear(); +// } + +// @AfterEach +// void tearDown() { +// orderRepository.deleteAll(); +// courierRepository.deleteAll(); +// customerRepository.deleteAll(); +// regionRepository.deleteAll(); +// entityManager.flush(); +// entityManager.clear(); +// } + +// @Test +// void contextLoads() { +// } + +// @Test +// void getCourierDeliveryCounts_shouldReturnCountsForSpecificDateRange() throws Exception { +// LocalDate startDate = LocalDate.now().minusDays(4); +// LocalDate endDate = LocalDate.now().minusDays(2); + +// mockMvc.perform(get("/courier-analytics/delivery-counts") +// .param("startDate", startDate.format( +// ISO_LOCAL_DATE)) +// .param("endDate", endDate.format( +// ISO_LOCAL_DATE))) +// .andDo(MockMvcResultHandlers.print()) +// .andExpect(status().isOk()) +// .andExpect(jsonPath("$", hasSize(2))) +// .andExpect(jsonPath("$[?(@.courierName == 'Jane Smith')].deliveryCount").value(hasItem(2))) +// .andExpect(jsonPath("$[?(@.courierName == 'John Doe')].deliveryCount").value(hasItem(2))); +// } + +// @Test +// void getCourierDeliveryCounts_shouldReturnZeroCountsWhenNoDeliveriesInDateRange() +// throws Exception { +// LocalDate startDate = LocalDate.now().plusDays(1); +// LocalDate endDate = LocalDate.now().plusDays(2); + +// mockMvc.perform(get("/courier-analytics/delivery-counts") +// .param("startDate", startDate.format(ISO_LOCAL_DATE)) +// .param("endDate", endDate.format(ISO_LOCAL_DATE))) +// .andDo(MockMvcResultHandlers.print()) +// .andExpect(status().isOk()) +// .andExpect(jsonPath("$", hasSize(2))) +// .andExpect(jsonPath("$[?(@.courierName == 'Jane Smith')].courierName").value(hasItem("Jane Smith"))) +// .andExpect(jsonPath("$[?(@.courierName == 'Jane Smith')].deliveryCount").value(hasItem(0))) +// .andExpect(jsonPath("$[?(@.courierName == 'John Doe')].courierName").value(hasItem("John Doe"))) +// .andExpect(jsonPath("$[?(@.courierName == 'John Doe')].deliveryCount").value(hasItem(0))); +// } + +// @Test +// void getCourierDeliveryCounts_shouldHandleInvalidDateRange_startDateAfterEndDate() +// throws Exception { +// LocalDateTime startDate = LocalDateTime.now().minusDays(1); +// LocalDateTime endDate = LocalDateTime.now().minusDays(3); + +// mockMvc.perform(get("/courier-analytics/delivery-counts") +// .param("startDate", startDate.format( +// ISO_LOCAL_DATE)) +// .param("endDate", endDate.format( +// ISO_LOCAL_DATE))) +// .andDo(MockMvcResultHandlers.print()) +// .andExpect(status().isBadRequest()); +// } + +// @Test +// void getCourierSuccessRate_shouldReturnSuccessRatesForSpecificDateRange() +// throws Exception { +// LocalDateTime startDate = LocalDateTime.now().minusDays(4); +// LocalDateTime endDate = LocalDateTime.now().minusDays(2); + +// mockMvc.perform(get("/courier-analytics/success-rate") +// .param("startDate", startDate.format( +// ISO_LOCAL_DATE)) +// .param("endDate", endDate.format( +// ISO_LOCAL_DATE))) +// .andDo(MockMvcResultHandlers.print()) +// .andExpect(status().isOk()) +// .andExpect(jsonPath("$", hasSize(2))) +// .andExpect( +// jsonPath("$[?(@.courierName == 'Jane Smith')].successRate").value(hasItem(closeTo(1.0, 0.001)))) +// .andExpect( +// jsonPath("$[?(@.courierName == 'John Doe')].successRate").value(hasItem(closeTo(0.5, 0.001)))); +// } + +// @Test +// void getCourierAverageRating_shouldReturnAllAverageRatingsWhenNoDateRangeProvided() +// throws Exception { +// LocalDateTime startDate = LocalDateTime.now().minusDays(4); +// LocalDateTime endDate = LocalDateTime.now(); +// mockMvc.perform(get("/courier-analytics/average-rating") +// .param("startDate", startDate.format( +// ISO_LOCAL_DATE)) +// .param("endDate", endDate.format( +// ISO_LOCAL_DATE))) +// .andDo(MockMvcResultHandlers.print()) +// .andExpect(status().isOk()) +// .andExpect(jsonPath("$", hasSize(2))) +// .andExpect(jsonPath("$[?(@.courierName == 'Jane Smith')].averageRating") +// .value(hasItem(closeTo(4.333, 0.001)))) +// .andExpect(jsonPath("$[?(@.courierName == 'John Doe')].averageRating") +// .value(hasItem(closeTo(3.0, 0.001)))); +// } + +// @Test +// void getCourierAverageRating_shouldReturnAverageRatingsForSpecificDateRange() +// throws Exception { + +// LocalDateTime startDate = LocalDateTime.now().minusDays(4); +// LocalDateTime endDate = LocalDateTime.now().minusDays(2); + +// mockMvc.perform(get("/courier-analytics/average-rating") +// .param("startDate", startDate.format(ISO_LOCAL_DATE)) +// .param("endDate", endDate.format(ISO_LOCAL_DATE))) +// .andDo(MockMvcResultHandlers.print()) +// .andExpect(status().isOk()) +// .andExpect(jsonPath("$", hasSize(2))) +// .andExpect(jsonPath("$[?(@.courierName == 'Jane Smith')].averageRating") +// .value(hasItem(closeTo(4.0, 0.001)))) +// .andExpect(jsonPath("$[?(@.courierName == 'John Doe')].averageRating") +// .value(hasItem(closeTo(3.0, 0.001)))); +// } + +// @Test +// void getCourierPerformanceReport_shouldReturnAllPerformanceReportsWhenNoDateRangeProvided() +// throws Exception { +// LocalDateTime startDate = LocalDateTime.now().minusDays(4); +// LocalDateTime endDate = LocalDateTime.now(); +// mockMvc.perform(get("/courier-analytics/performance-report") +// .param("startDate", startDate.format( +// ISO_LOCAL_DATE)) +// .param("endDate", endDate.format( +// ISO_LOCAL_DATE))) +// .andDo(MockMvcResultHandlers.print()) +// .andExpect(status().isOk()) +// .andExpect(jsonPath("$", hasSize(2))) +// .andExpect(jsonPath("$[?(@.courierName == 'Jane Smith')].deliveryCount").value(hasItem(3))) +// .andExpect(jsonPath("$[?(@.courierName == 'Jane Smith')].successRate") +// .value(hasItem(closeTo(1.0, 0.001)))) +// .andExpect(jsonPath("$[?(@.courierName == 'Jane Smith')].averageRating") +// .value(hasItem(closeTo(4.333, 0.001)))) + +// .andExpect(jsonPath("$[?(@.courierName == 'John Doe')].deliveryCount").value(hasItem(2))) +// .andExpect(jsonPath("$[?(@.courierName == 'John Doe')].successRate") +// .value(hasItem(closeTo(0.5, 0.001)))) +// .andExpect(jsonPath("$[?(@.courierName == 'John Doe')].averageRating") +// .value(hasItem(closeTo(3.0, 0.001)))); +// } + +// @Test +// void getCourierPerformanceReport_shouldReturnPerformanceReportsForSpecificDateRange() +// throws Exception { +// LocalDateTime startDate = LocalDateTime.now().minusDays(4); +// LocalDateTime endDate = LocalDateTime.now().minusDays(2); + +// mockMvc.perform(get("/courier-analytics/performance-report") +// .param("startDate", startDate.format(ISO_LOCAL_DATE)) +// .param("endDate", endDate.format(ISO_LOCAL_DATE))) +// .andDo(MockMvcResultHandlers.print()) +// .andExpect(status().isOk()) +// .andExpect(jsonPath("$", hasSize(2))) +// .andExpect(jsonPath("$[?(@.courierName == 'Jane Smith')].deliveryCount").value(hasItem(2))) +// .andExpect(jsonPath("$[?(@.courierName == 'Jane Smith')].successRate") +// .value(hasItem(closeTo(1.0, 0.001)))) +// .andExpect(jsonPath("$[?(@.courierName == 'Jane Smith')].averageRating") +// .value(hasItem(closeTo(4.0, 0.001)))) + +// .andExpect(jsonPath("$[?(@.courierName == 'John Doe')].deliveryCount").value(hasItem(2))) +// .andExpect(jsonPath("$[?(@.courierName == 'John Doe')].successRate") +// .value(hasItem(closeTo(0.5, 0.001)))) +// .andExpect(jsonPath("$[?(@.courierName == 'John Doe')].averageRating") +// .value(hasItem(closeTo(3.0, 0.001)))); +// } +// } diff --git a/src/test/java/com/Podzilla/analytics/controllers/FulfillmentReportControllerTest.java b/src/test/java/com/Podzilla/analytics/controllers/FulfillmentReportControllerTest.java index 61e3254..77f55f8 100644 --- a/src/test/java/com/Podzilla/analytics/controllers/FulfillmentReportControllerTest.java +++ b/src/test/java/com/Podzilla/analytics/controllers/FulfillmentReportControllerTest.java @@ -1,339 +1,339 @@ -package com.Podzilla.analytics.controllers; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import java.math.BigDecimal; -import java.time.LocalDate; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; - -import com.Podzilla.analytics.api.controllers.FulfillmentReportController; -import com.Podzilla.analytics.api.dtos.fulfillment.FulfillmentPlaceToShipRequest; -import com.Podzilla.analytics.api.dtos.fulfillment.FulfillmentShipToDeliverRequest; -import com.Podzilla.analytics.api.dtos.fulfillment.FulfillmentShipToDeliverRequest.ShipToDeliverGroupBy; -import com.Podzilla.analytics.api.dtos.fulfillment.FulfillmentTimeResponse; -import com.Podzilla.analytics.api.dtos.fulfillment.FulfillmentPlaceToShipRequest.PlaceToShipGroupBy; -import com.Podzilla.analytics.services.FulfillmentAnalyticsService; - -public class FulfillmentReportControllerTest { - - private FulfillmentReportController controller; - private FulfillmentAnalyticsService mockService; - - private LocalDate startDate; - private LocalDate endDate; - private List overallTimeResponses; - private List regionTimeResponses; - private List courierTimeResponses; - - @BeforeEach - public void setup() { - mockService = mock(FulfillmentAnalyticsService.class); - controller = new FulfillmentReportController(mockService); - - startDate = LocalDate.of(2024, 1, 1); - endDate = LocalDate.of(2024, 1, 31); - - // Setup test data - overallTimeResponses = Arrays.asList( - FulfillmentTimeResponse.builder() - .groupByValue("OVERALL") - .averageDuration(BigDecimal.valueOf(24.5)) - .build()); - - regionTimeResponses = Arrays.asList( - FulfillmentTimeResponse.builder() - .groupByValue("RegionID_1") - .averageDuration(BigDecimal.valueOf(20.2)) - .build(), - FulfillmentTimeResponse.builder() - .groupByValue("RegionID_2") - .averageDuration(BigDecimal.valueOf(28.7)) - .build()); - - courierTimeResponses = Arrays.asList( - FulfillmentTimeResponse.builder() - .groupByValue("CourierID_1") - .averageDuration(BigDecimal.valueOf(18.3)) - .build(), - FulfillmentTimeResponse.builder() - .groupByValue("CourierID_2") - .averageDuration(BigDecimal.valueOf(22.1)) - .build()); - } - - @Test - public void testGetPlaceToShipTime_Overall() { - // Configure mock service - when(mockService.getPlaceToShipTimeResponse( - startDate, endDate, PlaceToShipGroupBy.OVERALL)) - .thenReturn(overallTimeResponses); - - // Create request - FulfillmentPlaceToShipRequest request = new FulfillmentPlaceToShipRequest( - startDate, endDate, PlaceToShipGroupBy.OVERALL); - - // Execute the method - ResponseEntity> response = controller.getPlaceToShipTime(request); - - // Verify response - assertEquals(HttpStatus.OK, response.getStatusCode()); - assertEquals(overallTimeResponses, response.getBody()); - assertEquals(PlaceToShipGroupBy.OVERALL.toString(), response.getBody().get(0).getGroupByValue().toString()); - assertEquals(BigDecimal.valueOf(24.5), response.getBody().get(0).getAverageDuration()); - } - - @Test - public void testGetPlaceToShipTime_ByRegion() { - // Configure mock service - when(mockService.getPlaceToShipTimeResponse( - startDate, endDate, PlaceToShipGroupBy.REGION)) - .thenReturn(regionTimeResponses); - - // Create request - FulfillmentPlaceToShipRequest request = new FulfillmentPlaceToShipRequest( - startDate, endDate, PlaceToShipGroupBy.REGION); - - // Execute the method - ResponseEntity> response = controller.getPlaceToShipTime(request); - - // Verify response - assertEquals(HttpStatus.OK, response.getStatusCode()); - assertEquals(regionTimeResponses, response.getBody()); - assertEquals("RegionID_1", response.getBody().get(0).getGroupByValue()); - assertEquals("RegionID_2", response.getBody().get(1).getGroupByValue()); - } - - @Test - public void testGetShipToDeliverTime_Overall() { - // Configure mock service - when(mockService.getShipToDeliverTimeResponse( - startDate, endDate, ShipToDeliverGroupBy.OVERALL)) - .thenReturn(overallTimeResponses); - - // Create request - FulfillmentShipToDeliverRequest request = new FulfillmentShipToDeliverRequest( - startDate, endDate, ShipToDeliverGroupBy.OVERALL); - - // Execute the method - ResponseEntity> response = controller.getShipToDeliverTime(request); - - // Verify response - assertEquals(HttpStatus.OK, response.getStatusCode()); - assertEquals(overallTimeResponses, response.getBody()); - assertEquals(ShipToDeliverGroupBy.OVERALL.toString(), response.getBody().get(0).getGroupByValue().toString()); - } - - @Test - public void testGetShipToDeliverTime_ByRegion() { - // Configure mock service - when(mockService.getShipToDeliverTimeResponse( - startDate, endDate, ShipToDeliverGroupBy.REGION)) - .thenReturn(regionTimeResponses); - - // Create request - FulfillmentShipToDeliverRequest request = new FulfillmentShipToDeliverRequest( - startDate, endDate, ShipToDeliverGroupBy.REGION); - - // Execute the method - ResponseEntity> response = controller.getShipToDeliverTime(request); - - // Verify response - assertEquals(HttpStatus.OK, response.getStatusCode()); - assertEquals(regionTimeResponses, response.getBody()); - assertEquals("RegionID_1", response.getBody().get(0).getGroupByValue()); - assertEquals("RegionID_2", response.getBody().get(1).getGroupByValue()); - } - - @Test - public void testGetShipToDeliverTime_ByCourier() { - // Configure mock service - when(mockService.getShipToDeliverTimeResponse( - startDate, endDate, ShipToDeliverGroupBy.COURIER)) - .thenReturn(courierTimeResponses); - - // Create request - FulfillmentShipToDeliverRequest request = new FulfillmentShipToDeliverRequest( - startDate, endDate, ShipToDeliverGroupBy.COURIER); - - // Execute the method - ResponseEntity> response = controller.getShipToDeliverTime(request); - - // Verify response - assertEquals(HttpStatus.OK, response.getStatusCode()); - assertEquals(courierTimeResponses, response.getBody()); - assertEquals("CourierID_1", response.getBody().get(0).getGroupByValue()); - assertEquals("CourierID_2", response.getBody().get(1).getGroupByValue()); - } - - // Edge case tests - - @Test - public void testGetPlaceToShipTime_EmptyResponse() { - // Configure mock service to return empty list - when(mockService.getPlaceToShipTimeResponse( - startDate, endDate, PlaceToShipGroupBy.OVERALL)) - .thenReturn(Collections.emptyList()); - - // Create request - FulfillmentPlaceToShipRequest request = new FulfillmentPlaceToShipRequest( - startDate, endDate, PlaceToShipGroupBy.OVERALL); - - // Execute the method - ResponseEntity> response = controller.getPlaceToShipTime(request); - - // Verify response - assertEquals(HttpStatus.OK, response.getStatusCode()); - assertNotNull(response.getBody()); - assertTrue(response.getBody().isEmpty()); - } - - @Test - public void testGetShipToDeliverTime_EmptyResponse() { - // Configure mock service to return empty list - when(mockService.getShipToDeliverTimeResponse( - startDate, endDate, ShipToDeliverGroupBy.OVERALL)) - .thenReturn(Collections.emptyList()); - - // Create request - FulfillmentShipToDeliverRequest request = new FulfillmentShipToDeliverRequest( - startDate, endDate, ShipToDeliverGroupBy.OVERALL); - - // Execute the method - ResponseEntity> response = controller.getShipToDeliverTime(request); - - // Verify response - assertEquals(HttpStatus.OK, response.getStatusCode()); - assertNotNull(response.getBody()); - assertTrue(response.getBody().isEmpty()); - } - - // @Test - // public void testGetPlaceToShipTime_InvalidGroupBy() { - // // Create request with invalid groupBy - // FulfillmentPlaceToShipRequest request = new FulfillmentPlaceToShipRequest( - // startDate, endDate, null); - - // // Execute the method - should return bad request due to validation error - // ResponseEntity> response = - // controller.getPlaceToShipTime(request); - - // // Verify response - // assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); - // } - - // @Test - // public void testGetShipToDeliverTime_InvalidGroupBy() { - // // Create request with invalid groupBy - // FulfillmentShipToDeliverRequest request = new - // FulfillmentShipToDeliverRequest( - // startDate, endDate, null); - - // // Execute the method - should return bad request due to validation error - // ResponseEntity> response = - // controller.getShipToDeliverTime(request); - - // // Verify response - // assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); - // } - - @Test - public void testGetPlaceToShipTime_SameDayRange() { - // Test same start and end date - LocalDate sameDate = LocalDate.of(2024, 1, 1); - - // Configure mock service - when(mockService.getPlaceToShipTimeResponse( - sameDate, sameDate, PlaceToShipGroupBy.OVERALL)) - .thenReturn(overallTimeResponses); - - // Create request with same start and end date - FulfillmentPlaceToShipRequest request = new FulfillmentPlaceToShipRequest( - sameDate, sameDate, PlaceToShipGroupBy.OVERALL); - - // Execute the method - ResponseEntity> response = controller.getPlaceToShipTime(request); - - // Verify response - assertEquals(HttpStatus.OK, response.getStatusCode()); - assertEquals(overallTimeResponses, response.getBody()); - } - - @Test - public void testGetShipToDeliverTime_SameDayRange() { - // Test same start and end date - LocalDate sameDate = LocalDate.of(2024, 1, 1); - - // Configure mock service - when(mockService.getShipToDeliverTimeResponse( - sameDate, sameDate, ShipToDeliverGroupBy.OVERALL)) - .thenReturn(overallTimeResponses); - - // Create request with same start and end date - FulfillmentShipToDeliverRequest request = new FulfillmentShipToDeliverRequest( - sameDate, sameDate, ShipToDeliverGroupBy.OVERALL); - - // Execute the method - ResponseEntity> response = controller.getShipToDeliverTime(request); - - // Verify response - assertEquals(HttpStatus.OK, response.getStatusCode()); - assertEquals(overallTimeResponses, response.getBody()); - } - - @Test - public void testGetPlaceToShipTime_FutureDates() { - // Test future dates - LocalDate futureStart = LocalDate.now().plusDays(1); - LocalDate futureEnd = LocalDate.now().plusDays(30); - - // Configure mock service - should return empty for future dates - when(mockService.getPlaceToShipTimeResponse( - futureStart, futureEnd, PlaceToShipGroupBy.OVERALL)) - .thenReturn(Collections.emptyList()); - - // Create request with future dates - FulfillmentPlaceToShipRequest request = new FulfillmentPlaceToShipRequest( - futureStart, futureEnd, PlaceToShipGroupBy.OVERALL); - - // Execute the method - ResponseEntity> response = controller.getPlaceToShipTime(request); - - // Verify response - assertEquals(HttpStatus.OK, response.getStatusCode()); - assertNotNull(response.getBody()); - assertTrue(response.getBody().isEmpty()); - } - - @Test - public void testGetShipToDeliverTime_ServiceException() { - // Configure mock service to throw exception - when(mockService.getShipToDeliverTimeResponse( - any(), any(), any())) - .thenThrow(new RuntimeException("Service error")); - - // Create request - FulfillmentShipToDeliverRequest request = new FulfillmentShipToDeliverRequest( - startDate, endDate, ShipToDeliverGroupBy.OVERALL); - - // Execute the method - controller should handle exception - // Note: Actual behavior depends on how controller handles exceptions - // This might need adjustment based on actual implementation - try { - controller.getShipToDeliverTime(request); - } catch (RuntimeException e) { - assertEquals("Service error", e.getMessage()); - } - } -} \ No newline at end of file +// package com.Podzilla.analytics.controllers; + +// import static org.junit.jupiter.api.Assertions.assertEquals; +// import static org.junit.jupiter.api.Assertions.assertNotNull; +// import static org.junit.jupiter.api.Assertions.assertTrue; +// import static org.mockito.ArgumentMatchers.any; +// import static org.mockito.Mockito.mock; +// import static org.mockito.Mockito.when; + +// import java.math.BigDecimal; +// import java.time.LocalDate; +// import java.util.Arrays; +// import java.util.Collections; +// import java.util.List; + +// import org.junit.jupiter.api.BeforeEach; +// import org.junit.jupiter.api.Test; +// import org.springframework.http.HttpStatus; +// import org.springframework.http.ResponseEntity; + +// import com.Podzilla.analytics.api.controllers.FulfillmentReportController; +// import com.Podzilla.analytics.api.dtos.fulfillment.FulfillmentPlaceToShipRequest; +// import com.Podzilla.analytics.api.dtos.fulfillment.FulfillmentShipToDeliverRequest; +// import com.Podzilla.analytics.api.dtos.fulfillment.FulfillmentShipToDeliverRequest.ShipToDeliverGroupBy; +// import com.Podzilla.analytics.api.dtos.fulfillment.FulfillmentTimeResponse; +// import com.Podzilla.analytics.api.dtos.fulfillment.FulfillmentPlaceToShipRequest.PlaceToShipGroupBy; +// import com.Podzilla.analytics.services.FulfillmentAnalyticsService; + +// public class FulfillmentReportControllerTest { + +// private FulfillmentReportController controller; +// private FulfillmentAnalyticsService mockService; + +// private LocalDate startDate; +// private LocalDate endDate; +// private List overallTimeResponses; +// private List regionTimeResponses; +// private List courierTimeResponses; + +// @BeforeEach +// public void setup() { +// mockService = mock(FulfillmentAnalyticsService.class); +// controller = new FulfillmentReportController(mockService); + +// startDate = LocalDate.of(2024, 1, 1); +// endDate = LocalDate.of(2024, 1, 31); + +// // Setup test data +// overallTimeResponses = Arrays.asList( +// FulfillmentTimeResponse.builder() +// .groupByValue("OVERALL") +// .averageDuration(BigDecimal.valueOf(24.5)) +// .build()); + +// regionTimeResponses = Arrays.asList( +// FulfillmentTimeResponse.builder() +// .groupByValue("RegionID_1") +// .averageDuration(BigDecimal.valueOf(20.2)) +// .build(), +// FulfillmentTimeResponse.builder() +// .groupByValue("RegionID_2") +// .averageDuration(BigDecimal.valueOf(28.7)) +// .build()); + +// courierTimeResponses = Arrays.asList( +// FulfillmentTimeResponse.builder() +// .groupByValue("CourierID_1") +// .averageDuration(BigDecimal.valueOf(18.3)) +// .build(), +// FulfillmentTimeResponse.builder() +// .groupByValue("CourierID_2") +// .averageDuration(BigDecimal.valueOf(22.1)) +// .build()); +// } + +// @Test +// public void testGetPlaceToShipTime_Overall() { +// // Configure mock service +// when(mockService.getPlaceToShipTimeResponse( +// startDate, endDate, PlaceToShipGroupBy.OVERALL)) +// .thenReturn(overallTimeResponses); + +// // Create request +// FulfillmentPlaceToShipRequest request = new FulfillmentPlaceToShipRequest( +// startDate, endDate, PlaceToShipGroupBy.OVERALL); + +// // Execute the method +// ResponseEntity> response = controller.getPlaceToShipTime(request); + +// // Verify response +// assertEquals(HttpStatus.OK, response.getStatusCode()); +// assertEquals(overallTimeResponses, response.getBody()); +// assertEquals(PlaceToShipGroupBy.OVERALL.toString(), response.getBody().get(0).getGroupByValue().toString()); +// assertEquals(BigDecimal.valueOf(24.5), response.getBody().get(0).getAverageDuration()); +// } + +// @Test +// public void testGetPlaceToShipTime_ByRegion() { +// // Configure mock service +// when(mockService.getPlaceToShipTimeResponse( +// startDate, endDate, PlaceToShipGroupBy.REGION)) +// .thenReturn(regionTimeResponses); + +// // Create request +// FulfillmentPlaceToShipRequest request = new FulfillmentPlaceToShipRequest( +// startDate, endDate, PlaceToShipGroupBy.REGION); + +// // Execute the method +// ResponseEntity> response = controller.getPlaceToShipTime(request); + +// // Verify response +// assertEquals(HttpStatus.OK, response.getStatusCode()); +// assertEquals(regionTimeResponses, response.getBody()); +// assertEquals("RegionID_1", response.getBody().get(0).getGroupByValue()); +// assertEquals("RegionID_2", response.getBody().get(1).getGroupByValue()); +// } + +// @Test +// public void testGetShipToDeliverTime_Overall() { +// // Configure mock service +// when(mockService.getShipToDeliverTimeResponse( +// startDate, endDate, ShipToDeliverGroupBy.OVERALL)) +// .thenReturn(overallTimeResponses); + +// // Create request +// FulfillmentShipToDeliverRequest request = new FulfillmentShipToDeliverRequest( +// startDate, endDate, ShipToDeliverGroupBy.OVERALL); + +// // Execute the method +// ResponseEntity> response = controller.getShipToDeliverTime(request); + +// // Verify response +// assertEquals(HttpStatus.OK, response.getStatusCode()); +// assertEquals(overallTimeResponses, response.getBody()); +// assertEquals(ShipToDeliverGroupBy.OVERALL.toString(), response.getBody().get(0).getGroupByValue().toString()); +// } + +// @Test +// public void testGetShipToDeliverTime_ByRegion() { +// // Configure mock service +// when(mockService.getShipToDeliverTimeResponse( +// startDate, endDate, ShipToDeliverGroupBy.REGION)) +// .thenReturn(regionTimeResponses); + +// // Create request +// FulfillmentShipToDeliverRequest request = new FulfillmentShipToDeliverRequest( +// startDate, endDate, ShipToDeliverGroupBy.REGION); + +// // Execute the method +// ResponseEntity> response = controller.getShipToDeliverTime(request); + +// // Verify response +// assertEquals(HttpStatus.OK, response.getStatusCode()); +// assertEquals(regionTimeResponses, response.getBody()); +// assertEquals("RegionID_1", response.getBody().get(0).getGroupByValue()); +// assertEquals("RegionID_2", response.getBody().get(1).getGroupByValue()); +// } + +// @Test +// public void testGetShipToDeliverTime_ByCourier() { +// // Configure mock service +// when(mockService.getShipToDeliverTimeResponse( +// startDate, endDate, ShipToDeliverGroupBy.COURIER)) +// .thenReturn(courierTimeResponses); + +// // Create request +// FulfillmentShipToDeliverRequest request = new FulfillmentShipToDeliverRequest( +// startDate, endDate, ShipToDeliverGroupBy.COURIER); + +// // Execute the method +// ResponseEntity> response = controller.getShipToDeliverTime(request); + +// // Verify response +// assertEquals(HttpStatus.OK, response.getStatusCode()); +// assertEquals(courierTimeResponses, response.getBody()); +// assertEquals("CourierID_1", response.getBody().get(0).getGroupByValue()); +// assertEquals("CourierID_2", response.getBody().get(1).getGroupByValue()); +// } + +// // Edge case tests + +// @Test +// public void testGetPlaceToShipTime_EmptyResponse() { +// // Configure mock service to return empty list +// when(mockService.getPlaceToShipTimeResponse( +// startDate, endDate, PlaceToShipGroupBy.OVERALL)) +// .thenReturn(Collections.emptyList()); + +// // Create request +// FulfillmentPlaceToShipRequest request = new FulfillmentPlaceToShipRequest( +// startDate, endDate, PlaceToShipGroupBy.OVERALL); + +// // Execute the method +// ResponseEntity> response = controller.getPlaceToShipTime(request); + +// // Verify response +// assertEquals(HttpStatus.OK, response.getStatusCode()); +// assertNotNull(response.getBody()); +// assertTrue(response.getBody().isEmpty()); +// } + +// @Test +// public void testGetShipToDeliverTime_EmptyResponse() { +// // Configure mock service to return empty list +// when(mockService.getShipToDeliverTimeResponse( +// startDate, endDate, ShipToDeliverGroupBy.OVERALL)) +// .thenReturn(Collections.emptyList()); + +// // Create request +// FulfillmentShipToDeliverRequest request = new FulfillmentShipToDeliverRequest( +// startDate, endDate, ShipToDeliverGroupBy.OVERALL); + +// // Execute the method +// ResponseEntity> response = controller.getShipToDeliverTime(request); + +// // Verify response +// assertEquals(HttpStatus.OK, response.getStatusCode()); +// assertNotNull(response.getBody()); +// assertTrue(response.getBody().isEmpty()); +// } + +// // @Test +// // public void testGetPlaceToShipTime_InvalidGroupBy() { +// // // Create request with invalid groupBy +// // FulfillmentPlaceToShipRequest request = new FulfillmentPlaceToShipRequest( +// // startDate, endDate, null); + +// // // Execute the method - should return bad request due to validation error +// // ResponseEntity> response = +// // controller.getPlaceToShipTime(request); + +// // // Verify response +// // assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); +// // } + +// // @Test +// // public void testGetShipToDeliverTime_InvalidGroupBy() { +// // // Create request with invalid groupBy +// // FulfillmentShipToDeliverRequest request = new +// // FulfillmentShipToDeliverRequest( +// // startDate, endDate, null); + +// // // Execute the method - should return bad request due to validation error +// // ResponseEntity> response = +// // controller.getShipToDeliverTime(request); + +// // // Verify response +// // assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); +// // } + +// @Test +// public void testGetPlaceToShipTime_SameDayRange() { +// // Test same start and end date +// LocalDate sameDate = LocalDate.of(2024, 1, 1); + +// // Configure mock service +// when(mockService.getPlaceToShipTimeResponse( +// sameDate, sameDate, PlaceToShipGroupBy.OVERALL)) +// .thenReturn(overallTimeResponses); + +// // Create request with same start and end date +// FulfillmentPlaceToShipRequest request = new FulfillmentPlaceToShipRequest( +// sameDate, sameDate, PlaceToShipGroupBy.OVERALL); + +// // Execute the method +// ResponseEntity> response = controller.getPlaceToShipTime(request); + +// // Verify response +// assertEquals(HttpStatus.OK, response.getStatusCode()); +// assertEquals(overallTimeResponses, response.getBody()); +// } + +// @Test +// public void testGetShipToDeliverTime_SameDayRange() { +// // Test same start and end date +// LocalDate sameDate = LocalDate.of(2024, 1, 1); + +// // Configure mock service +// when(mockService.getShipToDeliverTimeResponse( +// sameDate, sameDate, ShipToDeliverGroupBy.OVERALL)) +// .thenReturn(overallTimeResponses); + +// // Create request with same start and end date +// FulfillmentShipToDeliverRequest request = new FulfillmentShipToDeliverRequest( +// sameDate, sameDate, ShipToDeliverGroupBy.OVERALL); + +// // Execute the method +// ResponseEntity> response = controller.getShipToDeliverTime(request); + +// // Verify response +// assertEquals(HttpStatus.OK, response.getStatusCode()); +// assertEquals(overallTimeResponses, response.getBody()); +// } + +// @Test +// public void testGetPlaceToShipTime_FutureDates() { +// // Test future dates +// LocalDate futureStart = LocalDate.now().plusDays(1); +// LocalDate futureEnd = LocalDate.now().plusDays(30); + +// // Configure mock service - should return empty for future dates +// when(mockService.getPlaceToShipTimeResponse( +// futureStart, futureEnd, PlaceToShipGroupBy.OVERALL)) +// .thenReturn(Collections.emptyList()); + +// // Create request with future dates +// FulfillmentPlaceToShipRequest request = new FulfillmentPlaceToShipRequest( +// futureStart, futureEnd, PlaceToShipGroupBy.OVERALL); + +// // Execute the method +// ResponseEntity> response = controller.getPlaceToShipTime(request); + +// // Verify response +// assertEquals(HttpStatus.OK, response.getStatusCode()); +// assertNotNull(response.getBody()); +// assertTrue(response.getBody().isEmpty()); +// } + +// @Test +// public void testGetShipToDeliverTime_ServiceException() { +// // Configure mock service to throw exception +// when(mockService.getShipToDeliverTimeResponse( +// any(), any(), any())) +// .thenThrow(new RuntimeException("Service error")); + +// // Create request +// FulfillmentShipToDeliverRequest request = new FulfillmentShipToDeliverRequest( +// startDate, endDate, ShipToDeliverGroupBy.OVERALL); + +// // Execute the method - controller should handle exception +// // Note: Actual behavior depends on how controller handles exceptions +// // This might need adjustment based on actual implementation +// try { +// controller.getShipToDeliverTime(request); +// } catch (RuntimeException e) { +// assertEquals("Service error", e.getMessage()); +// } +// } +// } \ No newline at end of file diff --git a/src/test/java/com/Podzilla/analytics/integration/ProductAnalyticsServiceIntegrationTest.java b/src/test/java/com/Podzilla/analytics/integration/ProductAnalyticsServiceIntegrationTest.java index 8e1bd51..4a8d2ca 100644 --- a/src/test/java/com/Podzilla/analytics/integration/ProductAnalyticsServiceIntegrationTest.java +++ b/src/test/java/com/Podzilla/analytics/integration/ProductAnalyticsServiceIntegrationTest.java @@ -1,654 +1,679 @@ -package com.Podzilla.analytics.integration; - -import java.math.BigDecimal; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertTrue; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; - -import com.Podzilla.analytics.api.dtos.product.TopSellerRequest; -import com.Podzilla.analytics.api.dtos.product.TopSellerResponse; -import com.Podzilla.analytics.models.Courier; -import com.Podzilla.analytics.models.Customer; -import com.Podzilla.analytics.models.Order; -import com.Podzilla.analytics.models.Product; -import com.Podzilla.analytics.models.Region; -import com.Podzilla.analytics.models.SalesLineItem; -import com.Podzilla.analytics.repositories.CourierRepository; -import com.Podzilla.analytics.repositories.CustomerRepository; -import com.Podzilla.analytics.repositories.OrderRepository; -import com.Podzilla.analytics.repositories.ProductRepository; -import com.Podzilla.analytics.repositories.RegionRepository; -import com.Podzilla.analytics.repositories.SalesLineItemRepository; -import com.Podzilla.analytics.services.ProductAnalyticsService; - -import jakarta.transaction.Transactional; - -@SpringBootTest -@Transactional -class ProductAnalyticsServiceIntegrationTest { - - @Autowired - private ProductAnalyticsService productAnalyticsService; - - @Autowired - private ProductRepository productRepository; - - @Autowired - private OrderRepository orderRepository; - - @Autowired - private SalesLineItemRepository salesLineItemRepository; - - @Autowired - private CustomerRepository customerRepository; - - @Autowired - private CourierRepository courierRepository; - - @Autowired - private RegionRepository regionRepository; - - // Class-level test data objects - private Product phone; - private Product laptop; - private Product book; - private Product tablet; - private Product headphones; - - private Customer customer; - private Courier courier; - private Region region; - - private Order order1; // May 1st - private Order order2; // May 2nd - private Order order3; // May 3rd - private Order order4; // May 4th - Failed order - private Order order5; // May 5th - Products with same revenue but different units - private Order order6; // April 30th - Outside default test range - - @BeforeEach - void setUp() { - insertTestData(); - } - - private void insertTestData() { - // Create test products - phone = Product.builder() - .name("Smartphone") - .category("Electronics") - .cost(new BigDecimal("300.00")) - .lowStockThreshold(5) - .build(); - - laptop = Product.builder() - .name("Laptop") - .category("Electronics") - .cost(new BigDecimal("700.00")) - .lowStockThreshold(3) - .build(); - - book = Product.builder() - .name("Programming Book") - .category("Books") - .cost(new BigDecimal("20.00")) - .lowStockThreshold(10) - .build(); - - tablet = Product.builder() - .name("Tablet") - .category("Electronics") - .cost(new BigDecimal("200.00")) - .lowStockThreshold(5) - .build(); - - headphones = Product.builder() - .name("Wireless Headphones") - .category("Audio") - .cost(new BigDecimal("80.00")) - .lowStockThreshold(8) - .build(); - - productRepository.saveAll(List.of(phone, laptop, book, tablet, headphones)); - - // Create required entities for orders - customer = Customer.builder() - .name("Test Customer") - .build(); - customerRepository.save(customer); - - courier = Courier.builder() - .name("Test Courier") - .status(Courier.CourierStatus.ACTIVE) - .build(); - courierRepository.save(courier); - - region = Region.builder() - .city("Test City") - .state("Test State") - .country("Test Country") - .postalCode("12345") - .build(); - regionRepository.save(region); - - // Create orders with different dates and statuses - order1 = Order.builder() - .orderPlacedTimestamp(LocalDateTime.of(2024, 5, 1, 10, 0)) - .finalStatusTimestamp(LocalDateTime.of(2024, 5, 1, 15, 0)) - .status(Order.OrderStatus.COMPLETED) - .totalAmount(new BigDecimal("2000.00")) - .customer(customer) - .courier(courier) - .region(region) - .build(); - - order2 = Order.builder() - .orderPlacedTimestamp(LocalDateTime.of(2024, 5, 2, 11, 0)) - .finalStatusTimestamp(LocalDateTime.of(2024, 5, 2, 16, 0)) - .status(Order.OrderStatus.COMPLETED) - .totalAmount(new BigDecimal("1500.00")) - .customer(customer) - .courier(courier) - .region(region) - .build(); - - order3 = Order.builder() - .orderPlacedTimestamp(LocalDateTime.of(2024, 5, 3, 9, 0)) - .finalStatusTimestamp(LocalDateTime.of(2024, 5, 3, 14, 0)) - .status(Order.OrderStatus.COMPLETED) - .totalAmount(new BigDecimal("800.00")) - .customer(customer) - .courier(courier) - .region(region) - .build(); - - order4 = Order.builder() - .orderPlacedTimestamp(LocalDateTime.of(2024, 5, 4, 10, 0)) - .finalStatusTimestamp(LocalDateTime.of(2024, 5, 4, 12, 0)) - .status(Order.OrderStatus.FAILED) // Failed order - should be excluded - .failureReason("Payment declined") - .totalAmount(new BigDecimal("1200.00")) - .customer(customer) - .courier(courier) - .region(region) - .build(); - - order5 = Order.builder() - .orderPlacedTimestamp(LocalDateTime.of(2024, 5, 5, 14, 0)) - .finalStatusTimestamp(LocalDateTime.of(2024, 5, 5, 18, 0)) - .status(Order.OrderStatus.COMPLETED) - .totalAmount(new BigDecimal("1000.00")) - .customer(customer) - .courier(courier) - .region(region) - .build(); - - // Order outside of default test date range - order6 = Order.builder() - .orderPlacedTimestamp(LocalDateTime.of(2024, 4, 30, 9, 0)) - .finalStatusTimestamp(LocalDateTime.of(2024, 4, 30, 15, 0)) - .status(Order.OrderStatus.COMPLETED) - .totalAmount(new BigDecimal("750.00")) - .customer(customer) - .courier(courier) - .region(region) - .build(); - - orderRepository.saveAll(List.of(order1, order2, order3, order4, order5, order6)); - - // Create sales line items with different quantities and prices - // Order 1 - May 1 - SalesLineItem item1_1 = SalesLineItem.builder() - .order(order1) - .product(phone) - .quantity(2) // 2 phones - .pricePerUnit(new BigDecimal("500.00")) // $500 each - .build(); - - SalesLineItem item1_2 = SalesLineItem.builder() - .order(order1) - .product(laptop) - .quantity(1) // 1 laptop - .pricePerUnit(new BigDecimal("1000.00")) // $1000 each - .build(); - - // Order 2 - May 2 - SalesLineItem item2_1 = SalesLineItem.builder() - .order(order2) - .product(phone) - .quantity(3) // 3 phones - .pricePerUnit(new BigDecimal("500.00")) // $500 each - .build(); - - // Order 3 - May 3 - SalesLineItem item3_1 = SalesLineItem.builder() - .order(order3) - .product(book) - .quantity(5) // 5 books - .pricePerUnit(new BigDecimal("40.00")) // $40 each - .build(); - - SalesLineItem item3_2 = SalesLineItem.builder() - .order(order3) - .product(tablet) - .quantity(2) // 2 tablets - .pricePerUnit(new BigDecimal("300.00")) // $300 each - .build(); - - // Order 4 - May 4 (Failed order) - SalesLineItem item4_1 = SalesLineItem.builder() - .order(order4) - .product(laptop) - .quantity(1) // 1 laptop - .pricePerUnit(new BigDecimal("1200.00")) // $1200 each - .build(); - - // Order 5 - May 5 (Same revenue different products) - SalesLineItem item5_1 = SalesLineItem.builder() - .order(order5) - .product(headphones) - .quantity(5) // 5 headphones - .pricePerUnit(new BigDecimal("100.00")) // $100 each = $500 total - .build(); - - SalesLineItem item5_2 = SalesLineItem.builder() - .order(order5) - .product(tablet) - .quantity(1) // 1 tablet - .pricePerUnit(new BigDecimal("500.00")) // $500 each = $500 total (same as headphones) - .build(); - - // Order 6 - April 30 (Outside default range) - SalesLineItem item6_1 = SalesLineItem.builder() - .order(order6) - .product(phone) - .quantity(1) // 1 phone - .pricePerUnit(new BigDecimal("450.00")) // $450 each - .build(); - - SalesLineItem item6_2 = SalesLineItem.builder() - .order(order6) - .product(book) - .quantity(10) // 10 books - .pricePerUnit(new BigDecimal("30.00")) // $30 each - .build(); - - salesLineItemRepository.saveAll(List.of( - item1_1, item1_2, item2_1, item3_1, item3_2, - item4_1, item5_1, item5_2, item6_1, item6_2)); - } - - @Nested - @DisplayName("Basic Functionality Tests") - class BasicFunctionalityTests { - - @Test - @DisplayName("Get top sellers by revenue should return products in correct order") - void getTopSellers_byRevenue_shouldReturnCorrectOrder() { - TopSellerRequest request = TopSellerRequest.builder() - .startDate(LocalDate.of(2024, 5, 1)) - .endDate(LocalDate.of(2024, 5, 6)) - .limit(5) - .sortBy(TopSellerRequest.SortBy.REVENUE) - .build(); - - List results = productAnalyticsService.getTopSellers(request.getStartDate(), - request.getEndDate(), - request.getLimit(), - request.getSortBy()); - - System.out.println("Results: " + results); - assertThat(results).hasSize(5); // Phone, Laptop, Tablet, Headphones, Book - - // Verify proper ordering by revenue - assertThat(results.get(0).getProductName()).isEqualTo("Smartphone"); - assertThat(results.get(0).getValue()).isEqualByComparingTo("2500.00"); // 5 phones * $500 - assertThat(results.get(1).getProductName()).isEqualTo("Tablet"); - assertThat(results.get(1).getValue()).isEqualByComparingTo("1100.00"); // (2 * $300) + (1 * $500) - assertThat(results.get(2).getProductName()).isEqualTo("Laptop"); - assertThat(results.get(2).getValue()).isEqualByComparingTo("1000.00"); // 1 laptop * $1000 - - assertThat(results.get(3).getProductName()).isEqualTo("Wireless Headphones"); - assertThat(results.get(3).getValue()).isEqualByComparingTo("500.00"); // 5 * $100 - } - - @Test - @DisplayName("Get top sellers by units should return products in correct order") - void getTopSellers_byUnits_shouldReturnCorrectOrder() { - TopSellerRequest request = TopSellerRequest.builder() - .startDate(LocalDate.of(2024, 5, 1)) - .endDate(LocalDate.of(2024, 5, 6)) - .limit(5) - .sortBy(TopSellerRequest.SortBy.UNITS) - .build(); - - List results = productAnalyticsService.getTopSellers(request.getStartDate(), - request.getEndDate(), - request.getLimit(), - request.getSortBy()); - - assertThat(results).hasSize(5); - - // Order by units sold - assertThat(results.get(0).getProductName()).isEqualTo("Smartphone"); - assertThat(results.get(0).getValue()).isEqualByComparingTo("5"); // 2 + 3 phones - assertThat(results.get(1).getProductName()).isEqualTo("Wireless Headphones"); - assertThat(results.get(1).getValue()).isEqualByComparingTo("5"); // 5 headphones - assertThat(results.get(2).getProductName()).isEqualTo("Programming Book"); - assertThat(results.get(2).getValue()).isEqualByComparingTo("5"); // 5 books - - // Check correct tie-breaking behavior - Map orderMap = results.stream() - .collect(Collectors.toMap(TopSellerResponse::getProductName, - r -> r.getValue().intValue())); - - // Assuming tie-breaking is by revenue (which is how the repository query is - // sorted) - assertTrue(orderMap.get("Smartphone") >= orderMap.get("Wireless Headphones")); - assertTrue(orderMap.get("Wireless Headphones") >= orderMap.get("Programming Book")); - } - - @Test - @DisplayName("Get top sellers with limit should respect the limit parameter") - void getTopSellers_withLimit_shouldRespectLimit() { - TopSellerRequest request = TopSellerRequest.builder() - .startDate(LocalDate.of(2024, 5, 1)) - .endDate(LocalDate.of(2024, 5, 6)) - .limit(2) - .sortBy(TopSellerRequest.SortBy.REVENUE) - .build(); - - List results = productAnalyticsService.getTopSellers(request.getStartDate(), - request.getEndDate(), - request.getLimit(), - request.getSortBy()); - // System.out.println("Results:**-*-*-*-**-* " + results); - - assertThat(results).hasSize(2); - assertThat(results.get(0).getProductName()).isEqualTo("Smartphone"); - assertThat(results.get(1).getProductName()).isEqualTo("Tablet"); - } - - @Test - @DisplayName("Get top sellers with date range should only include orders in range") - void getTopSellers_withDateRange_shouldOnlyIncludeOrdersInRange() { - TopSellerRequest request = TopSellerRequest.builder() - .startDate(LocalDate.of(2024, 5, 2)) // Start from May 2nd - .endDate(LocalDate.of(2024, 5, 4)) // End before May 4th - .sortBy(TopSellerRequest.SortBy.REVENUE) - .limit(5) - .build(); - - List results = productAnalyticsService.getTopSellers(request.getStartDate(), - request.getEndDate(), - request.getLimit(), - request.getSortBy()); - - // Should have only phone, book, and tablet (from orders 2 and 3) - assertThat(results).hasSize(3); - - // First should be phone with only Order 2 revenue - assertThat(results.get(0).getProductName()).isEqualTo("Smartphone"); - assertThat(results.get(0).getValue()).isEqualByComparingTo("1500.00"); // Only order 2: 3 phones * $500 - - // Should include tablets from order 3 - boolean hasTablet = results.stream() - .anyMatch(r -> r.getProductName().equals("Tablet") - && r.getValue().compareTo(new BigDecimal("600.00")) == 0); - assertThat(hasTablet).isTrue(); - - // Should include books from order 3 - boolean hasBook = results.stream() - .anyMatch(r -> r.getProductName().equals("Programming Book") - && r.getValue().compareTo(new BigDecimal("200.00")) == 0); - assertThat(hasBook).isTrue(); - - // Should NOT include laptop (only in order 1) - boolean hasLaptop = results.stream() - .anyMatch(r -> r.getProductName().equals("Laptop")); - assertThat(hasLaptop).isFalse(); - } - } - - @Nested - @DisplayName("Edge Case Tests") - class EdgeCaseTests { - - @Test - @DisplayName("Get top sellers with empty result set should return empty list") - void getTopSellers_withNoMatchingData_shouldReturnEmptyList() { - TopSellerRequest request = TopSellerRequest.builder() - .startDate(LocalDate.of(2024, 6, 1)) // Future date with no data - .endDate(LocalDate.of(2024, 6, 2)) - .sortBy(TopSellerRequest.SortBy.REVENUE) - .limit(5) - .build(); - - List results = productAnalyticsService.getTopSellers(request.getStartDate(), - request.getEndDate(), - request.getLimit(), - request.getSortBy()); - - assertThat(results).isEmpty(); - } - - @Test - @DisplayName("Get top sellers with zero limit should return all results") - void getTopSellers_withZeroLimit_shouldReturnAllResults() { - TopSellerRequest request = TopSellerRequest.builder() - .startDate(LocalDate.of(2024, 5, 1)) - .endDate(LocalDate.of(2024, 5, 6)) - .limit(0) // Zero limit - .sortBy(TopSellerRequest.SortBy.REVENUE) - .build(); - - List results = productAnalyticsService.getTopSellers(request.getStartDate(), - request.getEndDate(), - request.getLimit(), - request.getSortBy()); - - // Should return all 4 products with sales in the period - assertThat(results).hasSize(0); - } - - @Test - @DisplayName("Get top sellers with single day range (inclusive) should work correctly") - void getTopSellers_withSingleDayRange_shouldWorkCorrectly() { - TopSellerRequest request = TopSellerRequest.builder() - .startDate(LocalDate.of(2024, 5, 1)) - .endDate(LocalDate.of(2024, 5, 1)) // End date inclusive - .limit(5) - .sortBy(TopSellerRequest.SortBy.REVENUE) - .build(); - - List results = productAnalyticsService.getTopSellers(request.getStartDate(), - request.getEndDate(), - request.getLimit(), - request.getSortBy()); - - // Should only include products from order1 (May 1st) - assertThat(results).hasSize(2); - - // Smartphone should be included - boolean hasPhone = results.stream() - .anyMatch(r -> r.getProductName().equals("Smartphone") - && r.getValue().compareTo(new BigDecimal("1000.00")) == 0); - assertThat(hasPhone).isTrue(); - - // Laptop should be included - boolean hasLaptop = results.stream() - .anyMatch(r -> r.getProductName().equals("Laptop") - && r.getValue().compareTo(new BigDecimal("1000.00")) == 0); - assertThat(hasLaptop).isTrue(); - } - - @Test - @DisplayName("Get top sellers should exclude failed orders") - void getTopSellers_shouldExcludeFailedOrders() { - TopSellerRequest request = TopSellerRequest.builder() - .startDate(LocalDate.of(2024, 5, 4)) // Only include May 4th (failed order day) - .endDate(LocalDate.of(2024, 5, 4)) - .limit(5) - .sortBy(TopSellerRequest.SortBy.REVENUE) - .build(); - - List results = productAnalyticsService.getTopSellers(request.getStartDate(), - request.getEndDate(), - request.getLimit(), - request.getSortBy()); - - // Should be empty because the only order on May 4th was failed - assertThat(results).isEmpty(); - - // Specifically, the laptop from the failed order should not be included - boolean hasLaptop = results.stream() - .anyMatch(r -> r.getProductName().equals("Laptop")); - assertThat(hasLaptop).isFalse(); - } - - @Test - @DisplayName("Get top sellers including boundary dates should work correctly") - void getTopSellers_withBoundaryDates_shouldWorkCorrectly() { - TopSellerRequest request = TopSellerRequest.builder() - .startDate(LocalDate.of(2024, 4, 30)) // Include April 30 - .endDate(LocalDate.of(2024, 4, 30)) // Exclude May 1 - .limit(5) - .sortBy(TopSellerRequest.SortBy.REVENUE) - .build(); - - List results = productAnalyticsService.getTopSellers(request.getStartDate(), - request.getEndDate(), - request.getLimit(), - request.getSortBy()); - - // Should only include products from April 30th (order6) - assertThat(results).hasSize(2); - - // Book should be included - boolean hasBook = results.stream() - .anyMatch(r -> r.getProductName().equals("Programming Book") - && r.getValue().compareTo(new BigDecimal("300.00")) == 0); - assertThat(hasBook).isTrue(); - - // Phone should be included - boolean hasPhone = results.stream() - .anyMatch(r -> r.getProductName().equals("Smartphone") - && r.getValue().compareTo(new BigDecimal("450.00")) == 0); - assertThat(hasPhone).isTrue(); - } - } - - @Nested - @DisplayName("Sorting and Value Tests") - class SortingAndValueTests { - - @Test - @DisplayName("Products with same revenue but different units should sort by revenue first") - void getTopSellers_withSameRevenue_shouldSortCorrectly() { - TopSellerRequest request = TopSellerRequest.builder() - .startDate(LocalDate.of(2024, 5, 5)) // Only include May 5th order - .endDate(LocalDate.of(2024, 5, 6)) - .limit(5) - .sortBy(TopSellerRequest.SortBy.REVENUE) - .build(); - - List results = productAnalyticsService.getTopSellers(request.getStartDate(), - request.getEndDate(), - request.getLimit(), - request.getSortBy()); - - // Should have both products with $500 revenue - assertThat(results).hasSize(2); - - // Both should have same revenue value - assertThat(results.get(0).getValue()).isEqualByComparingTo(results.get(1).getValue()); - assertThat(results.get(0).getValue()).isEqualByComparingTo("500.00"); - - // Check units separately to verify the data is correct - // (This doesn't test sorting order, but verifies the test data is as expected) - boolean hasTablet = results.stream() - .anyMatch(r -> r.getProductName().equals("Tablet")); - boolean hasHeadphones = results.stream() - .anyMatch(r -> r.getProductName().equals("Wireless Headphones")); - - assertThat(hasTablet).isTrue(); - assertThat(hasHeadphones).isTrue(); - } - - @Test - @DisplayName("Get top sellers by units with products having same units should respect secondary sort") - void getTopSellers_byUnitsWithSameUnits_shouldRespectSecondarySorting() { - TopSellerRequest request = TopSellerRequest.builder() - .startDate(LocalDate.of(2024, 5, 1)) - .endDate(LocalDate.of(2024, 5, 6)) - .sortBy(TopSellerRequest.SortBy.UNITS).limit(10) - .build(); - - List results = productAnalyticsService.getTopSellers(request.getStartDate(), - request.getEndDate(), - request.getLimit(), - request.getSortBy()); - - // Find all products with 5 units - List productsWithFiveUnits = results.stream() - .filter(r -> r.getValue().compareTo(BigDecimal.valueOf(5)) == 0) - .collect(Collectors.toList()); - - // Should have 3 products with 5 units (phone, headphones, book) - assertThat(productsWithFiveUnits.size()).isEqualTo(3); - - // Verify that secondary sorting works (we expect by revenue) - // Get product names in order - List productOrder = productsWithFiveUnits.stream() - .map(TopSellerResponse::getProductName) - .collect(Collectors.toList()); - - // Expected order: Smartphone ($2500), Headphones ($500), Book ($200) - int smartphoneIdx = productOrder.indexOf("Smartphone"); - int headphonesIdx = productOrder.indexOf("Wireless Headphones"); - int bookIdx = productOrder.indexOf("Programming Book"); - - assertTrue(smartphoneIdx < headphonesIdx, "Smartphone should come before Headphones"); - assertTrue(headphonesIdx < bookIdx, "Headphones should come before Programming Book"); - } - } - - @Nested - @DisplayName("Request Parameter Tests") - class RequestParameterTests { - - @Test - @DisplayName("Get top sellers with swapped date range should handle gracefully") - void getTopSellers_withSwappedDateRange_shouldHandleGracefully() { - // Start date is after end date - test depends on how service handles this - TopSellerRequest request = TopSellerRequest.builder() - .startDate(LocalDate.of(2024, 5, 6)) // Start after end - .endDate(LocalDate.of(2024, 5, 1)) // End before start - .limit(5) - .sortBy(TopSellerRequest.SortBy.REVENUE) - .build(); - - // If service handles swapped dates, this may return empty result - // or throw an exception - List results = productAnalyticsService.getTopSellers(request.getStartDate(), - request.getEndDate(), - request.getLimit(), - request.getSortBy()); - // Should return empty list if swapped dates are handled - assertThat(results).isEmpty(); - // If exception is expected, you may need to adjust this test - // assertThrows(IllegalArgumentException.class, () -> - // productAnalyticsService.getTopSellers(request)); - } - } -} +// package com.Podzilla.analytics.integration; + +// import java.math.BigDecimal; +// import java.time.LocalDate; +// import java.time.LocalDateTime; +// import java.util.List; +// import java.util.Map; +// import java.util.UUID; +// import java.util.stream.Collectors; + +// import static org.assertj.core.api.Assertions.assertThat; +// import static org.junit.jupiter.api.Assertions.assertTrue; +// import org.junit.jupiter.api.BeforeEach; +// import org.junit.jupiter.api.DisplayName; +// import org.junit.jupiter.api.Nested; +// import org.junit.jupiter.api.Test; +// import org.springframework.beans.factory.annotation.Autowired; +// import org.springframework.boot.test.context.SpringBootTest; + +// import com.Podzilla.analytics.api.dtos.product.TopSellerRequest; +// import com.Podzilla.analytics.api.dtos.product.TopSellerResponse; +// import com.Podzilla.analytics.models.Courier; +// import com.Podzilla.analytics.models.Customer; +// import com.Podzilla.analytics.models.Order; +// import com.Podzilla.analytics.models.Product; +// import com.Podzilla.analytics.models.Region; +// import com.Podzilla.analytics.models.SalesLineItem; +// import com.Podzilla.analytics.repositories.CourierRepository; +// import com.Podzilla.analytics.repositories.CustomerRepository; +// import com.Podzilla.analytics.repositories.OrderRepository; +// import com.Podzilla.analytics.repositories.ProductRepository; +// import com.Podzilla.analytics.repositories.RegionRepository; +// import com.Podzilla.analytics.repositories.SalesLineItemRepository; +// import com.Podzilla.analytics.services.ProductAnalyticsService; + +// import jakarta.transaction.Transactional; + +// @SpringBootTest +// @Transactional +// class ProductAnalyticsServiceIntegrationTest { + +// @Autowired +// private ProductAnalyticsService productAnalyticsService; + +// @Autowired +// private ProductRepository productRepository; + +// @Autowired +// private OrderRepository orderRepository; + +// @Autowired +// private SalesLineItemRepository salesLineItemRepository; + +// @Autowired +// private CustomerRepository customerRepository; + +// @Autowired +// private CourierRepository courierRepository; + +// @Autowired +// private RegionRepository regionRepository; + +// // Class-level test data objects +// private Product phone; +// private Product laptop; +// private Product book; +// private Product tablet; +// private Product headphones; + +// private Customer customer; +// private Courier courier; +// private Region region; + +// private Order order1; // May 1st +// private Order order2; // May 2nd +// private Order order3; // May 3rd +// private Order order4; // May 4th - Failed order +// private Order order5; // May 5th - Products with same revenue but different units +// private Order order6; // April 30th - Outside default test range + +// @BeforeEach +// void setUp() { +// insertTestData(); +// } + +// private void insertTestData() { +// // Create test products +// phone = Product.builder() +// .id(UUID.randomUUID()) +// .name("Smartphone") +// .category("Electronics") +// .cost(new BigDecimal("300.00")) +// .lowStockThreshold(5) +// .build(); + +// laptop = Product.builder() +// .id(UUID.randomUUID()) +// .name("Laptop") +// .category("Electronics") +// .cost(new BigDecimal("700.00")) +// .lowStockThreshold(3) +// .build(); + +// book = Product.builder() +// .id(UUID.randomUUID()) +// .name("Programming Book") +// .category("Books") +// .cost(new BigDecimal("20.00")) +// .lowStockThreshold(10) +// .build(); + +// tablet = Product.builder() +// .id(UUID.randomUUID()) +// .name("Tablet") +// .category("Electronics") +// .cost(new BigDecimal("200.00")) +// .lowStockThreshold(5) +// .build(); + +// headphones = Product.builder() +// .id(UUID.randomUUID()) +// .name("Wireless Headphones") +// .category("Audio") +// .cost(new BigDecimal("80.00")) +// .lowStockThreshold(8) +// .build(); + +// productRepository.saveAll(List.of(phone, laptop, book, tablet, headphones)); + +// // Create required entities for orders +// customer = Customer.builder() +// .id(UUID.randomUUID()) +// .name("Test Customer") +// .build(); +// customerRepository.save(customer); + +// courier = Courier.builder() +// .id(UUID.randomUUID()) +// .name("Test Courier") +// .status(Courier.CourierStatus.ACTIVE) +// .build(); +// courierRepository.save(courier); + +// region = Region.builder() +// .id(UUID.randomUUID()) +// .city("Test City") +// .state("Test State") +// .country("Test Country") +// .postalCode("12345") +// .build(); +// regionRepository.save(region); + +// // Create orders with different dates and statuses +// order1 = Order.builder() +// .id(UUID.randomUUID()) +// .orderPlacedTimestamp(LocalDateTime.of(2024, 5, 1, 10, 0)) +// .finalStatusTimestamp(LocalDateTime.of(2024, 5, 1, 15, 0)) +// .status(Order.OrderStatus.COMPLETED) +// .totalAmount(new BigDecimal("2000.00")) +// .customer(customer) +// .courier(courier) +// .region(region) +// .build(); + +// order2 = Order.builder() +// .id(UUID.randomUUID()) +// .orderPlacedTimestamp(LocalDateTime.of(2024, 5, 2, 11, 0)) +// .finalStatusTimestamp(LocalDateTime.of(2024, 5, 2, 16, 0)) +// .status(Order.OrderStatus.COMPLETED) +// .totalAmount(new BigDecimal("1500.00")) +// .customer(customer) +// .courier(courier) +// .region(region) +// .build(); + +// order3 = Order.builder() +// .id(UUID.randomUUID()) +// .orderPlacedTimestamp(LocalDateTime.of(2024, 5, 3, 9, 0)) +// .finalStatusTimestamp(LocalDateTime.of(2024, 5, 3, 14, 0)) +// .status(Order.OrderStatus.COMPLETED) +// .totalAmount(new BigDecimal("800.00")) +// .customer(customer) +// .courier(courier) +// .region(region) +// .build(); + +// order4 = Order.builder() +// .id(UUID.randomUUID()) +// .orderPlacedTimestamp(LocalDateTime.of(2024, 5, 4, 10, 0)) +// .finalStatusTimestamp(LocalDateTime.of(2024, 5, 4, 12, 0)) +// .status(Order.OrderStatus.FAILED) // Failed order - should be excluded +// .failureReason("Payment declined") +// .totalAmount(new BigDecimal("1200.00")) +// .customer(customer) +// .courier(courier) +// .region(region) +// .build(); + +// order5 = Order.builder() +// .id(UUID.randomUUID()) +// .orderPlacedTimestamp(LocalDateTime.of(2024, 5, 5, 14, 0)) +// .finalStatusTimestamp(LocalDateTime.of(2024, 5, 5, 18, 0)) +// .status(Order.OrderStatus.COMPLETED) +// .totalAmount(new BigDecimal("1000.00")) +// .customer(customer) +// .courier(courier) +// .region(region) +// .build(); + +// // Order outside of default test date range +// order6 = Order.builder() +// .id(UUID.randomUUID()) +// .orderPlacedTimestamp(LocalDateTime.of(2024, 4, 30, 9, 0)) +// .finalStatusTimestamp(LocalDateTime.of(2024, 4, 30, 15, 0)) +// .status(Order.OrderStatus.COMPLETED) +// .totalAmount(new BigDecimal("750.00")) +// .customer(customer) +// .courier(courier) +// .region(region) +// .build(); + +// orderRepository.saveAll(List.of(order1, order2, order3, order4, order5, order6)); + +// // Create sales line items with different quantities and prices +// // Order 1 - May 1 +// SalesLineItem item1_1 = SalesLineItem.builder() +// .id(UUID.randomUUID()) +// .order(order1) +// .product(phone) +// .quantity(2) // 2 phones +// .pricePerUnit(new BigDecimal("500.00")) // $500 each +// .build(); + +// SalesLineItem item1_2 = SalesLineItem.builder() +// .id(UUID.randomUUID()) +// .order(order1) +// .product(laptop) +// .quantity(1) // 1 laptop +// .pricePerUnit(new BigDecimal("1000.00")) // $1000 each +// .build(); + +// // Order 2 - May 2 +// SalesLineItem item2_1 = SalesLineItem.builder() +// .id(UUID.randomUUID()) +// .order(order2) +// .product(phone) +// .quantity(3) // 3 phones +// .pricePerUnit(new BigDecimal("500.00")) // $500 each +// .build(); + +// // Order 3 - May 3 +// SalesLineItem item3_1 = SalesLineItem.builder() +// .id(UUID.randomUUID()) +// .order(order3) +// .product(book) +// .quantity(5) // 5 books +// .pricePerUnit(new BigDecimal("40.00")) // $40 each +// .build(); + +// SalesLineItem item3_2 = SalesLineItem.builder() +// .id(UUID.randomUUID()) +// .order(order3) +// .product(tablet) +// .quantity(2) // 2 tablets +// .pricePerUnit(new BigDecimal("300.00")) // $300 each +// .build(); + +// // Order 4 - May 4 (Failed order) +// SalesLineItem item4_1 = SalesLineItem.builder() +// .id(UUID.randomUUID()) +// .order(order4) +// .product(laptop) +// .quantity(1) // 1 laptop +// .pricePerUnit(new BigDecimal("1200.00")) // $1200 each +// .build(); + +// // Order 5 - May 5 (Same revenue different products) +// SalesLineItem item5_1 = SalesLineItem.builder() +// .id(UUID.randomUUID()) +// .order(order5) +// .product(headphones) +// .quantity(5) // 5 headphones +// .pricePerUnit(new BigDecimal("100.00")) // $100 each = $500 total +// .build(); + +// SalesLineItem item5_2 = SalesLineItem.builder() +// .id(UUID.randomUUID()) +// .order(order5) +// .product(tablet) +// .quantity(1) // 1 tablet +// .pricePerUnit(new BigDecimal("500.00")) // $500 each = $500 total (same as headphones) +// .build(); + +// // Order 6 - April 30 (Outside default range) +// SalesLineItem item6_1 = SalesLineItem.builder() +// .id(UUID.randomUUID()) +// .order(order6) +// .product(phone) +// .quantity(1) // 1 phone +// .pricePerUnit(new BigDecimal("450.00")) // $450 each +// .build(); + +// SalesLineItem item6_2 = SalesLineItem.builder() +// .id(UUID.randomUUID()) +// .order(order6) +// .product(book) +// .quantity(10) // 10 books +// .pricePerUnit(new BigDecimal("30.00")) // $30 each +// .build(); + +// salesLineItemRepository.saveAll(List.of( +// item1_1, item1_2, item2_1, item3_1, item3_2, +// item4_1, item5_1, item5_2, item6_1, item6_2)); +// } + +// @Nested +// @DisplayName("Basic Functionality Tests") +// class BasicFunctionalityTests { + +// @Test +// @DisplayName("Get top sellers by revenue should return products in correct order") +// void getTopSellers_byRevenue_shouldReturnCorrectOrder() { +// TopSellerRequest request = TopSellerRequest.builder() +// .startDate(LocalDate.of(2024, 5, 1)) +// .endDate(LocalDate.of(2024, 5, 6)) +// .limit(5) +// .sortBy(TopSellerRequest.SortBy.REVENUE) +// .build(); + +// List results = productAnalyticsService.getTopSellers(request.getStartDate(), +// request.getEndDate(), +// request.getLimit(), +// request.getSortBy()); + +// System.out.println("Results: " + results); +// assertThat(results).hasSize(5); // Phone, Laptop, Tablet, Headphones, Book + +// // Verify proper ordering by revenue +// assertThat(results.get(0).getProductName()).isEqualTo("Smartphone"); +// assertThat(results.get(0).getValue()).isEqualByComparingTo("2500.00"); // 5 phones * $500 +// assertThat(results.get(1).getProductName()).isEqualTo("Tablet"); +// assertThat(results.get(1).getValue()).isEqualByComparingTo("1100.00"); // (2 * $300) + (1 * $500) +// assertThat(results.get(2).getProductName()).isEqualTo("Laptop"); +// assertThat(results.get(2).getValue()).isEqualByComparingTo("1000.00"); // 1 laptop * $1000 + +// assertThat(results.get(3).getProductName()).isEqualTo("Wireless Headphones"); +// assertThat(results.get(3).getValue()).isEqualByComparingTo("500.00"); // 5 * $100 +// } + +// @Test +// @DisplayName("Get top sellers by units should return products in correct order") +// void getTopSellers_byUnits_shouldReturnCorrectOrder() { +// TopSellerRequest request = TopSellerRequest.builder() +// .startDate(LocalDate.of(2024, 5, 1)) +// .endDate(LocalDate.of(2024, 5, 6)) +// .limit(5) +// .sortBy(TopSellerRequest.SortBy.UNITS) +// .build(); + +// List results = productAnalyticsService.getTopSellers(request.getStartDate(), +// request.getEndDate(), +// request.getLimit(), +// request.getSortBy()); + +// assertThat(results).hasSize(5); + +// // Order by units sold +// assertThat(results.get(0).getProductName()).isEqualTo("Smartphone"); +// assertThat(results.get(0).getValue()).isEqualByComparingTo("5"); // 2 + 3 phones +// assertThat(results.get(1).getProductName()).isEqualTo("Wireless Headphones"); +// assertThat(results.get(1).getValue()).isEqualByComparingTo("5"); // 5 headphones +// assertThat(results.get(2).getProductName()).isEqualTo("Programming Book"); +// assertThat(results.get(2).getValue()).isEqualByComparingTo("5"); // 5 books + +// // Check correct tie-breaking behavior +// Map orderMap = results.stream() +// .collect(Collectors.toMap(TopSellerResponse::getProductName, +// r -> r.getValue().intValue())); + +// // Assuming tie-breaking is by revenue (which is how the repository query is +// // sorted) +// assertTrue(orderMap.get("Smartphone") >= orderMap.get("Wireless Headphones")); +// assertTrue(orderMap.get("Wireless Headphones") >= orderMap.get("Programming Book")); +// } + +// @Test +// @DisplayName("Get top sellers with limit should respect the limit parameter") +// void getTopSellers_withLimit_shouldRespectLimit() { +// TopSellerRequest request = TopSellerRequest.builder() +// .startDate(LocalDate.of(2024, 5, 1)) +// .endDate(LocalDate.of(2024, 5, 6)) +// .limit(2) +// .sortBy(TopSellerRequest.SortBy.REVENUE) +// .build(); + +// List results = productAnalyticsService.getTopSellers(request.getStartDate(), +// request.getEndDate(), +// request.getLimit(), +// request.getSortBy()); +// // System.out.println("Results:**-*-*-*-**-* " + results); + +// assertThat(results).hasSize(2); +// assertThat(results.get(0).getProductName()).isEqualTo("Smartphone"); +// assertThat(results.get(1).getProductName()).isEqualTo("Tablet"); +// } + +// @Test +// @DisplayName("Get top sellers with date range should only include orders in range") +// void getTopSellers_withDateRange_shouldOnlyIncludeOrdersInRange() { +// TopSellerRequest request = TopSellerRequest.builder() +// .startDate(LocalDate.of(2024, 5, 2)) // Start from May 2nd +// .endDate(LocalDate.of(2024, 5, 4)) // End before May 4th +// .sortBy(TopSellerRequest.SortBy.REVENUE) +// .limit(5) +// .build(); + +// List results = productAnalyticsService.getTopSellers(request.getStartDate(), +// request.getEndDate(), +// request.getLimit(), +// request.getSortBy()); + +// // Should have only phone, book, and tablet (from orders 2 and 3) +// assertThat(results).hasSize(3); + +// // First should be phone with only Order 2 revenue +// assertThat(results.get(0).getProductName()).isEqualTo("Smartphone"); +// assertThat(results.get(0).getValue()).isEqualByComparingTo("1500.00"); // Only order 2: 3 phones * $500 + +// // Should include tablets from order 3 +// boolean hasTablet = results.stream() +// .anyMatch(r -> r.getProductName().equals("Tablet") +// && r.getValue().compareTo(new BigDecimal("600.00")) == 0); +// assertThat(hasTablet).isTrue(); + +// // Should include books from order 3 +// boolean hasBook = results.stream() +// .anyMatch(r -> r.getProductName().equals("Programming Book") +// && r.getValue().compareTo(new BigDecimal("200.00")) == 0); +// assertThat(hasBook).isTrue(); + +// // Should NOT include laptop (only in order 1) +// boolean hasLaptop = results.stream() +// .anyMatch(r -> r.getProductName().equals("Laptop")); +// assertThat(hasLaptop).isFalse(); +// } +// } + +// @Nested +// @DisplayName("Edge Case Tests") +// class EdgeCaseTests { + +// @Test +// @DisplayName("Get top sellers with empty result set should return empty list") +// void getTopSellers_withNoMatchingData_shouldReturnEmptyList() { +// TopSellerRequest request = TopSellerRequest.builder() +// .startDate(LocalDate.of(2024, 6, 1)) // Future date with no data +// .endDate(LocalDate.of(2024, 6, 2)) +// .sortBy(TopSellerRequest.SortBy.REVENUE) +// .limit(5) +// .build(); + +// List results = productAnalyticsService.getTopSellers(request.getStartDate(), +// request.getEndDate(), +// request.getLimit(), +// request.getSortBy()); + +// assertThat(results).isEmpty(); +// } + +// @Test +// @DisplayName("Get top sellers with zero limit should return all results") +// void getTopSellers_withZeroLimit_shouldReturnAllResults() { +// TopSellerRequest request = TopSellerRequest.builder() +// .startDate(LocalDate.of(2024, 5, 1)) +// .endDate(LocalDate.of(2024, 5, 6)) +// .limit(0) // Zero limit +// .sortBy(TopSellerRequest.SortBy.REVENUE) +// .build(); + +// List results = productAnalyticsService.getTopSellers(request.getStartDate(), +// request.getEndDate(), +// request.getLimit(), +// request.getSortBy()); + +// // Should return all 4 products with sales in the period +// assertThat(results).hasSize(0); +// } + +// @Test +// @DisplayName("Get top sellers with single day range (inclusive) should work correctly") +// void getTopSellers_withSingleDayRange_shouldWorkCorrectly() { +// TopSellerRequest request = TopSellerRequest.builder() +// .startDate(LocalDate.of(2024, 5, 1)) +// .endDate(LocalDate.of(2024, 5, 1)) // End date inclusive +// .limit(5) +// .sortBy(TopSellerRequest.SortBy.REVENUE) +// .build(); + +// List results = productAnalyticsService.getTopSellers(request.getStartDate(), +// request.getEndDate(), +// request.getLimit(), +// request.getSortBy()); + +// // Should only include products from order1 (May 1st) +// assertThat(results).hasSize(2); + +// // Smartphone should be included +// boolean hasPhone = results.stream() +// .anyMatch(r -> r.getProductName().equals("Smartphone") +// && r.getValue().compareTo(new BigDecimal("1000.00")) == 0); +// assertThat(hasPhone).isTrue(); + +// // Laptop should be included +// boolean hasLaptop = results.stream() +// .anyMatch(r -> r.getProductName().equals("Laptop") +// && r.getValue().compareTo(new BigDecimal("1000.00")) == 0); +// assertThat(hasLaptop).isTrue(); +// } + +// @Test +// @DisplayName("Get top sellers should exclude failed orders") +// void getTopSellers_shouldExcludeFailedOrders() { +// TopSellerRequest request = TopSellerRequest.builder() +// .startDate(LocalDate.of(2024, 5, 4)) // Only include May 4th (failed order day) +// .endDate(LocalDate.of(2024, 5, 4)) +// .limit(5) +// .sortBy(TopSellerRequest.SortBy.REVENUE) +// .build(); + +// List results = productAnalyticsService.getTopSellers(request.getStartDate(), +// request.getEndDate(), +// request.getLimit(), +// request.getSortBy()); + +// // Should be empty because the only order on May 4th was failed +// assertThat(results).isEmpty(); + +// // Specifically, the laptop from the failed order should not be included +// boolean hasLaptop = results.stream() +// .anyMatch(r -> r.getProductName().equals("Laptop")); +// assertThat(hasLaptop).isFalse(); +// } + +// @Test +// @DisplayName("Get top sellers including boundary dates should work correctly") +// void getTopSellers_withBoundaryDates_shouldWorkCorrectly() { +// TopSellerRequest request = TopSellerRequest.builder() +// .startDate(LocalDate.of(2024, 4, 30)) // Include April 30 +// .endDate(LocalDate.of(2024, 4, 30)) // Exclude May 1 +// .limit(5) +// .sortBy(TopSellerRequest.SortBy.REVENUE) +// .build(); + +// List results = productAnalyticsService.getTopSellers(request.getStartDate(), +// request.getEndDate(), +// request.getLimit(), +// request.getSortBy()); + +// // Should only include products from April 30th (order6) +// assertThat(results).hasSize(2); + +// // Book should be included +// boolean hasBook = results.stream() +// .anyMatch(r -> r.getProductName().equals("Programming Book") +// && r.getValue().compareTo(new BigDecimal("300.00")) == 0); +// assertThat(hasBook).isTrue(); + +// // Phone should be included +// boolean hasPhone = results.stream() +// .anyMatch(r -> r.getProductName().equals("Smartphone") +// && r.getValue().compareTo(new BigDecimal("450.00")) == 0); +// assertThat(hasPhone).isTrue(); +// } +// } + +// @Nested +// @DisplayName("Sorting and Value Tests") +// class SortingAndValueTests { + +// @Test +// @DisplayName("Products with same revenue but different units should sort by revenue first") +// void getTopSellers_withSameRevenue_shouldSortCorrectly() { +// TopSellerRequest request = TopSellerRequest.builder() +// .startDate(LocalDate.of(2024, 5, 5)) // Only include May 5th order +// .endDate(LocalDate.of(2024, 5, 6)) +// .limit(5) +// .sortBy(TopSellerRequest.SortBy.REVENUE) +// .build(); + +// List results = productAnalyticsService.getTopSellers(request.getStartDate(), +// request.getEndDate(), +// request.getLimit(), +// request.getSortBy()); + +// // Should have both products with $500 revenue +// assertThat(results).hasSize(2); + +// // Both should have same revenue value +// assertThat(results.get(0).getValue()).isEqualByComparingTo(results.get(1).getValue()); +// assertThat(results.get(0).getValue()).isEqualByComparingTo("500.00"); + +// // Check units separately to verify the data is correct +// // (This doesn't test sorting order, but verifies the test data is as expected) +// boolean hasTablet = results.stream() +// .anyMatch(r -> r.getProductName().equals("Tablet")); +// boolean hasHeadphones = results.stream() +// .anyMatch(r -> r.getProductName().equals("Wireless Headphones")); + +// assertThat(hasTablet).isTrue(); +// assertThat(hasHeadphones).isTrue(); +// } + +// @Test +// @DisplayName("Get top sellers by units with products having same units should respect secondary sort") +// void getTopSellers_byUnitsWithSameUnits_shouldRespectSecondarySorting() { +// TopSellerRequest request = TopSellerRequest.builder() +// .startDate(LocalDate.of(2024, 5, 1)) +// .endDate(LocalDate.of(2024, 5, 6)) +// .sortBy(TopSellerRequest.SortBy.UNITS).limit(10) +// .build(); + +// List results = productAnalyticsService.getTopSellers(request.getStartDate(), +// request.getEndDate(), +// request.getLimit(), +// request.getSortBy()); + +// // Find all products with 5 units +// List productsWithFiveUnits = results.stream() +// .filter(r -> r.getValue().compareTo(BigDecimal.valueOf(5)) == 0) +// .collect(Collectors.toList()); + +// // Should have 3 products with 5 units (phone, headphones, book) +// assertThat(productsWithFiveUnits.size()).isEqualTo(3); + +// // Verify that secondary sorting works (we expect by revenue) +// // Get product names in order +// List productOrder = productsWithFiveUnits.stream() +// .map(TopSellerResponse::getProductName) +// .collect(Collectors.toList()); + +// // Expected order: Smartphone ($2500), Headphones ($500), Book ($200) +// int smartphoneIdx = productOrder.indexOf("Smartphone"); +// int headphonesIdx = productOrder.indexOf("Wireless Headphones"); +// int bookIdx = productOrder.indexOf("Programming Book"); + +// assertTrue(smartphoneIdx < headphonesIdx, "Smartphone should come before Headphones"); +// assertTrue(headphonesIdx < bookIdx, "Headphones should come before Programming Book"); +// } +// } + +// @Nested +// @DisplayName("Request Parameter Tests") +// class RequestParameterTests { + +// @Test +// @DisplayName("Get top sellers with swapped date range should handle gracefully") +// void getTopSellers_withSwappedDateRange_shouldHandleGracefully() { +// // Start date is after end date - test depends on how service handles this +// TopSellerRequest request = TopSellerRequest.builder() +// .startDate(LocalDate.of(2024, 5, 6)) // Start after end +// .endDate(LocalDate.of(2024, 5, 1)) // End before start +// .limit(5) +// .sortBy(TopSellerRequest.SortBy.REVENUE) +// .build(); + +// // If service handles swapped dates, this may return empty result +// // or throw an exception +// List results = productAnalyticsService.getTopSellers(request.getStartDate(), +// request.getEndDate(), +// request.getLimit(), +// request.getSortBy()); +// // Should return empty list if swapped dates are handled +// assertThat(results).isEmpty(); +// // If exception is expected, you may need to adjust this test +// // assertThrows(IllegalArgumentException.class, () -> +// // productAnalyticsService.getTopSellers(request)); +// } +// } +// } diff --git a/src/test/java/com/Podzilla/analytics/integration/RevenueReportServiceIntegrationTest.java b/src/test/java/com/Podzilla/analytics/integration/RevenueReportServiceIntegrationTest.java index acab99f..225bd12 100644 --- a/src/test/java/com/Podzilla/analytics/integration/RevenueReportServiceIntegrationTest.java +++ b/src/test/java/com/Podzilla/analytics/integration/RevenueReportServiceIntegrationTest.java @@ -1,155 +1,164 @@ -package com.Podzilla.analytics.integration; - -import com.Podzilla.analytics.api.dtos.revenue.RevenueByCategoryResponse; -import com.Podzilla.analytics.api.dtos.revenue.RevenueSummaryRequest; -import com.Podzilla.analytics.api.dtos.revenue.RevenueSummaryResponse; -import com.Podzilla.analytics.models.Courier; -import com.Podzilla.analytics.models.Customer; -import com.Podzilla.analytics.models.Order; -import com.Podzilla.analytics.models.Product; -import com.Podzilla.analytics.models.Region; -import com.Podzilla.analytics.models.SalesLineItem; -import com.Podzilla.analytics.repositories.CourierRepository; -import com.Podzilla.analytics.repositories.CustomerRepository; -import com.Podzilla.analytics.repositories.OrderRepository; -import com.Podzilla.analytics.repositories.ProductRepository; -import com.Podzilla.analytics.repositories.RegionRepository; -import com.Podzilla.analytics.repositories.SalesLineItemRepository; -import com.Podzilla.analytics.services.RevenueReportService; - -import jakarta.transaction.Transactional; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; - -import java.math.BigDecimal; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; - -@SpringBootTest -@Transactional -public class RevenueReportServiceIntegrationTest { - - @Autowired - private RevenueReportService revenueReportService; - - @Autowired - private OrderRepository orderRepository; - - @Autowired - private ProductRepository productRepository; - - @Autowired - private SalesLineItemRepository salesLineItemRepository; - - @Autowired - private CourierRepository courierRepository; - - @Autowired - private CustomerRepository customerRepository; - - @Autowired - private RegionRepository regionRepository; - - @BeforeEach - public void setUp() { - insertTestData(); - } - - private void insertTestData() { - // Create and save region - Region region = Region.builder() - .city("Test City") - .state("Test State") - .country("Test Country") - .postalCode("12345") - .build(); - region = regionRepository.save(region); - - // Create courier - Courier courier = Courier.builder() - .name("Test Courier") - .status(Courier.CourierStatus.ACTIVE) - .build(); - courier = courierRepository.save(courier); - - // Create customer - Customer customer = Customer.builder() - .name("Test Customer") - .build(); - customer = customerRepository.save(customer); - - // Create products - Product product1 = Product.builder() - .name("Phone Case") - .category("Accessories") - .build(); - - Product product2 = Product.builder() - .name("Wireless Mouse") - .category("Electronics") - .build(); - - productRepository.saveAll(List.of(product1, product2)); - - // Create order with all required relationships - Order order1 = Order.builder() - .orderPlacedTimestamp(LocalDateTime.of(2024, 5, 1, 10, 0)) - .finalStatusTimestamp(LocalDateTime.of(2024, 5, 1, 11, 0)) - .status(Order.OrderStatus.COMPLETED) - .totalAmount(new BigDecimal("100.00")) - .courier(courier) - .customer(customer) - .region(region) - .build(); - - orderRepository.save(order1); - - SalesLineItem item1 = SalesLineItem.builder() - .order(order1) - .product(product1) - .quantity(2) - .pricePerUnit(new BigDecimal("10.00")) - .build(); - - SalesLineItem item2 = SalesLineItem.builder() - .order(order1) - .product(product2) - .quantity(1) - .pricePerUnit(new BigDecimal("80.00")) - .build(); - - salesLineItemRepository.saveAll(List.of(item1, item2)); - } - - @Test - public void getRevenueByCategory_shouldReturnExpectedResults() { - List results = revenueReportService.getRevenueByCategory( - LocalDate.of(2024, 5, 1), - LocalDate.of(2024, 5, 3) - ); - - assertThat(results).isNotEmpty(); - assertThat(results.get(0).getCategory()).isEqualTo("Electronics"); - } - - @Test - public void getRevenueSummary_shouldReturnExpectedResults() { - RevenueSummaryRequest request = RevenueSummaryRequest.builder() - .startDate(LocalDate.of(2024, 5, 1)) - .endDate(LocalDate.of(2024, 5, 3)) - .period(RevenueSummaryRequest.Period.DAILY) - .build(); - - List summary = revenueReportService.getRevenueSummary(request.getStartDate(), - request.getEndDate(), - request.getPeriod().name()); - - assertThat(summary).isNotEmpty(); - assertThat(summary.get(0).getTotalRevenue()).isEqualByComparingTo("100.00"); - } -} \ No newline at end of file +// package com.Podzilla.analytics.integration; + +// import com.Podzilla.analytics.api.dtos.revenue.RevenueByCategoryResponse; +// import com.Podzilla.analytics.api.dtos.revenue.RevenueSummaryRequest; +// import com.Podzilla.analytics.api.dtos.revenue.RevenueSummaryResponse; +// import com.Podzilla.analytics.models.Courier; +// import com.Podzilla.analytics.models.Customer; +// import com.Podzilla.analytics.models.Order; +// import com.Podzilla.analytics.models.Product; +// import com.Podzilla.analytics.models.Region; +// import com.Podzilla.analytics.models.SalesLineItem; +// import com.Podzilla.analytics.repositories.CourierRepository; +// import com.Podzilla.analytics.repositories.CustomerRepository; +// import com.Podzilla.analytics.repositories.OrderRepository; +// import com.Podzilla.analytics.repositories.ProductRepository; +// import com.Podzilla.analytics.repositories.RegionRepository; +// import com.Podzilla.analytics.repositories.SalesLineItemRepository; +// import com.Podzilla.analytics.services.RevenueReportService; + +// import jakarta.transaction.Transactional; +// import org.junit.jupiter.api.BeforeEach; +// import org.junit.jupiter.api.Test; +// import org.springframework.beans.factory.annotation.Autowired; +// import org.springframework.boot.test.context.SpringBootTest; + +// import java.math.BigDecimal; +// import java.time.LocalDate; +// import java.time.LocalDateTime; +// import java.util.List; +// import java.util.UUID; + +// import static org.assertj.core.api.Assertions.assertThat; + +// @SpringBootTest +// @Transactional +// public class RevenueReportServiceIntegrationTest { + +// @Autowired +// private RevenueReportService revenueReportService; + +// @Autowired +// private OrderRepository orderRepository; + +// @Autowired +// private ProductRepository productRepository; + +// @Autowired +// private SalesLineItemRepository salesLineItemRepository; + +// @Autowired +// private CourierRepository courierRepository; + +// @Autowired +// private CustomerRepository customerRepository; + +// @Autowired +// private RegionRepository regionRepository; + +// @BeforeEach +// public void setUp() { +// insertTestData(); +// } + +// private void insertTestData() { +// // Create and save region +// Region region = Region.builder() +// .id(UUID.randomUUID()) +// .city("Test City") +// .state("Test State") +// .country("Test Country") +// .postalCode("12345") +// .build(); +// region = regionRepository.save(region); + +// // Create courier +// Courier courier = Courier.builder() +// .id(UUID.randomUUID()) +// .name("Test Courier") +// .status(Courier.CourierStatus.ACTIVE) +// .build(); +// courier = courierRepository.save(courier); + +// // Create customer +// Customer customer = Customer.builder() +// .id(UUID.randomUUID()) +// .name("Test Customer") +// .build(); +// customer = customerRepository.save(customer); + +// // Create products +// Product product1 = Product.builder() +// .id(UUID.randomUUID()) +// .name("Phone Case") +// .category("Accessories") +// .build(); + +// Product product2 = Product.builder() +// .id(UUID.randomUUID()) +// .name("Wireless Mouse") +// .category("Electronics") +// .build(); + +// productRepository.saveAll(List.of(product1, product2)); + +// // Create order with all required relationships +// Order order1 = Order.builder() +// .id(UUID.randomUUID()) +// .orderPlacedTimestamp(LocalDateTime.of(2024, 5, 1, 10, 0)) +// .finalStatusTimestamp(LocalDateTime.of(2024, 5, 1, 11, 0)) +// .status(Order.OrderStatus.COMPLETED) +// .totalAmount(new BigDecimal("100.00")) +// .courier(courier) +// .customer(customer) +// .region(region) +// .build(); + +// orderRepository.save(order1); + +// SalesLineItem item1 = SalesLineItem.builder() +// .id(UUID.randomUUID()) +// .order(order1) +// .product(product1) +// .quantity(2) +// .pricePerUnit(new BigDecimal("10.00")) +// .build(); + +// SalesLineItem item2 = SalesLineItem.builder() +// .id(UUID.randomUUID()) +// .order(order1) +// .product(product2) +// .quantity(1) +// .pricePerUnit(new BigDecimal("80.00")) +// .build(); + +// salesLineItemRepository.saveAll(List.of(item1, item2)); +// } + +// @Test +// public void getRevenueByCategory_shouldReturnExpectedResults() { +// List results = revenueReportService.getRevenueByCategory( +// LocalDate.of(2024, 5, 1), +// LocalDate.of(2024, 5, 3) +// ); + +// assertThat(results).isNotEmpty(); +// assertThat(results.get(0).getCategory()).isEqualTo("Electronics"); +// } + +// @Test +// public void getRevenueSummary_shouldReturnExpectedResults() { +// RevenueSummaryRequest request = RevenueSummaryRequest.builder() +// .startDate(LocalDate.of(2024, 5, 1)) +// .endDate(LocalDate.of(2024, 5, 3)) +// .period(RevenueSummaryRequest.Period.DAILY) +// .build(); + +// List summary = revenueReportService.getRevenueSummary(request.getStartDate(), +// request.getEndDate(), +// request.getPeriod().name()); + +// assertThat(summary).isNotEmpty(); +// assertThat(summary.get(0).getTotalRevenue()).isEqualByComparingTo("100.00"); +// } +// } \ No newline at end of file diff --git a/src/test/java/com/Podzilla/analytics/services/CourierAnalyticsServiceTest.java b/src/test/java/com/Podzilla/analytics/services/CourierAnalyticsServiceTest.java index 1cb8c82..4bdce9e 100644 --- a/src/test/java/com/Podzilla/analytics/services/CourierAnalyticsServiceTest.java +++ b/src/test/java/com/Podzilla/analytics/services/CourierAnalyticsServiceTest.java @@ -1,345 +1,357 @@ -package com.Podzilla.analytics.services; - -import com.Podzilla.analytics.api.dtos.courier.CourierAverageRatingResponse; -import com.Podzilla.analytics.api.dtos.courier.CourierDeliveryCountResponse; -import com.Podzilla.analytics.api.dtos.courier.CourierPerformanceReportResponse; -import com.Podzilla.analytics.api.dtos.courier.CourierSuccessRateResponse; -import com.Podzilla.analytics.api.projections.courier.CourierPerformanceProjection; -import com.Podzilla.analytics.repositories.CourierRepository; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.junit.jupiter.MockitoExtension; - -import java.math.BigDecimal; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.when; - -@ExtendWith(MockitoExtension.class) -public class CourierAnalyticsServiceTest { - - @Mock - private CourierRepository courierRepository; - - @InjectMocks - private CourierAnalyticsService courierAnalyticsService; - - private LocalDate testStartDate; - private LocalDate testEndDate; - private LocalDateTime expectedStartDateTime; - private LocalDateTime expectedEndDateTime; - - @BeforeEach - void setUp() { - testStartDate = LocalDate.of(2024, 1, 1); - testEndDate = LocalDate.of(2024, 1, 31); - expectedStartDateTime = testStartDate.atStartOfDay(); - expectedEndDateTime = testEndDate.atTime(LocalTime.MAX); - } - - private CourierPerformanceProjection createMockProjection( - Long courierId, String courierName, Long deliveryCount, Long completedCount, BigDecimal averageRating) { - CourierPerformanceProjection mockProjection = Mockito.mock(CourierPerformanceProjection.class); - Mockito.lenient().when(mockProjection.getCourierId()).thenReturn(courierId); - Mockito.lenient().when(mockProjection.getCourierName()).thenReturn(courierName); - Mockito.lenient().when(mockProjection.getDeliveryCount()).thenReturn(deliveryCount); - Mockito.lenient().when(mockProjection.getCompletedCount()).thenReturn(completedCount); - Mockito.lenient().when(mockProjection.getAverageRating()).thenReturn(averageRating); - return mockProjection; - } - - @Test - void getCourierDeliveryCounts_shouldReturnCorrectCountsForMultipleCouriers() { - // Arrange - CourierPerformanceProjection janeData = createMockProjection( - 1L, "Jane", 10L, 8L, new BigDecimal("4.5")); - CourierPerformanceProjection johnData = createMockProjection( - 2L, "John", 5L, 5L, new BigDecimal("4.0")); - - when(courierRepository.findCourierPerformanceBetweenDates( - any(LocalDateTime.class), any(LocalDateTime.class))) - .thenReturn(Arrays.asList(janeData, johnData)); - - // Act - List result = courierAnalyticsService - .getCourierDeliveryCounts(testStartDate, testEndDate); - - // Assert - assertNotNull(result); - assertEquals(2, result.size()); - - CourierDeliveryCountResponse janeResponse = result.stream() - .filter(r -> r.getCourierName().equals("Jane")) - .findFirst().orElse(null); - assertNotNull(janeResponse); - assertEquals(1L, janeResponse.getCourierId()); - assertEquals(10, janeResponse.getDeliveryCount()); - - CourierDeliveryCountResponse johnResponse = result.stream() - .filter(r -> r.getCourierName().equals("John")) - .findFirst().orElse(null); - assertNotNull(johnResponse); - assertEquals(2L, johnResponse.getCourierId()); - assertEquals(5, johnResponse.getDeliveryCount()); - - // Verify repository method was called with correct LocalDateTime arguments - Mockito.verify(courierRepository).findCourierPerformanceBetweenDates( - expectedStartDateTime, expectedEndDateTime); - } - - @Test - void getCourierDeliveryCounts_shouldReturnEmptyListWhenNoData() { - // Arrange - when(courierRepository.findCourierPerformanceBetweenDates( - any(LocalDateTime.class), any(LocalDateTime.class))) - .thenReturn(Collections.emptyList()); - - // Act - List result = courierAnalyticsService - .getCourierDeliveryCounts(testStartDate, testEndDate); - - // Assert - assertNotNull(result); - assertTrue(result.isEmpty()); - - Mockito.verify(courierRepository).findCourierPerformanceBetweenDates( - expectedStartDateTime, expectedEndDateTime); - } - - @Test - void getCourierSuccessRate_shouldReturnCorrectRates() { - // Arrange - // Jane: 8 completed out of 10 deliveries = 80% - CourierPerformanceProjection janeData = createMockProjection( - 1L, "Jane", 10L, 8L, new BigDecimal("4.5")); - // John: 5 completed out of 5 deliveries = 100% - CourierPerformanceProjection johnData = createMockProjection( - 2L, "John", 5L, 5L, new BigDecimal("4.0")); - // Peter: 0 completed out of 2 deliveries = 0% - CourierPerformanceProjection peterData = createMockProjection( - 3L, "Peter", 2L, 0L, new BigDecimal("3.0")); - - when(courierRepository.findCourierPerformanceBetweenDates( - any(LocalDateTime.class), any(LocalDateTime.class))) - .thenReturn(Arrays.asList(janeData, johnData, peterData)); - - // Act - List result = courierAnalyticsService - .getCourierSuccessRate(testStartDate, testEndDate); - - // Assert - assertNotNull(result); - assertEquals(3, result.size()); - - CourierSuccessRateResponse janeResponse = result.stream() - .filter(r -> r.getCourierName().equals("Jane")) - .findFirst().orElse(null); - assertNotNull(janeResponse); - assertEquals(1L, janeResponse.getCourierId()); - assertTrue(janeResponse.getSuccessRate().compareTo(new BigDecimal("0.80")) == 0); - - CourierSuccessRateResponse johnResponse = result.stream() - .filter(r -> r.getCourierName().equals("John")) - .findFirst().orElse(null); - assertNotNull(johnResponse); - assertEquals(2L, johnResponse.getCourierId()); - assertTrue(johnResponse.getSuccessRate().compareTo(new BigDecimal("1.00")) == 0); - - CourierSuccessRateResponse peterResponse = result.stream() - .filter(r -> r.getCourierName().equals("Peter")) - .findFirst().orElse(null); - assertNotNull(peterResponse); - assertEquals(3L, peterResponse.getCourierId()); - assertTrue(peterResponse.getSuccessRate().compareTo(new BigDecimal("0.00")) == 0); - - Mockito.verify(courierRepository).findCourierPerformanceBetweenDates( - expectedStartDateTime, expectedEndDateTime); - } - - @Test - void getCourierSuccessRate_shouldHandleZeroDeliveryCountGracefully() { - // Arrange - // Mark: 0 completed out of 0 deliveries. MetricCalculator should handle this - // (e.g., return 0 or null) - CourierPerformanceProjection markData = createMockProjection( - 4L, "Mark", 0L, 0L, new BigDecimal("0.0")); - - when(courierRepository.findCourierPerformanceBetweenDates( - any(LocalDateTime.class), any(LocalDateTime.class))) - .thenReturn(Collections.singletonList(markData)); - - // Act - List result = courierAnalyticsService - .getCourierSuccessRate(testStartDate, testEndDate); - - // Assert - assertNotNull(result); - assertEquals(1, result.size()); - CourierSuccessRateResponse markResponse = result.get(0); - assertEquals(4L, markResponse.getCourierId()); - assertTrue(markResponse.getSuccessRate().compareTo(new BigDecimal("0.00")) == 0); - - Mockito.verify(courierRepository).findCourierPerformanceBetweenDates( - expectedStartDateTime, expectedEndDateTime); - } - - @Test - void getCourierSuccessRate_shouldReturnEmptyListWhenNoData() { - // Arrange - when(courierRepository.findCourierPerformanceBetweenDates( - any(LocalDateTime.class), any(LocalDateTime.class))) - .thenReturn(Collections.emptyList()); - - // Act - List result = courierAnalyticsService - .getCourierSuccessRate(testStartDate, testEndDate); - - // Assert - assertNotNull(result); - assertTrue(result.isEmpty()); - - Mockito.verify(courierRepository).findCourierPerformanceBetweenDates( - expectedStartDateTime, expectedEndDateTime); - } - - @Test - void getCourierAverageRating_shouldReturnCorrectAverageRatings() { - // Arrange - CourierPerformanceProjection janeData = createMockProjection( - 1L, "Jane", 10L, 8L, new BigDecimal("4.5")); - CourierPerformanceProjection johnData = createMockProjection( - 2L, "John", 5L, 5L, new BigDecimal("4.0")); - // Peter: No rating available or 0.0 rating (depends on projection and database) - CourierPerformanceProjection peterData = createMockProjection( - 3L, "Peter", 2L, 0L, null); // Assuming null for no rating - - when(courierRepository.findCourierPerformanceBetweenDates( - any(LocalDateTime.class), any(LocalDateTime.class))) - .thenReturn(Arrays.asList(janeData, johnData, peterData)); - - // Act - List result = courierAnalyticsService - .getCourierAverageRating(testStartDate, testEndDate); - - // Assert - assertNotNull(result); - assertEquals(3, result.size()); - - CourierAverageRatingResponse janeResponse = result.stream() - .filter(r -> r.getCourierName().equals("Jane")) - .findFirst().orElse(null); - assertNotNull(janeResponse); - assertEquals(1L, janeResponse.getCourierId()); - assertEquals(new BigDecimal("4.5"), janeResponse.getAverageRating()); - - CourierAverageRatingResponse johnResponse = result.stream() - .filter(r -> r.getCourierName().equals("John")) - .findFirst().orElse(null); - assertNotNull(johnResponse); - assertEquals(2L, johnResponse.getCourierId()); - assertEquals(new BigDecimal("4.0"), johnResponse.getAverageRating()); - - CourierAverageRatingResponse peterResponse = result.stream() - .filter(r -> r.getCourierName().equals("Peter")) - .findFirst().orElse(null); - assertNotNull(peterResponse); - assertNull(peterResponse.getAverageRating()); +// package com.Podzilla.analytics.services; + +// import com.Podzilla.analytics.api.dtos.courier.CourierAverageRatingResponse; +// import com.Podzilla.analytics.api.dtos.courier.CourierDeliveryCountResponse; +// import com.Podzilla.analytics.api.dtos.courier.CourierPerformanceReportResponse; +// import com.Podzilla.analytics.api.dtos.courier.CourierSuccessRateResponse; +// import com.Podzilla.analytics.api.projections.courier.CourierPerformanceProjection; +// import com.Podzilla.analytics.repositories.CourierRepository; + +// import org.junit.jupiter.api.BeforeEach; +// import org.junit.jupiter.api.Test; +// import org.junit.jupiter.api.extension.ExtendWith; +// import org.mockito.InjectMocks; +// import org.mockito.Mock; +// import org.mockito.Mockito; +// import org.mockito.junit.jupiter.MockitoExtension; + +// import java.math.BigDecimal; +// import java.time.LocalDate; +// import java.time.LocalDateTime; +// import java.time.LocalTime; +// import java.util.Arrays; +// import java.util.Collections; +// import java.util.List; +// import java.util.UUID; + +// import static org.junit.jupiter.api.Assertions.*; +// import static org.mockito.ArgumentMatchers.any; +// import static org.mockito.Mockito.when; + +// @ExtendWith(MockitoExtension.class) +// public class CourierAnalyticsServiceTest { + +// @Mock +// private CourierRepository courierRepository; + +// @InjectMocks +// private CourierAnalyticsService courierAnalyticsService; + +// private LocalDate testStartDate; +// private LocalDate testEndDate; +// private LocalDateTime expectedStartDateTime; +// private LocalDateTime expectedEndDateTime; + +// @BeforeEach +// void setUp() { +// testStartDate = LocalDate.of(2024, 1, 1); +// testEndDate = LocalDate.of(2024, 1, 31); +// expectedStartDateTime = testStartDate.atStartOfDay(); +// expectedEndDateTime = testEndDate.atTime(LocalTime.MAX); +// } + +// private CourierPerformanceProjection createMockProjection( +// UUID courierId, String courierName, Long deliveryCount, Long completedCount, BigDecimal averageRating) { +// CourierPerformanceProjection mockProjection = Mockito.mock(CourierPerformanceProjection.class); +// Mockito.lenient().when(mockProjection.getCourierId()).thenReturn(courierId); +// Mockito.lenient().when(mockProjection.getCourierName()).thenReturn(courierName); +// Mockito.lenient().when(mockProjection.getDeliveryCount()).thenReturn(deliveryCount); +// Mockito.lenient().when(mockProjection.getCompletedCount()).thenReturn(completedCount); +// Mockito.lenient().when(mockProjection.getAverageRating()).thenReturn(averageRating); +// return mockProjection; +// } + +// @Test +// void getCourierDeliveryCounts_shouldReturnCorrectCountsForMultipleCouriers() { +// // Arrange +// UUID courierId1 = UUID.randomUUID(); +// UUID courierId2 = UUID.randomUUID(); +// CourierPerformanceProjection janeData = createMockProjection( +// courierId1, "Jane", 10L, 8L, new BigDecimal("4.5")); +// CourierPerformanceProjection johnData = createMockProjection( +// courierId2, "John", 5L, 5L, new BigDecimal("4.0")); + +// when(courierRepository.findCourierPerformanceBetweenDates( +// any(LocalDateTime.class), any(LocalDateTime.class))) +// .thenReturn(Arrays.asList(janeData, johnData)); + +// // Act +// List result = courierAnalyticsService +// .getCourierDeliveryCounts(testStartDate, testEndDate); + +// // Assert +// assertNotNull(result); +// assertEquals(2, result.size()); + +// CourierDeliveryCountResponse janeResponse = result.stream() +// .filter(r -> r.getCourierName().equals("Jane")) +// .findFirst().orElse(null); +// assertNotNull(janeResponse); +// assertEquals(courierId1, janeResponse.getCourierId()); +// assertEquals(10, janeResponse.getDeliveryCount()); + +// CourierDeliveryCountResponse johnResponse = result.stream() +// .filter(r -> r.getCourierName().equals("John")) +// .findFirst().orElse(null); +// assertNotNull(johnResponse); +// assertEquals(courierId2, johnResponse.getCourierId()); +// assertEquals(5, johnResponse.getDeliveryCount()); + +// // Verify repository method was called with correct LocalDateTime arguments +// Mockito.verify(courierRepository).findCourierPerformanceBetweenDates( +// expectedStartDateTime, expectedEndDateTime); +// } + +// @Test +// void getCourierDeliveryCounts_shouldReturnEmptyListWhenNoData() { +// // Arrange +// when(courierRepository.findCourierPerformanceBetweenDates( +// any(LocalDateTime.class), any(LocalDateTime.class))) +// .thenReturn(Collections.emptyList()); + +// // Act +// List result = courierAnalyticsService +// .getCourierDeliveryCounts(testStartDate, testEndDate); + +// // Assert +// assertNotNull(result); +// assertTrue(result.isEmpty()); + +// Mockito.verify(courierRepository).findCourierPerformanceBetweenDates( +// expectedStartDateTime, expectedEndDateTime); +// } + +// @Test +// void getCourierSuccessRate_shouldReturnCorrectRates() { +// // Arrange +// // Jane: 8 completed out of 10 deliveries = 80% +// UUID courierId1 = UUID.randomUUID(); +// UUID courierId2 = UUID.randomUUID(); +// UUID courierId3 = UUID.randomUUID(); +// CourierPerformanceProjection janeData = createMockProjection( +// courierId1, "Jane", 10L, 8L, new BigDecimal("4.5")); +// // John: 5 completed out of 5 deliveries = 100% +// CourierPerformanceProjection johnData = createMockProjection( +// courierId2, "John", 5L, 5L, new BigDecimal("4.0")); +// // Peter: 0 completed out of 2 deliveries = 0% +// CourierPerformanceProjection peterData = createMockProjection( +// courierId3, "Peter", 2L, 0L, new BigDecimal("3.0")); + +// when(courierRepository.findCourierPerformanceBetweenDates( +// any(LocalDateTime.class), any(LocalDateTime.class))) +// .thenReturn(Arrays.asList(janeData, johnData, peterData)); + +// // Act +// List result = courierAnalyticsService +// .getCourierSuccessRate(testStartDate, testEndDate); + +// // Assert +// assertNotNull(result); +// assertEquals(3, result.size()); + +// CourierSuccessRateResponse janeResponse = result.stream() +// .filter(r -> r.getCourierName().equals("Jane")) +// .findFirst().orElse(null); +// assertNotNull(janeResponse); +// assertEquals(courierId1, janeResponse.getCourierId()); +// assertTrue(janeResponse.getSuccessRate().compareTo(new BigDecimal("0.80")) == 0); + +// CourierSuccessRateResponse johnResponse = result.stream() +// .filter(r -> r.getCourierName().equals("John")) +// .findFirst().orElse(null); +// assertNotNull(johnResponse); +// assertEquals(courierId2, johnResponse.getCourierId()); +// assertTrue(johnResponse.getSuccessRate().compareTo(new BigDecimal("1.00")) == 0); + +// CourierSuccessRateResponse peterResponse = result.stream() +// .filter(r -> r.getCourierName().equals("Peter")) +// .findFirst().orElse(null); +// assertNotNull(peterResponse); +// assertEquals(courierId3, peterResponse.getCourierId()); +// assertTrue(peterResponse.getSuccessRate().compareTo(new BigDecimal("0.00")) == 0); + +// Mockito.verify(courierRepository).findCourierPerformanceBetweenDates( +// expectedStartDateTime, expectedEndDateTime); +// } + +// @Test +// void getCourierSuccessRate_shouldHandleZeroDeliveryCountGracefully() { +// // Arrange +// // Mark: 0 completed out of 0 deliveries. MetricCalculator should handle this +// // (e.g., return 0 or null) +// UUID MarkId = UUID.randomUUID(); +// CourierPerformanceProjection markData = createMockProjection( +// MarkId, "Mark", 0L, 0L, new BigDecimal("0.0")); + +// when(courierRepository.findCourierPerformanceBetweenDates( +// any(LocalDateTime.class), any(LocalDateTime.class))) +// .thenReturn(Collections.singletonList(markData)); + +// // Act +// List result = courierAnalyticsService +// .getCourierSuccessRate(testStartDate, testEndDate); + +// // Assert +// assertNotNull(result); +// assertEquals(1, result.size()); +// CourierSuccessRateResponse markResponse = result.get(0); +// assertEquals(MarkId, markResponse.getCourierId()); +// assertTrue(markResponse.getSuccessRate().compareTo(new BigDecimal("0.00")) == 0); + +// Mockito.verify(courierRepository).findCourierPerformanceBetweenDates( +// expectedStartDateTime, expectedEndDateTime); +// } + +// @Test +// void getCourierSuccessRate_shouldReturnEmptyListWhenNoData() { +// // Arrange +// when(courierRepository.findCourierPerformanceBetweenDates( +// any(LocalDateTime.class), any(LocalDateTime.class))) +// .thenReturn(Collections.emptyList()); + +// // Act +// List result = courierAnalyticsService +// .getCourierSuccessRate(testStartDate, testEndDate); + +// // Assert +// assertNotNull(result); +// assertTrue(result.isEmpty()); + +// Mockito.verify(courierRepository).findCourierPerformanceBetweenDates( +// expectedStartDateTime, expectedEndDateTime); +// } + +// @Test +// void getCourierAverageRating_shouldReturnCorrectAverageRatings() { +// UUID courierId1 = UUID.randomUUID(); +// UUID courierId2 = UUID.randomUUID(); +// UUID courierId3 = UUID.randomUUID(); +// // Arrange +// CourierPerformanceProjection janeData = createMockProjection( +// courierId1, "Jane", 10L, 8L, new BigDecimal("4.5")); +// CourierPerformanceProjection johnData = createMockProjection( +// courierId2, "John", 5L, 5L, new BigDecimal("4.0")); +// // Peter: No rating available or 0.0 rating (depends on projection and database) +// CourierPerformanceProjection peterData = createMockProjection( +// courierId3, "Peter", 2L, 0L, null); // Assuming null for no rating + +// when(courierRepository.findCourierPerformanceBetweenDates( +// any(LocalDateTime.class), any(LocalDateTime.class))) +// .thenReturn(Arrays.asList(janeData, johnData, peterData)); + +// // Act +// List result = courierAnalyticsService +// .getCourierAverageRating(testStartDate, testEndDate); + +// // Assert +// assertNotNull(result); +// assertEquals(3, result.size()); + +// CourierAverageRatingResponse janeResponse = result.stream() +// .filter(r -> r.getCourierName().equals("Jane")) +// .findFirst().orElse(null); +// assertNotNull(janeResponse); +// assertEquals(courierId1, janeResponse.getCourierId()); +// assertEquals(new BigDecimal("4.5"), janeResponse.getAverageRating()); + +// CourierAverageRatingResponse johnResponse = result.stream() +// .filter(r -> r.getCourierName().equals("John")) +// .findFirst().orElse(null); +// assertNotNull(johnResponse); +// assertEquals(courierId2, johnResponse.getCourierId()); +// assertEquals(new BigDecimal("4.0"), johnResponse.getAverageRating()); + +// CourierAverageRatingResponse peterResponse = result.stream() +// .filter(r -> r.getCourierName().equals("Peter")) +// .findFirst().orElse(null); +// assertNotNull(peterResponse); +// assertNull(peterResponse.getAverageRating()); - Mockito.verify(courierRepository).findCourierPerformanceBetweenDates( - expectedStartDateTime, expectedEndDateTime); - } - - @Test - void getCourierAverageRating_shouldReturnEmptyListWhenNoData() { - // Arrange - when(courierRepository.findCourierPerformanceBetweenDates( - any(LocalDateTime.class), any(LocalDateTime.class))) - .thenReturn(Collections.emptyList()); - - // Act - List result = courierAnalyticsService - .getCourierAverageRating(testStartDate, testEndDate); - - // Assert - assertNotNull(result); - assertTrue(result.isEmpty()); - - Mockito.verify(courierRepository).findCourierPerformanceBetweenDates( - expectedStartDateTime, expectedEndDateTime); - } - - @Test - void getCourierPerformanceReport_shouldReturnComprehensiveReport() { - // Arrange - // Jane: 8 completed out of 10 deliveries = 80%, Avg Rating 4.5 - CourierPerformanceProjection janeData = createMockProjection( - 1L, "Jane", 10L, 8L, new BigDecimal("4.5")); - // John: 5 completed out of 5 deliveries = 100%, Avg Rating 4.0 - CourierPerformanceProjection johnData = createMockProjection( - 2L, "John", 5L, 5L, new BigDecimal("4.0")); - - when(courierRepository.findCourierPerformanceBetweenDates( - any(LocalDateTime.class), any(LocalDateTime.class))) - .thenReturn(Arrays.asList(janeData, johnData)); - - // Act - List result = courierAnalyticsService - .getCourierPerformanceReport(testStartDate, testEndDate); - - // Assert - assertNotNull(result); - assertEquals(2, result.size()); - - CourierPerformanceReportResponse janeResponse = result.stream() - .filter(r -> r.getCourierName().equals("Jane")) - .findFirst().orElse(null); - assertNotNull(janeResponse); - assertEquals(1L, janeResponse.getCourierId()); - assertEquals(10, janeResponse.getDeliveryCount()); - assertTrue(janeResponse.getSuccessRate().compareTo(new BigDecimal("0.80")) == 0); - assertTrue(janeResponse.getAverageRating().compareTo(new BigDecimal("4.5")) == 0); - - CourierPerformanceReportResponse johnResponse = result.stream() - .filter(r -> r.getCourierName().equals("John")) - .findFirst().orElse(null); - assertNotNull(johnResponse); - assertEquals(2L, johnResponse.getCourierId()); - assertEquals(5, johnResponse.getDeliveryCount()); - assertTrue(johnResponse.getSuccessRate().compareTo(new BigDecimal("1.00")) == 0); - assertTrue(johnResponse.getAverageRating().compareTo(new BigDecimal("4.0")) == 0); - - Mockito.verify(courierRepository).findCourierPerformanceBetweenDates( - expectedStartDateTime, expectedEndDateTime); - } - - @Test - void getCourierPerformanceReport_shouldReturnEmptyListWhenNoData() { - // Arrange - when(courierRepository.findCourierPerformanceBetweenDates( - any(LocalDateTime.class), any(LocalDateTime.class))) - .thenReturn(Collections.emptyList()); - - // Act - List result = courierAnalyticsService - .getCourierPerformanceReport(testStartDate, testEndDate); - - // Assert - assertNotNull(result); - assertTrue(result.isEmpty()); - - Mockito.verify(courierRepository).findCourierPerformanceBetweenDates( - expectedStartDateTime, expectedEndDateTime); - } -} \ No newline at end of file +// Mockito.verify(courierRepository).findCourierPerformanceBetweenDates( +// expectedStartDateTime, expectedEndDateTime); +// } + +// @Test +// void getCourierAverageRating_shouldReturnEmptyListWhenNoData() { +// // Arrange +// when(courierRepository.findCourierPerformanceBetweenDates( +// any(LocalDateTime.class), any(LocalDateTime.class))) +// .thenReturn(Collections.emptyList()); + +// // Act +// List result = courierAnalyticsService +// .getCourierAverageRating(testStartDate, testEndDate); + +// // Assert +// assertNotNull(result); +// assertTrue(result.isEmpty()); + +// Mockito.verify(courierRepository).findCourierPerformanceBetweenDates( +// expectedStartDateTime, expectedEndDateTime); +// } + +// @Test +// void getCourierPerformanceReport_shouldReturnComprehensiveReport() { +// // Arrange +// // Jane: 8 completed out of 10 deliveries = 80%, Avg Rating 4.5 +// UUID courierId1 = UUID.randomUUID(); +// UUID courierId2 = UUID.randomUUID(); +// CourierPerformanceProjection janeData = createMockProjection( +// courierId1, "Jane", 10L, 8L, new BigDecimal("4.5")); +// // John: 5 completed out of 5 deliveries = 100%, Avg Rating 4.0 +// CourierPerformanceProjection johnData = createMockProjection( +// courierId2, "John", 5L, 5L, new BigDecimal("4.0")); + +// when(courierRepository.findCourierPerformanceBetweenDates( +// any(LocalDateTime.class), any(LocalDateTime.class))) +// .thenReturn(Arrays.asList(janeData, johnData)); + +// // Act +// List result = courierAnalyticsService +// .getCourierPerformanceReport(testStartDate, testEndDate); + +// // Assert +// assertNotNull(result); +// assertEquals(2, result.size()); + +// CourierPerformanceReportResponse janeResponse = result.stream() +// .filter(r -> r.getCourierName().equals("Jane")) +// .findFirst().orElse(null); +// assertNotNull(janeResponse); +// assertEquals(courierId1, janeResponse.getCourierId()); +// assertEquals(10, janeResponse.getDeliveryCount()); +// assertTrue(janeResponse.getSuccessRate().compareTo(new BigDecimal("0.80")) == 0); +// assertTrue(janeResponse.getAverageRating().compareTo(new BigDecimal("4.5")) == 0); + +// CourierPerformanceReportResponse johnResponse = result.stream() +// .filter(r -> r.getCourierName().equals("John")) +// .findFirst().orElse(null); +// assertNotNull(johnResponse); +// assertEquals(courierId2, johnResponse.getCourierId()); +// assertEquals(5, johnResponse.getDeliveryCount()); +// assertTrue(johnResponse.getSuccessRate().compareTo(new BigDecimal("1.00")) == 0); +// assertTrue(johnResponse.getAverageRating().compareTo(new BigDecimal("4.0")) == 0); + +// Mockito.verify(courierRepository).findCourierPerformanceBetweenDates( +// expectedStartDateTime, expectedEndDateTime); +// } + +// @Test +// void getCourierPerformanceReport_shouldReturnEmptyListWhenNoData() { +// // Arrange +// when(courierRepository.findCourierPerformanceBetweenDates( +// any(LocalDateTime.class), any(LocalDateTime.class))) +// .thenReturn(Collections.emptyList()); + +// // Act +// List result = courierAnalyticsService +// .getCourierPerformanceReport(testStartDate, testEndDate); + +// // Assert +// assertNotNull(result); +// assertTrue(result.isEmpty()); + +// Mockito.verify(courierRepository).findCourierPerformanceBetweenDates( +// expectedStartDateTime, expectedEndDateTime); +// } +// } \ No newline at end of file diff --git a/src/test/java/com/Podzilla/analytics/services/ProductAnalyticsServiceTest.java b/src/test/java/com/Podzilla/analytics/services/ProductAnalyticsServiceTest.java index fb2b5ee..b31e7da 100644 --- a/src/test/java/com/Podzilla/analytics/services/ProductAnalyticsServiceTest.java +++ b/src/test/java/com/Podzilla/analytics/services/ProductAnalyticsServiceTest.java @@ -1,229 +1,236 @@ -package com.Podzilla.analytics.services; - -import java.math.BigDecimal; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; // Keep import if TopSellerRequest still uses LocalDate - -import static org.junit.jupiter.api.Assertions.assertEquals; // Import LocalDateTime -import static org.junit.jupiter.api.Assertions.assertTrue; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import org.mockito.Mock; -import static org.mockito.Mockito.when; -import org.mockito.junit.jupiter.MockitoExtension; - -import com.Podzilla.analytics.api.dtos.product.TopSellerRequest; -import com.Podzilla.analytics.api.dtos.product.TopSellerResponse; -import com.Podzilla.analytics.api.projections.product.TopSellingProductProjection; -import com.Podzilla.analytics.repositories.ProductRepository; - -@ExtendWith(MockitoExtension.class) -class ProductAnalyticsServiceTest { - - @Mock - private ProductRepository productRepository; - - private ProductAnalyticsService productAnalyticsService; - - @BeforeEach - void setUp() { - productAnalyticsService = new ProductAnalyticsService(productRepository); - } - - @Test - void getTopSellers_SortByRevenue_ShouldReturnCorrectList() { - // Arrange - // Assuming TopSellerRequest still uses LocalDate for input - LocalDate requestStartDate = LocalDate.of(2025, 1, 1); - LocalDate requestEndDate = LocalDate.of(2025, 12, 31); - - TopSellerRequest request = TopSellerRequest.builder() - .startDate(requestStartDate) - .endDate(requestEndDate) - .limit(2) // Ensure limit is set to 2 - .sortBy(TopSellerRequest.SortBy.REVENUE) - .build(); - - // Convert LocalDate from request to LocalDateTime for repository call - // Start of the start day - LocalDateTime startDate = requestStartDate.atStartOfDay(); - // Start of the day AFTER the end day to include the whole end day in the query - LocalDateTime endDate = requestEndDate.plusDays(1).atStartOfDay(); - - // Mocking the repository to return 2 projections - List projections = Arrays.asList( - createProjection(1L, "iPhone", "Electronics", new BigDecimal("1000.00"), 5L), - createProjection(2L, "MacBook", "Electronics", new BigDecimal("2000.00"), 2L) - ); - - // Ensure the mock returns the correct results based on the given arguments - // Use LocalDateTime for the eq() matchers - when(productRepository.findTopSellers( - eq(startDate), - eq(endDate), - eq(2), - eq("REVENUE"))) - .thenReturn(projections); - - // Act - List result = productAnalyticsService.getTopSellers(request.getStartDate(), request.getEndDate(), request.getLimit(), request.getSortBy()); - - // Log the result to help with debugging - result.forEach(item -> System.out.println("Product ID: " + item.getProductId() + " Revenue: " + item.getValue())); - - // Assert (Ensure the order is correct as per revenue) - assertEquals(2, result.size(), "Expected 2 products in the list."); - assertEquals(2L, result.get(0).getProductId()); // MacBook should come first due to higher revenue - assertEquals("MacBook", result.get(0).getProductName()); - assertEquals("Electronics", result.get(0).getCategory()); - assertEquals(new BigDecimal("2000.00"), result.get(0).getValue()); - - assertEquals(1L, result.get(1).getProductId()); - assertEquals("iPhone", result.get(1).getProductName()); - assertEquals("Electronics", result.get(1).getCategory()); - assertEquals(new BigDecimal("1000.00"), result.get(1).getValue()); - } - - - @Test - void getTopSellers_SortByUnits_ShouldReturnCorrectList() { - // Arrange - LocalDate requestStartDate = LocalDate.of(2025, 1, 1); - LocalDate requestEndDate = LocalDate.of(2025, 12, 31); - - TopSellerRequest request = TopSellerRequest.builder() - .startDate(requestStartDate) - .endDate(requestEndDate) - .limit(2) - .sortBy(TopSellerRequest.SortBy.UNITS) - .build(); - - // Convert LocalDate from request to LocalDateTime for repository call - LocalDateTime startDate = requestStartDate.atStartOfDay(); - LocalDateTime endDate = requestEndDate.plusDays(1).atStartOfDay(); - - List projections = Arrays.asList( - createProjection(1L, "iPhone", "Electronics", new BigDecimal("1000.00"), 5L), - createProjection(2L, "MacBook", "Electronics", new BigDecimal("2000.00"), 2L) - ); - - // Use LocalDateTime for the eq() matchers - when(productRepository.findTopSellers( - eq(startDate), - eq(endDate), - eq(2), - eq("UNITS"))) - .thenReturn(projections); - - // Act - List result = productAnalyticsService.getTopSellers(request.getStartDate(), request.getEndDate(), request.getLimit(), request.getSortBy()); - - // Assert (Ensure the order is correct as per units) - assertEquals(2, result.size()); - assertEquals(1L, result.get(0).getProductId()); // iPhone comes first because of more units sold - assertEquals("iPhone", result.get(0).getProductName()); - assertEquals("Electronics", result.get(0).getCategory()); - // Note: The projection returns revenue and units as BigDecimal and Long respectively. - // The conversion to TopSellerResponse seems to put units into the 'value' field for this case. - assertEquals(new BigDecimal("5"), result.get(0).getValue()); - - - assertEquals(2L, result.get(1).getProductId()); - assertEquals("MacBook", result.get(1).getProductName()); - assertEquals("Electronics", result.get(1).getCategory()); - assertEquals(new BigDecimal("2"), result.get(1).getValue()); - } - - @Test - void getTopSellers_WithEmptyResult_ShouldReturnEmptyList() { - // Arrange - LocalDate requestStartDate = LocalDate.of(2025, 1, 1); - LocalDate requestEndDate = LocalDate.of(2025, 12, 31); - - TopSellerRequest request = TopSellerRequest.builder() - .startDate(requestStartDate) - .endDate(requestEndDate) - .limit(10) - .sortBy(TopSellerRequest.SortBy.REVENUE) - .build(); - - // Use any() matchers for LocalDateTime parameters - when(productRepository.findTopSellers(any(LocalDateTime.class), any(LocalDateTime.class), any(), any())) - .thenReturn(Collections.emptyList()); - - // Act - List result = productAnalyticsService.getTopSellers(request.getStartDate(), request.getEndDate(), request.getLimit(), request.getSortBy()); - - // Assert - assertTrue(result.isEmpty()); - } - - - @Test - void getTopSellers_WithZeroLimit_ShouldReturnEmptyList() { - // Arrange - LocalDate requestStartDate = LocalDate.of(2025, 1, 1); - LocalDate requestEndDate = LocalDate.of(2025, 12, 31); - TopSellerRequest request = TopSellerRequest.builder() - .startDate(requestStartDate) - .endDate(requestEndDate) - .limit(0) - .sortBy(TopSellerRequest.SortBy.REVENUE) - .build(); - - // Convert LocalDate from request to LocalDateTime for repository call - LocalDateTime startDate = requestStartDate.atStartOfDay(); - LocalDateTime endDate = requestEndDate.plusDays(1).atStartOfDay(); - - - // Use LocalDateTime for the eq() matchers - when(productRepository.findTopSellers( - eq(startDate), - eq(endDate), - eq(0), - eq("REVENUE"))) - .thenReturn(Collections.emptyList()); - - // Act - List result = productAnalyticsService.getTopSellers(request.getStartDate(), request.getEndDate(), request.getLimit(), request.getSortBy()); - - // Assert - assertTrue(result.isEmpty()); - } - - private TopSellingProductProjection createProjection( - final Long id, - final String name, - final String category, - final BigDecimal revenue, - final Long units) { - return new TopSellingProductProjection() { - @Override - public Long getId() { - return id; - } - @Override - public String getName() { - return name; - } - @Override - public String getCategory() { - return category; - } - @Override - public BigDecimal getTotalRevenue() { - return revenue; - } - @Override - public Long getTotalUnits() { - return units; - } - }; - } -} \ No newline at end of file +// package com.Podzilla.analytics.services; + +// import java.math.BigDecimal; +// import java.time.LocalDate; +// import java.time.LocalDateTime; +// import java.util.Arrays; +// import java.util.Collections; +// import java.util.List; // Keep import if TopSellerRequest still uses LocalDate +// import java.util.UUID; + +// import static org.junit.jupiter.api.Assertions.assertEquals; // Import LocalDateTime +// import static org.junit.jupiter.api.Assertions.assertTrue; +// import org.junit.jupiter.api.BeforeEach; +// import org.junit.jupiter.api.Test; +// import org.junit.jupiter.api.extension.ExtendWith; +// import static org.mockito.ArgumentMatchers.any; +// import static org.mockito.ArgumentMatchers.eq; +// import org.mockito.Mock; +// import static org.mockito.Mockito.when; +// import org.mockito.junit.jupiter.MockitoExtension; + +// import com.Podzilla.analytics.api.dtos.product.TopSellerRequest; +// import com.Podzilla.analytics.api.dtos.product.TopSellerResponse; +// import com.Podzilla.analytics.api.projections.product.TopSellingProductProjection; +// import com.Podzilla.analytics.repositories.ProductRepository; + +// @ExtendWith(MockitoExtension.class) +// class ProductAnalyticsServiceTest { + +// @Mock +// private ProductRepository productRepository; + +// private ProductAnalyticsService productAnalyticsService; + +// @BeforeEach +// void setUp() { +// productAnalyticsService = new ProductAnalyticsService(productRepository); +// } + +// @Test +// void getTopSellers_SortByRevenue_ShouldReturnCorrectList() { +// // Arrange +// // Assuming TopSellerRequest still uses LocalDate for input +// LocalDate requestStartDate = LocalDate.of(2025, 1, 1); +// LocalDate requestEndDate = LocalDate.of(2025, 12, 31); + +// TopSellerRequest request = TopSellerRequest.builder() +// .startDate(requestStartDate) +// .endDate(requestEndDate) +// .limit(2) // Ensure limit is set to 2 +// .sortBy(TopSellerRequest.SortBy.REVENUE) +// .build(); + +// // Convert LocalDate from request to LocalDateTime for repository call +// // Start of the start day +// LocalDateTime startDate = requestStartDate.atStartOfDay(); +// // Start of the day AFTER the end day to include the whole end day in the query +// LocalDateTime endDate = requestEndDate.plusDays(1).atStartOfDay(); + +// UUID productId1 = UUID.randomUUID(); +// UUID productId2 = UUID.randomUUID(); +// // Mocking the repository to return 2 projections +// List projections = Arrays.asList( +// createProjection(productId1, "iPhone", "Electronics", new BigDecimal("1000.00"), 5L), +// createProjection(productId2, "MacBook", "Electronics", new BigDecimal("2000.00"), 2L) +// ); + +// // Ensure the mock returns the correct results based on the given arguments +// // Use LocalDateTime for the eq() matchers +// when(productRepository.findTopSellers( +// eq(startDate), +// eq(endDate), +// eq(2), +// eq("REVENUE"))) +// .thenReturn(projections); + +// // Act +// List result = productAnalyticsService.getTopSellers(request.getStartDate(), request.getEndDate(), request.getLimit(), request.getSortBy()); + +// // Log the result to help with debugging +// result.forEach(item -> System.out.println("Product ID: " + item.getProductId() + " Revenue: " + item.getValue())); + +// // Assert (Ensure the order is correct as per revenue) +// assertEquals(2, result.size(), "Expected 2 products in the list."); +// assertEquals(productId2, result.get(0).getProductId()); // MacBook should come first due to higher revenue +// assertEquals("MacBook", result.get(0).getProductName()); +// assertEquals("Electronics", result.get(0).getCategory()); +// assertEquals(new BigDecimal("2000.00"), result.get(0).getValue()); + +// assertEquals(productId1, result.get(1).getProductId()); +// assertEquals("iPhone", result.get(1).getProductName()); +// assertEquals("Electronics", result.get(1).getCategory()); +// assertEquals(new BigDecimal("1000.00"), result.get(1).getValue()); +// } + + +// @Test +// void getTopSellers_SortByUnits_ShouldReturnCorrectList() { +// // Arrange +// LocalDate requestStartDate = LocalDate.of(2025, 1, 1); +// LocalDate requestEndDate = LocalDate.of(2025, 12, 31); + +// TopSellerRequest request = TopSellerRequest.builder() +// .startDate(requestStartDate) +// .endDate(requestEndDate) +// .limit(2) +// .sortBy(TopSellerRequest.SortBy.UNITS) +// .build(); + +// // Convert LocalDate from request to LocalDateTime for repository call +// LocalDateTime startDate = requestStartDate.atStartOfDay(); +// LocalDateTime endDate = requestEndDate.plusDays(1).atStartOfDay(); + +// UUID productId1 = UUID.randomUUID(); +// UUID productId2 = UUID.randomUUID(); +// List projections = Arrays.asList( +// createProjection(productId1, "iPhone", "Electronics", new BigDecimal("1000.00"), 5L), +// createProjection(productId2, "MacBook", "Electronics", new BigDecimal("2000.00"), 2L) +// ); + +// // Use LocalDateTime for the eq() matchers +// when(productRepository.findTopSellers( +// eq(startDate), +// eq(endDate), +// eq(2), +// eq("UNITS"))) +// .thenReturn(projections); + +// // Act +// List result = productAnalyticsService.getTopSellers(request.getStartDate(), request.getEndDate(), request.getLimit(), request.getSortBy()); + +// // Assert (Ensure the order is correct as per units) +// assertEquals(2, result.size()); +// assertEquals(productId1, result.get(0).getProductId()); // iPhone comes first because of more units sold +// assertEquals("iPhone", result.get(0).getProductName()); +// assertEquals("Electronics", result.get(0).getCategory()); +// // Note: The projection returns revenue and units as BigDecimal and Long respectively. +// assertEquals(new BigDecimal("5"), result.get(0).getValue()); + + +// assertEquals(productId2, result.get(1).getProductId()); +// assertEquals("MacBook", result.get(1).getProductName()); +// assertEquals("Electronics", result.get(1).getCategory()); +// assertEquals(new BigDecimal("2"), result.get(1).getValue()); +// } + +// @Test +// void getTopSellers_WithEmptyResult_ShouldReturnEmptyList() { +// // Arrange +// LocalDate requestStartDate = LocalDate.of(2025, 1, 1); +// LocalDate requestEndDate = LocalDate.of(2025, 12, 31); + +// TopSellerRequest request = TopSellerRequest.builder() +// .startDate(requestStartDate) +// .endDate(requestEndDate) +// .limit(10) +// .sortBy(TopSellerRequest.SortBy.REVENUE) +// .build(); + +// // Use any() matchers for LocalDateTime parameters +// when(productRepository.findTopSellers(any(LocalDateTime.class), any(LocalDateTime.class), any(), any())) +// .thenReturn(Collections.emptyList()); + +// // Act +// List result = productAnalyticsService.getTopSellers(request.getStartDate(), request.getEndDate(), request.getLimit(), request.getSortBy()); + +// // Assert +// assertTrue(result.isEmpty()); +// } + + +// @Test +// void getTopSellers_WithZeroLimit_ShouldReturnEmptyList() { +// // Arrange +// LocalDate requestStartDate = LocalDate.of(2025, 1, 1); +// LocalDate requestEndDate = LocalDate.of(2025, 12, 31); +// TopSellerRequest request = TopSellerRequest.builder() +// .startDate(requestStartDate) +// .endDate(requestEndDate) +// .limit(0) +// .sortBy(TopSellerRequest.SortBy.REVENUE) +// .build(); + +// // Convert LocalDate from request to LocalDateTime for repository call +// LocalDateTime startDate = requestStartDate.atStartOfDay(); +// LocalDateTime endDate = requestEndDate.plusDays(1).atStartOfDay(); + + +// // Use LocalDateTime for the eq() matchers +// when(productRepository.findTopSellers( +// eq(startDate), +// eq(endDate), +// eq(0), +// eq("REVENUE"))) +// .thenReturn(Collections.emptyList()); + +// // Act +// List result = productAnalyticsService.getTopSellers(request.getStartDate(), request.getEndDate(), request.getLimit(), request.getSortBy()); + +// // Assert +// assertTrue(result.isEmpty()); +// } + +// private TopSellingProductProjection createProjection( +// final UUID id, +// final String name, +// final String category, +// final BigDecimal revenue, +// final Long units) { +// return new TopSellingProductProjection() { + +// @Override +// public UUID getId() { +// return id; +// } + + +// @Override +// public String getName() { +// return name; +// } +// @Override +// public String getCategory() { +// return category; +// } +// @Override +// public BigDecimal getTotalRevenue() { +// return revenue; +// } +// @Override +// public Long getTotalUnits() { +// return units; +// } +// }; +// } +// } \ No newline at end of file diff --git a/src/test/java/com/Podzilla/analytics/services/RevenueReportServiceTest.java b/src/test/java/com/Podzilla/analytics/services/RevenueReportServiceTest.java index c578ea0..8f4e799 100644 --- a/src/test/java/com/Podzilla/analytics/services/RevenueReportServiceTest.java +++ b/src/test/java/com/Podzilla/analytics/services/RevenueReportServiceTest.java @@ -1,211 +1,211 @@ -package com.Podzilla.analytics.services; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.when; -import static org.junit.jupiter.api.Assertions.*; - -import java.math.BigDecimal; -import java.time.LocalDate; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import com.Podzilla.analytics.api.dtos.revenue.RevenueByCategoryResponse; -import com.Podzilla.analytics.api.dtos.revenue.RevenueSummaryRequest; -import com.Podzilla.analytics.api.dtos.revenue.RevenueSummaryResponse; -import com.Podzilla.analytics.api.projections.revenue.RevenueByCategoryProjection; -import com.Podzilla.analytics.api.projections.revenue.RevenueSummaryProjection; -import com.Podzilla.analytics.repositories.OrderRepository; - -@ExtendWith(MockitoExtension.class) -class RevenueReportServiceTest { - - @Mock - private OrderRepository orderRepository; - - private RevenueReportService revenueReportService; - - @BeforeEach - void setUp() { - revenueReportService = new RevenueReportService(orderRepository); - } - - @Test - void getRevenueSummary_WithValidData_ShouldReturnCorrectSummary() { - // Arrange - LocalDate startDate = LocalDate.of(2025, 1, 1); - LocalDate endDate = LocalDate.of(2025, 12, 31); - RevenueSummaryRequest request = RevenueSummaryRequest.builder() - .startDate(startDate) - .endDate(endDate) - .period(RevenueSummaryRequest.Period.MONTHLY) - .build(); - - List projections = Arrays.asList( - summaryProjection(LocalDate.of(2025, 1, 1), new BigDecimal("1000.00")), - summaryProjection(LocalDate.of(2025, 2, 1), new BigDecimal("2000.00")) - ); - - when(orderRepository.findRevenueSummaryByPeriod(eq(startDate), eq(endDate), eq("MONTHLY"))) - .thenReturn(projections); - - // Act - List result = revenueReportService.getRevenueSummary(request.getStartDate(), - request.getEndDate(), request.getPeriod().name()); - - // Assert - assertEquals(2, result.size()); - assertEquals(LocalDate.of(2025, 1, 1), result.get(0).getPeriodStartDate()); - assertEquals(new BigDecimal("1000.00"), result.get(0).getTotalRevenue()); - assertEquals(LocalDate.of(2025, 2, 1), result.get(1).getPeriodStartDate()); - assertEquals(new BigDecimal("2000.00"), result.get(1).getTotalRevenue()); - } - - @Test - void getRevenueSummary_WithEmptyData_ShouldReturnEmptyList() { - // Arrange - LocalDate startDate = LocalDate.of(2025, 1, 1); - LocalDate endDate = LocalDate.of(2025, 12, 31); - RevenueSummaryRequest request = RevenueSummaryRequest.builder() - .startDate(startDate) - .endDate(endDate) - .period(RevenueSummaryRequest.Period.MONTHLY) - .build(); - - when(orderRepository.findRevenueSummaryByPeriod(any(), any(), any())) - .thenReturn(Collections.emptyList()); - - // Act - List result = revenueReportService.getRevenueSummary(request.getStartDate(), - request.getEndDate(), request.getPeriod().name()); - - // Assert - assertTrue(result.isEmpty()); - } - - @Test - void getRevenueSummary_WithStartDateAfterEndDate_ShouldReturnEmptyList() { - // Arrange - LocalDate startDate = LocalDate.of(2025, 12, 31); - LocalDate endDate = LocalDate.of(2025, 1, 1); - RevenueSummaryRequest request = RevenueSummaryRequest.builder() - .startDate(startDate) - .endDate(endDate) - .period(RevenueSummaryRequest.Period.MONTHLY) - .build(); - - when(orderRepository.findRevenueSummaryByPeriod(any(), any(), any())) - .thenReturn(Collections.emptyList()); - - // Act - List result = revenueReportService.getRevenueSummary(request.getStartDate(), - request.getEndDate(), request.getPeriod().name()); - - // Assert - assertTrue(result.isEmpty()); - } - - @Test - void getRevenueByCategory_WithValidData_ShouldReturnCorrectCategories() { - // Arrange - LocalDate startDate = LocalDate.of(2025, 1, 1); - LocalDate endDate = LocalDate.of(2025, 12, 31); List projections = Arrays.asList( - categoryProjection("Books", new BigDecimal("3000.00")), - categoryProjection("Electronics", new BigDecimal("5000.00")) - ); - - when(orderRepository.findRevenueByCategory(eq(startDate), eq(endDate))) - .thenReturn(projections);// Act - List result = revenueReportService.getRevenueByCategory(startDate, endDate); - - // Assert - assertEquals(2, result.size()); - assertEquals("Books", result.get(0).getCategory()); - assertEquals(new BigDecimal("3000.00"), result.get(0).getTotalRevenue()); - assertEquals("Electronics", result.get(1).getCategory()); - assertEquals(new BigDecimal("5000.00"), result.get(1).getTotalRevenue()); - } - - @Test - void getRevenueByCategory_WithEmptyData_ShouldReturnEmptyList() { - // Arrange - LocalDate startDate = LocalDate.of(2025, 1, 1); - LocalDate endDate = LocalDate.of(2025, 12, 31); - - when(orderRepository.findRevenueByCategory(any(), any())) - .thenReturn(Collections.emptyList()); - - // Act - List result = revenueReportService.getRevenueByCategory(startDate, endDate); - - // Assert - assertTrue(result.isEmpty()); - } - - @Test - void getRevenueByCategory_WithNullRevenue_ShouldHandleGracefully() { - // Arrange - LocalDate startDate = LocalDate.of(2025, 1, 1); - LocalDate endDate = LocalDate.of(2025, 12, 31); - - List projections = Arrays.asList( - new RevenueByCategoryProjection() { - @Override - public String getCategory() { - return "Electronics"; - } - @Override - public BigDecimal getTotalRevenue() { - return null; - } - } - ); - - when(orderRepository.findRevenueByCategory(eq(startDate), eq(endDate))) - .thenReturn(projections); - - // Act - List result = revenueReportService.getRevenueByCategory(startDate, endDate); - - // Assert - assertEquals(1, result.size()); - assertEquals("Electronics", result.get(0).getCategory()); - assertNull(result.get(0).getTotalRevenue()); - } - - @Test - void getRevenueByCategory_WithStartDateAfterEndDate_ShouldReturnEmptyList() { - // Arrange - LocalDate startDate = LocalDate.of(2025, 12, 31); - LocalDate endDate = LocalDate.of(2025, 1, 1); - - when(orderRepository.findRevenueByCategory(any(), any())) - .thenReturn(Collections.emptyList()); - - // Act - List result = revenueReportService.getRevenueByCategory(startDate, endDate); - - // Assert - assertTrue(result.isEmpty()); - } - private RevenueSummaryProjection summaryProjection(LocalDate date, BigDecimal revenue) { - return new RevenueSummaryProjection() { - public LocalDate getPeriod() { return date; } - public BigDecimal getTotalRevenue() { return revenue; } - }; -} - - private RevenueByCategoryProjection categoryProjection(String category, BigDecimal revenue) { - return new RevenueByCategoryProjection() { - public String getCategory() { return category; } - public BigDecimal getTotalRevenue() { return revenue; } - }; - } -} +// package com.Podzilla.analytics.services; + +// import static org.mockito.ArgumentMatchers.any; +// import static org.mockito.ArgumentMatchers.eq; +// import static org.mockito.Mockito.when; +// import static org.junit.jupiter.api.Assertions.*; + +// import java.math.BigDecimal; +// import java.time.LocalDate; +// import java.util.Arrays; +// import java.util.Collections; +// import java.util.List; + +// import org.junit.jupiter.api.BeforeEach; +// import org.junit.jupiter.api.Test; +// import org.junit.jupiter.api.extension.ExtendWith; +// import org.mockito.Mock; +// import org.mockito.junit.jupiter.MockitoExtension; + +// import com.Podzilla.analytics.api.dtos.revenue.RevenueByCategoryResponse; +// import com.Podzilla.analytics.api.dtos.revenue.RevenueSummaryRequest; +// import com.Podzilla.analytics.api.dtos.revenue.RevenueSummaryResponse; +// import com.Podzilla.analytics.api.projections.revenue.RevenueByCategoryProjection; +// import com.Podzilla.analytics.api.projections.revenue.RevenueSummaryProjection; +// import com.Podzilla.analytics.repositories.OrderRepository; + +// @ExtendWith(MockitoExtension.class) +// class RevenueReportServiceTest { + +// @Mock +// private OrderRepository orderRepository; + +// private RevenueReportService revenueReportService; + +// @BeforeEach +// void setUp() { +// revenueReportService = new RevenueReportService(orderRepository); +// } + +// @Test +// void getRevenueSummary_WithValidData_ShouldReturnCorrectSummary() { +// // Arrange +// LocalDate startDate = LocalDate.of(2025, 1, 1); +// LocalDate endDate = LocalDate.of(2025, 12, 31); +// RevenueSummaryRequest request = RevenueSummaryRequest.builder() +// .startDate(startDate) +// .endDate(endDate) +// .period(RevenueSummaryRequest.Period.MONTHLY) +// .build(); + +// List projections = Arrays.asList( +// summaryProjection(LocalDate.of(2025, 1, 1), new BigDecimal("1000.00")), +// summaryProjection(LocalDate.of(2025, 2, 1), new BigDecimal("2000.00")) +// ); + +// when(orderRepository.findRevenueSummaryByPeriod(eq(startDate), eq(endDate), eq("MONTHLY"))) +// .thenReturn(projections); + +// // Act +// List result = revenueReportService.getRevenueSummary(request.getStartDate(), +// request.getEndDate(), request.getPeriod().name()); + +// // Assert +// assertEquals(2, result.size()); +// assertEquals(LocalDate.of(2025, 1, 1), result.get(0).getPeriodStartDate()); +// assertEquals(new BigDecimal("1000.00"), result.get(0).getTotalRevenue()); +// assertEquals(LocalDate.of(2025, 2, 1), result.get(1).getPeriodStartDate()); +// assertEquals(new BigDecimal("2000.00"), result.get(1).getTotalRevenue()); +// } + +// @Test +// void getRevenueSummary_WithEmptyData_ShouldReturnEmptyList() { +// // Arrange +// LocalDate startDate = LocalDate.of(2025, 1, 1); +// LocalDate endDate = LocalDate.of(2025, 12, 31); +// RevenueSummaryRequest request = RevenueSummaryRequest.builder() +// .startDate(startDate) +// .endDate(endDate) +// .period(RevenueSummaryRequest.Period.MONTHLY) +// .build(); + +// when(orderRepository.findRevenueSummaryByPeriod(any(), any(), any())) +// .thenReturn(Collections.emptyList()); + +// // Act +// List result = revenueReportService.getRevenueSummary(request.getStartDate(), +// request.getEndDate(), request.getPeriod().name()); + +// // Assert +// assertTrue(result.isEmpty()); +// } + +// @Test +// void getRevenueSummary_WithStartDateAfterEndDate_ShouldReturnEmptyList() { +// // Arrange +// LocalDate startDate = LocalDate.of(2025, 12, 31); +// LocalDate endDate = LocalDate.of(2025, 1, 1); +// RevenueSummaryRequest request = RevenueSummaryRequest.builder() +// .startDate(startDate) +// .endDate(endDate) +// .period(RevenueSummaryRequest.Period.MONTHLY) +// .build(); + +// when(orderRepository.findRevenueSummaryByPeriod(any(), any(), any())) +// .thenReturn(Collections.emptyList()); + +// // Act +// List result = revenueReportService.getRevenueSummary(request.getStartDate(), +// request.getEndDate(), request.getPeriod().name()); + +// // Assert +// assertTrue(result.isEmpty()); +// } + +// @Test +// void getRevenueByCategory_WithValidData_ShouldReturnCorrectCategories() { +// // Arrange +// LocalDate startDate = LocalDate.of(2025, 1, 1); +// LocalDate endDate = LocalDate.of(2025, 12, 31); List projections = Arrays.asList( +// categoryProjection("Books", new BigDecimal("3000.00")), +// categoryProjection("Electronics", new BigDecimal("5000.00")) +// ); + +// when(orderRepository.findRevenueByCategory(eq(startDate), eq(endDate))) +// .thenReturn(projections);// Act +// List result = revenueReportService.getRevenueByCategory(startDate, endDate); + +// // Assert +// assertEquals(2, result.size()); +// assertEquals("Books", result.get(0).getCategory()); +// assertEquals(new BigDecimal("3000.00"), result.get(0).getTotalRevenue()); +// assertEquals("Electronics", result.get(1).getCategory()); +// assertEquals(new BigDecimal("5000.00"), result.get(1).getTotalRevenue()); +// } + +// @Test +// void getRevenueByCategory_WithEmptyData_ShouldReturnEmptyList() { +// // Arrange +// LocalDate startDate = LocalDate.of(2025, 1, 1); +// LocalDate endDate = LocalDate.of(2025, 12, 31); + +// when(orderRepository.findRevenueByCategory(any(), any())) +// .thenReturn(Collections.emptyList()); + +// // Act +// List result = revenueReportService.getRevenueByCategory(startDate, endDate); + +// // Assert +// assertTrue(result.isEmpty()); +// } + +// @Test +// void getRevenueByCategory_WithNullRevenue_ShouldHandleGracefully() { +// // Arrange +// LocalDate startDate = LocalDate.of(2025, 1, 1); +// LocalDate endDate = LocalDate.of(2025, 12, 31); + +// List projections = Arrays.asList( +// new RevenueByCategoryProjection() { +// @Override +// public String getCategory() { +// return "Electronics"; +// } +// @Override +// public BigDecimal getTotalRevenue() { +// return null; +// } +// } +// ); + +// when(orderRepository.findRevenueByCategory(eq(startDate), eq(endDate))) +// .thenReturn(projections); + +// // Act +// List result = revenueReportService.getRevenueByCategory(startDate, endDate); + +// // Assert +// assertEquals(1, result.size()); +// assertEquals("Electronics", result.get(0).getCategory()); +// assertNull(result.get(0).getTotalRevenue()); +// } + +// @Test +// void getRevenueByCategory_WithStartDateAfterEndDate_ShouldReturnEmptyList() { +// // Arrange +// LocalDate startDate = LocalDate.of(2025, 12, 31); +// LocalDate endDate = LocalDate.of(2025, 1, 1); + +// when(orderRepository.findRevenueByCategory(any(), any())) +// .thenReturn(Collections.emptyList()); + +// // Act +// List result = revenueReportService.getRevenueByCategory(startDate, endDate); + +// // Assert +// assertTrue(result.isEmpty()); +// } +// private RevenueSummaryProjection summaryProjection(LocalDate date, BigDecimal revenue) { +// return new RevenueSummaryProjection() { +// public LocalDate getPeriod() { return date; } +// public BigDecimal getTotalRevenue() { return revenue; } +// }; +// } + +// private RevenueByCategoryProjection categoryProjection(String category, BigDecimal revenue) { +// return new RevenueByCategoryProjection() { +// public String getCategory() { return category; } +// public BigDecimal getTotalRevenue() { return revenue; } +// }; +// } +// } From 598c803f08263c9e057a93040a2b80c44fb3321b Mon Sep 17 00:00:00 2001 From: AhmadHoseiny Date: Sun, 18 May 2025 05:21:37 +0200 Subject: [PATCH 05/10] feat: added listeners for users and inventory events --- .../controllers/RabbitTesterController.java | 34 +++-- .../analytics/config/DatabaseSeeder.java | 16 +-- .../messaging/InvokerDispatcherConfig.java | 21 +-- .../messaging/commands/CommandFactory.java | 57 +++++++++ .../inventory/CreateProductCommand.java | 30 +++++ .../inventory/UpdateInventoryCommand.java | 25 ++++ .../commands/user/RegisterCourierCommand.java | 22 ++++ .../messaging/invokers/InvokerFactory.java | 19 ++- .../inventory/InventoryUpdatedInvoker.java | 19 ++- .../inventory/ProductCreatedInvoker.java | 21 ++- .../order/OrderAssignedToCourierInvoker.java | 9 ++ .../invokers/order/OrderCancelledInvoker.java | 9 ++ .../invokers/order/OrderDeliveredInvoker.java | 9 ++ .../order/OrderDeliveryFailedInvoker.java | 9 ++ .../order/OrderOutForDeliveryInvoker.java | 9 ++ .../invokers/order/OrderPlacedInvoker.java | 9 ++ .../user/CourierRegisteredInvoker.java | 18 ++- .../analytics/models/InventorySnapshot.java | 3 + .../analytics/models/SalesLineItem.java | 2 + .../repositories/CourierRepository.java | 3 +- .../repositories/CustomerRepository.java | 3 +- .../InventorySnapshotRepository.java | 3 +- .../repositories/OrderRepository.java | 3 +- .../repositories/ProductRepository.java | 4 +- .../repositories/RegionRepository.java | 3 +- .../repositories/SalesLineItemRepository.java | 3 +- .../services/CourierAnalyticsService.java | 120 +++++++++++------- .../services/InventoryAnalyticsService.java | 30 +++++ .../services/ProductAnalyticsService.java | 21 +++ .../analytics/util/DatetimeFormatter.java | 10 ++ 30 files changed, 448 insertions(+), 96 deletions(-) create mode 100644 src/main/java/com/Podzilla/analytics/messaging/commands/inventory/CreateProductCommand.java create mode 100644 src/main/java/com/Podzilla/analytics/messaging/commands/inventory/UpdateInventoryCommand.java create mode 100644 src/main/java/com/Podzilla/analytics/messaging/commands/user/RegisterCourierCommand.java diff --git a/src/main/java/com/Podzilla/analytics/api/controllers/RabbitTesterController.java b/src/main/java/com/Podzilla/analytics/api/controllers/RabbitTesterController.java index 7bf1e97..881203b 100644 --- a/src/main/java/com/Podzilla/analytics/api/controllers/RabbitTesterController.java +++ b/src/main/java/com/Podzilla/analytics/api/controllers/RabbitTesterController.java @@ -6,6 +6,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import com.Podzilla.analytics.messaging.AnalyticsRabbitListener; @@ -25,13 +26,13 @@ @RestController @RequestMapping("/rabbit-tester") public class RabbitTesterController { - @Autowired private AnalyticsRabbitListener listener; @GetMapping("/courier-registered-event") public void testCourierRegisteredEvent() { - BaseEvent event = new CourierRegisteredEvent("1", "ahmad", "010"); + BaseEvent event = new CourierRegisteredEvent( + "87f23fee-2e09-4331-bc9c-912045ef0832", "ahmad the courier", "010"); listener.handleUserEvents(event); } @@ -46,39 +47,44 @@ public void testCustomerRegisteredEvent() { @GetMapping("/order-assigned-to-courier-event") public void testOrderAssignedToCourierEvent() { - BaseEvent event = new OrderAssignedToCourierEvent("1", "2"); + BaseEvent event = new OrderAssignedToCourierEvent( + "e715d122-2628-4c68-82bc-a3c4fc1eefd1", "2"); listener.handleOrderEvents(event); } @GetMapping("/order-cancelled-event") public void testOrderCancelledEvent() { - BaseEvent event = new OrderCancelledEvent("1", "2", "some reason"); + BaseEvent event = new OrderCancelledEvent( + "d7c897d1-b23d-46aa-bfb6-258b4b8dcbd4", "2", "some reason"); listener.handleOrderEvents(event); } @GetMapping("/order-delivered-event") public void testOrderDeliveredEvent() { - BaseEvent event = new OrderDeliveredEvent("1", "2", + BaseEvent event = new OrderDeliveredEvent( + "21063caa-4265-4286-8b16-6361b5bda83a", "2", new BigDecimal("10.0")); listener.handleOrderEvents(event); } @GetMapping("/order-delivery-failed-event") public void testOrderDeliveryFailedEvent() { - BaseEvent event = new OrderDeliveryFailedEvent("1", "2", "some reason"); + BaseEvent event = new OrderDeliveryFailedEvent( + "a0736362-6e37-46d7-95c6-4ad9092bf642", "2", "some reason"); listener.handleOrderEvents(event); } @GetMapping("/order-out-for-delivery-event") public void testOrderOutForDeliveryEvent() { - BaseEvent event = new OrderOutForDeliveryEvent("1", "2"); + BaseEvent event = new OrderOutForDeliveryEvent( + "55399710-a835-4f66-ba9d-1d299e40702b", "2"); listener.handleOrderEvents(event); } @GetMapping("/order-placed-event") public void testOrderPlacedEvent() { BaseEvent event = new OrderPlacedEvent( - "1", + "a1aa7c7d-fe6a-491f-a2cc-b3b923340777", "2", new BigDecimal("10.0"), new ArrayList<>(), @@ -94,16 +100,20 @@ public void testOrderPlacedEvent() { } @GetMapping("inventory-updated-event") - public void testInventoryUpdatedEvent() { - BaseEvent event = new InventoryUpdatedEvent("1", Integer.valueOf(1)); + public void testInventoryUpdatedEvent( + @RequestParam final String productId, + @RequestParam final Integer quantity + ) { + BaseEvent event = new InventoryUpdatedEvent( + productId, quantity); listener.handleInventoryEvents(event); } @GetMapping("product-created-event") public void testProductCreatedEvent() { BaseEvent event = new ProductCreatedEvent( - "1", - "some name", + "f12afb47-ad23-4ca8-a162-8b12de7a5e49", + "the rabbit product", "some category", new BigDecimal("10.0"), Integer.valueOf(1) diff --git a/src/main/java/com/Podzilla/analytics/config/DatabaseSeeder.java b/src/main/java/com/Podzilla/analytics/config/DatabaseSeeder.java index 9fed9ad..95c087c 100644 --- a/src/main/java/com/Podzilla/analytics/config/DatabaseSeeder.java +++ b/src/main/java/com/Podzilla/analytics/config/DatabaseSeeder.java @@ -237,11 +237,11 @@ private void seedOrders( .courierRating(RATING_GOOD) .build(); SalesLineItem itemFirstOrderFirst = SalesLineItem.builder() - .id(UUID.randomUUID()) + // .id(UUID.randomUUID()) .order(order1).product(products.get(0)).quantity(1) .pricePerUnit(PRICE_PROD1).build(); SalesLineItem itemFirstOrderSecond = SalesLineItem.builder() - .id(UUID.randomUUID()) + // .id(UUID.randomUUID()) .order(order1).product(products.get(2)).quantity(2) .pricePerUnit(PRICE_PROD3).build(); order1.setSalesLineItems(Arrays.asList(itemFirstOrderFirst, @@ -272,7 +272,7 @@ private void seedOrders( .courierRating(null).failureReason(null) .build(); SalesLineItem itemSecondOrderFirst = SalesLineItem.builder() - .id(UUID.randomUUID()) + // .id(UUID.randomUUID()) .order(order2).product(products.get(1)).quantity(1) .pricePerUnit(PRICE_PROD2).build(); order2.setSalesLineItems(List.of(itemSecondOrderFirst)); @@ -301,7 +301,7 @@ private void seedOrders( .courierRating(RATING_POOR) .build(); SalesLineItem itemThirdOrderFirst = SalesLineItem.builder() - .id(UUID.randomUUID()) + // .id(UUID.randomUUID()) .order(order3).product(products.get(INDEX_THREE)).quantity(1) .pricePerUnit(PRICE_PROD4).build(); order3.setSalesLineItems(List.of(itemThirdOrderFirst)); @@ -328,11 +328,11 @@ private void seedOrders( .courierRating(RATING_EXCELLENT) .build(); SalesLineItem itemFourthOrderFirst = SalesLineItem.builder() - .id(UUID.randomUUID()) + // .id(UUID.randomUUID()) .order(order4).product(products.get(0)).quantity(1) .pricePerUnit(PRICE_PROD1).build(); SalesLineItem itemFourthOrderSecond = SalesLineItem.builder() - .id(UUID.randomUUID()) + // .id(UUID.randomUUID()) .order(order4).product(products.get(INDEX_THREE)).quantity(1) .pricePerUnit(PRICE_PROD4).build(); order4.setSalesLineItems(Arrays.asList(itemFourthOrderFirst, @@ -364,7 +364,7 @@ private void seedInventorySnapshot( final Product product, final int range, final int quantity) { inventorySnapshotRepository.save( InventorySnapshot.builder() - .id(UUID.randomUUID()) + // .id(UUID.randomUUID()) .product(product) .quantity(random.nextInt(range) + product.getLowStockThreshold()) @@ -373,7 +373,7 @@ private void seedInventorySnapshot( .build()); inventorySnapshotRepository.save( InventorySnapshot.builder() - .id(UUID.randomUUID()) + // .id(UUID.randomUUID()) .product(product) .quantity(random.nextInt(quantity) + product.getLowStockThreshold()) diff --git a/src/main/java/com/Podzilla/analytics/messaging/InvokerDispatcherConfig.java b/src/main/java/com/Podzilla/analytics/messaging/InvokerDispatcherConfig.java index 759ff81..6c85f82 100644 --- a/src/main/java/com/Podzilla/analytics/messaging/InvokerDispatcherConfig.java +++ b/src/main/java/com/Podzilla/analytics/messaging/InvokerDispatcherConfig.java @@ -5,6 +5,7 @@ import org.springframework.context.annotation.Configuration; import com.Podzilla.analytics.messaging.invokers.user.CourierRegisteredInvoker; +import com.Podzilla.analytics.messaging.invokers.user.CustomerRegisteredInvoker; import com.Podzilla.analytics.messaging.invokers.order.OrderAssignedToCourierInvoker; import com.Podzilla.analytics.messaging.invokers.order.OrderDeliveryFailedInvoker; import com.Podzilla.analytics.messaging.invokers.order.OrderPlacedInvoker; @@ -53,12 +54,12 @@ private void registerUserInvokers( ) { dispatcher.registerInvoker( CourierRegisteredEvent.class, - new CourierRegisteredInvoker() + invokerFactory.createInvoker(CourierRegisteredInvoker.class) ); dispatcher.registerInvoker( CustomerRegisteredEvent.class, - invokerFactory.createRegisterCustomerInvoker() + invokerFactory.createInvoker(CustomerRegisteredInvoker.class) ); } @@ -67,27 +68,27 @@ private void registerOrderInvokers( ) { dispatcher.registerInvoker( OrderAssignedToCourierEvent.class, - new OrderAssignedToCourierInvoker() + invokerFactory.createInvoker(OrderAssignedToCourierInvoker.class) ); dispatcher.registerInvoker( OrderCancelledEvent.class, - new OrderCancelledInvoker() + invokerFactory.createInvoker(OrderCancelledInvoker.class) ); dispatcher.registerInvoker( OrderDeliveredEvent.class, - new OrderDeliveredInvoker() + invokerFactory.createInvoker(OrderDeliveredInvoker.class) ); dispatcher.registerInvoker( OrderDeliveryFailedEvent.class, - new OrderDeliveryFailedInvoker() + invokerFactory.createInvoker(OrderDeliveryFailedInvoker.class) ); dispatcher.registerInvoker( OrderOutForDeliveryEvent.class, - new OrderOutForDeliveryInvoker() + invokerFactory.createInvoker(OrderOutForDeliveryInvoker.class) ); dispatcher.registerInvoker( OrderPlacedEvent.class, - new OrderPlacedInvoker() + invokerFactory.createInvoker(OrderPlacedInvoker.class) ); } @@ -96,11 +97,11 @@ private void registerInventoryInvokers( ) { dispatcher.registerInvoker( InventoryUpdatedEvent.class, - new InventoryUpdatedInvoker() + invokerFactory.createInvoker(InventoryUpdatedInvoker.class) ); dispatcher.registerInvoker( ProductCreatedEvent.class, - new ProductCreatedInvoker() + invokerFactory.createInvoker(ProductCreatedInvoker.class) ); } diff --git a/src/main/java/com/Podzilla/analytics/messaging/commands/CommandFactory.java b/src/main/java/com/Podzilla/analytics/messaging/commands/CommandFactory.java index 23fc0ba..7d6bc95 100644 --- a/src/main/java/com/Podzilla/analytics/messaging/commands/CommandFactory.java +++ b/src/main/java/com/Podzilla/analytics/messaging/commands/CommandFactory.java @@ -4,14 +4,31 @@ import org.springframework.stereotype.Component; import com.Podzilla.analytics.services.CustomerAnalyticsService; +import com.Podzilla.analytics.services.CourierAnalyticsService; +import com.Podzilla.analytics.services.ProductAnalyticsService; +import com.Podzilla.analytics.services.InventoryAnalyticsService; import com.Podzilla.analytics.messaging.commands.user.RegisterCustomerCommand; +import com.Podzilla.analytics.messaging.commands.user.RegisterCourierCommand; +import com.Podzilla.analytics.messaging.commands.inventory.CreateProductCommand; +import com.Podzilla.analytics.messaging.commands.inventory.UpdateInventoryCommand; +import java.math.BigDecimal; +import java.time.Instant; @Component public class CommandFactory { @Autowired private CustomerAnalyticsService customerAnalyticsService; + @Autowired + private CourierAnalyticsService courierAnalyticsService; + + @Autowired + private ProductAnalyticsService productAnalyticsService; + + @Autowired + private InventoryAnalyticsService inventoryAnalyticsService; + public RegisterCustomerCommand createRegisterCustomerCommand( final String customerId, final String customerName @@ -23,4 +40,44 @@ public RegisterCustomerCommand createRegisterCustomerCommand( .build(); } + public RegisterCourierCommand createRegisterCourierCommand( + final String courierId, + final String courierName + ) { + return RegisterCourierCommand.builder() + .courierAnalyticsService(courierAnalyticsService) + .courierId(courierId) + .courierName(courierName) + .build(); + } + + public CreateProductCommand createCreateProductCommand( + final String productId, + final String productName, + final String productCategory, + final BigDecimal productCost, + final Integer productLowStockThreshold + ) { + return CreateProductCommand.builder() + .productAnalyticsService(productAnalyticsService) + .productId(productId) + .productName(productName) + .productCategory(productCategory) + .productCost(productCost) + .productLowStockThreshold(productLowStockThreshold) + .build(); + } + + public UpdateInventoryCommand createUpdateInventoryCommand( + final String productId, + final Integer quantity, + final Instant timestamp + ) { + return UpdateInventoryCommand.builder() + .inventoryAnalyticsService(inventoryAnalyticsService) + .productId(productId) + .quantity(quantity) + .timestamp(timestamp) + .build(); + } } diff --git a/src/main/java/com/Podzilla/analytics/messaging/commands/inventory/CreateProductCommand.java b/src/main/java/com/Podzilla/analytics/messaging/commands/inventory/CreateProductCommand.java new file mode 100644 index 0000000..a13cb48 --- /dev/null +++ b/src/main/java/com/Podzilla/analytics/messaging/commands/inventory/CreateProductCommand.java @@ -0,0 +1,30 @@ +package com.Podzilla.analytics.messaging.commands.inventory; + +import java.math.BigDecimal; +import com.Podzilla.analytics.services.ProductAnalyticsService; + +import lombok.Builder; + +import com.Podzilla.analytics.messaging.commands.Command; + +@Builder +public class CreateProductCommand implements Command { + + private ProductAnalyticsService productAnalyticsService; + private String productId; + private String productName; + private String productCategory; + private BigDecimal productCost; + private Integer productLowStockThreshold; + + @Override + public void execute() { + productAnalyticsService.saveProduct( + productId, + productName, + productCategory, + productCost, + productLowStockThreshold + ); + } +} diff --git a/src/main/java/com/Podzilla/analytics/messaging/commands/inventory/UpdateInventoryCommand.java b/src/main/java/com/Podzilla/analytics/messaging/commands/inventory/UpdateInventoryCommand.java new file mode 100644 index 0000000..1bbfcc2 --- /dev/null +++ b/src/main/java/com/Podzilla/analytics/messaging/commands/inventory/UpdateInventoryCommand.java @@ -0,0 +1,25 @@ +package com.Podzilla.analytics.messaging.commands.inventory; + +import com.Podzilla.analytics.services.InventoryAnalyticsService; + +import lombok.Builder; + +import com.Podzilla.analytics.messaging.commands.Command; +import java.time.Instant; + +@Builder +public class UpdateInventoryCommand implements Command { + private final InventoryAnalyticsService inventoryAnalyticsService; + private final String productId; + private final Integer quantity; + private final Instant timestamp; + + @Override + public void execute() { + inventoryAnalyticsService.saveInventorySnapshot( + productId, + quantity, + timestamp + ); + } +} diff --git a/src/main/java/com/Podzilla/analytics/messaging/commands/user/RegisterCourierCommand.java b/src/main/java/com/Podzilla/analytics/messaging/commands/user/RegisterCourierCommand.java new file mode 100644 index 0000000..58d638e --- /dev/null +++ b/src/main/java/com/Podzilla/analytics/messaging/commands/user/RegisterCourierCommand.java @@ -0,0 +1,22 @@ +package com.Podzilla.analytics.messaging.commands.user; + +import com.Podzilla.analytics.messaging.commands.Command; +import com.Podzilla.analytics.services.CourierAnalyticsService; + +import lombok.Builder; + +@Builder +public class RegisterCourierCommand implements Command { + + private CourierAnalyticsService courierAnalyticsService; + private String courierId; + private String courierName; + + @Override + public void execute() { + courierAnalyticsService.saveCourier( + courierId, + courierName + ); + } +} diff --git a/src/main/java/com/Podzilla/analytics/messaging/invokers/InvokerFactory.java b/src/main/java/com/Podzilla/analytics/messaging/invokers/InvokerFactory.java index c9d6901..8577f28 100644 --- a/src/main/java/com/Podzilla/analytics/messaging/invokers/InvokerFactory.java +++ b/src/main/java/com/Podzilla/analytics/messaging/invokers/InvokerFactory.java @@ -1,10 +1,11 @@ package com.Podzilla.analytics.messaging.invokers; +import java.lang.reflect.Constructor; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import com.Podzilla.analytics.messaging.commands.CommandFactory; -import com.Podzilla.analytics.messaging.invokers.user.CustomerRegisteredInvoker; @Component public class InvokerFactory { @@ -16,7 +17,19 @@ public InvokerFactory(final CommandFactory commandFactory) { this.commandFactory = commandFactory; } - public CustomerRegisteredInvoker createRegisterCustomerInvoker() { - return new CustomerRegisteredInvoker(commandFactory); + public > T createInvoker( + final Class invokerClass + ) { + try { + Constructor constructor = + invokerClass.getConstructor(CommandFactory.class); + return constructor.newInstance(commandFactory); + } catch (Exception e) { + throw new RuntimeException( + "Failed to create invoker of type: " + invokerClass.getName(), e + ); } } + +} + diff --git a/src/main/java/com/Podzilla/analytics/messaging/invokers/inventory/InventoryUpdatedInvoker.java b/src/main/java/com/Podzilla/analytics/messaging/invokers/inventory/InventoryUpdatedInvoker.java index cdc981a..a653df2 100644 --- a/src/main/java/com/Podzilla/analytics/messaging/invokers/inventory/InventoryUpdatedInvoker.java +++ b/src/main/java/com/Podzilla/analytics/messaging/invokers/inventory/InventoryUpdatedInvoker.java @@ -1,15 +1,30 @@ package com.Podzilla.analytics.messaging.invokers.inventory; +import org.springframework.beans.factory.annotation.Autowired; + +import com.Podzilla.analytics.messaging.commands.CommandFactory; +import com.Podzilla.analytics.messaging.commands.inventory.UpdateInventoryCommand; import com.Podzilla.analytics.messaging.invokers.Invoker; import com.podzilla.mq.events.InventoryUpdatedEvent; public class InventoryUpdatedInvoker implements Invoker { + @Autowired + private final CommandFactory commandFactory; + public InventoryUpdatedInvoker(final CommandFactory commandFactory) { + this.commandFactory = commandFactory; + } + @Override public void invoke(final InventoryUpdatedEvent event) { - // create a command and call its execute method - System.out.println("Inventory Updated Event Invoked: " + event); + UpdateInventoryCommand command = + commandFactory.createUpdateInventoryCommand( + event.getProductId(), + event.getNewQuantity(), + event.getTimestamp() + ); + command.execute(); } } diff --git a/src/main/java/com/Podzilla/analytics/messaging/invokers/inventory/ProductCreatedInvoker.java b/src/main/java/com/Podzilla/analytics/messaging/invokers/inventory/ProductCreatedInvoker.java index f84fd12..ad36a28 100644 --- a/src/main/java/com/Podzilla/analytics/messaging/invokers/inventory/ProductCreatedInvoker.java +++ b/src/main/java/com/Podzilla/analytics/messaging/invokers/inventory/ProductCreatedInvoker.java @@ -1,14 +1,31 @@ package com.Podzilla.analytics.messaging.invokers.inventory; +import org.springframework.beans.factory.annotation.Autowired; + +import com.Podzilla.analytics.messaging.commands.CommandFactory; +import com.Podzilla.analytics.messaging.commands.inventory.CreateProductCommand; import com.Podzilla.analytics.messaging.invokers.Invoker; import com.podzilla.mq.events.ProductCreatedEvent; public class ProductCreatedInvoker implements Invoker { + @Autowired + private final CommandFactory commandFactory; + public ProductCreatedInvoker(final CommandFactory commandFactory) { + this.commandFactory = commandFactory; + } + @Override public void invoke(final ProductCreatedEvent event) { - // create a command and call its execute method - System.out.println("Product Created Event Invoked: " + event); + CreateProductCommand command = commandFactory + .createCreateProductCommand( + event.getProductId(), + event.getName(), + event.getCategory(), + event.getCost(), + event.getLowStockThreshold() + ); + command.execute(); } } diff --git a/src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderAssignedToCourierInvoker.java b/src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderAssignedToCourierInvoker.java index a68e932..cc01a8f 100644 --- a/src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderAssignedToCourierInvoker.java +++ b/src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderAssignedToCourierInvoker.java @@ -1,11 +1,20 @@ package com.Podzilla.analytics.messaging.invokers.order; +import org.springframework.beans.factory.annotation.Autowired; + +import com.Podzilla.analytics.messaging.commands.CommandFactory; import com.Podzilla.analytics.messaging.invokers.Invoker; import com.podzilla.mq.events.OrderAssignedToCourierEvent; public class OrderAssignedToCourierInvoker implements Invoker { + @Autowired + private final CommandFactory commandFactory; + public OrderAssignedToCourierInvoker(final CommandFactory commandFactory) { + this.commandFactory = commandFactory; + } + @Override public void invoke(final OrderAssignedToCourierEvent event) { // create a command and call its execute method diff --git a/src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderCancelledInvoker.java b/src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderCancelledInvoker.java index 61860d8..8b3499b 100644 --- a/src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderCancelledInvoker.java +++ b/src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderCancelledInvoker.java @@ -1,11 +1,20 @@ package com.Podzilla.analytics.messaging.invokers.order; +import org.springframework.beans.factory.annotation.Autowired; + +import com.Podzilla.analytics.messaging.commands.CommandFactory; import com.Podzilla.analytics.messaging.invokers.Invoker; import com.podzilla.mq.events.OrderCancelledEvent; public class OrderCancelledInvoker implements Invoker { + @Autowired + private final CommandFactory commandFactory; + public OrderCancelledInvoker(final CommandFactory commandFactory) { + this.commandFactory = commandFactory; + } + @Override public void invoke(final OrderCancelledEvent event) { // create a command and call its execute method diff --git a/src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderDeliveredInvoker.java b/src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderDeliveredInvoker.java index befda6a..903c97f 100644 --- a/src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderDeliveredInvoker.java +++ b/src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderDeliveredInvoker.java @@ -1,11 +1,20 @@ package com.Podzilla.analytics.messaging.invokers.order; +import org.springframework.beans.factory.annotation.Autowired; + +import com.Podzilla.analytics.messaging.commands.CommandFactory; import com.Podzilla.analytics.messaging.invokers.Invoker; import com.podzilla.mq.events.OrderDeliveredEvent; public class OrderDeliveredInvoker implements Invoker { + @Autowired + private final CommandFactory commandFactory; + public OrderDeliveredInvoker(final CommandFactory commandFactory) { + this.commandFactory = commandFactory; + } + @Override public void invoke(final OrderDeliveredEvent event) { // create a command and call its execute method diff --git a/src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderDeliveryFailedInvoker.java b/src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderDeliveryFailedInvoker.java index 031b369..eebcd8e 100644 --- a/src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderDeliveryFailedInvoker.java +++ b/src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderDeliveryFailedInvoker.java @@ -1,11 +1,20 @@ package com.Podzilla.analytics.messaging.invokers.order; +import org.springframework.beans.factory.annotation.Autowired; + +import com.Podzilla.analytics.messaging.commands.CommandFactory; import com.Podzilla.analytics.messaging.invokers.Invoker; import com.podzilla.mq.events.OrderDeliveryFailedEvent; public class OrderDeliveryFailedInvoker implements Invoker { + @Autowired + private final CommandFactory commandFactory; + public OrderDeliveryFailedInvoker(final CommandFactory commandFactory) { + this.commandFactory = commandFactory; + } + @Override public void invoke(final OrderDeliveryFailedEvent event) { // create a command and call its execute method diff --git a/src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderOutForDeliveryInvoker.java b/src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderOutForDeliveryInvoker.java index bb1d681..b9caacf 100644 --- a/src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderOutForDeliveryInvoker.java +++ b/src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderOutForDeliveryInvoker.java @@ -1,11 +1,20 @@ package com.Podzilla.analytics.messaging.invokers.order; +import org.springframework.beans.factory.annotation.Autowired; + +import com.Podzilla.analytics.messaging.commands.CommandFactory; import com.Podzilla.analytics.messaging.invokers.Invoker; import com.podzilla.mq.events.OrderOutForDeliveryEvent; public class OrderOutForDeliveryInvoker implements Invoker { + @Autowired + private final CommandFactory commandFactory; + public OrderOutForDeliveryInvoker(final CommandFactory commandFactory) { + this.commandFactory = commandFactory; + } + @Override public void invoke(final OrderOutForDeliveryEvent event) { // create a command and call its execute method diff --git a/src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderPlacedInvoker.java b/src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderPlacedInvoker.java index 3bca1dd..77a2899 100644 --- a/src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderPlacedInvoker.java +++ b/src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderPlacedInvoker.java @@ -1,11 +1,20 @@ package com.Podzilla.analytics.messaging.invokers.order; +import org.springframework.beans.factory.annotation.Autowired; + +import com.Podzilla.analytics.messaging.commands.CommandFactory; import com.Podzilla.analytics.messaging.invokers.Invoker; import com.podzilla.mq.events.OrderPlacedEvent; public class OrderPlacedInvoker implements Invoker { + @Autowired + private final CommandFactory commandFactory; + public OrderPlacedInvoker(final CommandFactory commandFactory) { + this.commandFactory = commandFactory; + } + @Override public void invoke(final OrderPlacedEvent event) { // create a command and call its execute method diff --git a/src/main/java/com/Podzilla/analytics/messaging/invokers/user/CourierRegisteredInvoker.java b/src/main/java/com/Podzilla/analytics/messaging/invokers/user/CourierRegisteredInvoker.java index 5aa734e..760625b 100644 --- a/src/main/java/com/Podzilla/analytics/messaging/invokers/user/CourierRegisteredInvoker.java +++ b/src/main/java/com/Podzilla/analytics/messaging/invokers/user/CourierRegisteredInvoker.java @@ -2,13 +2,27 @@ import com.Podzilla.analytics.messaging.invokers.Invoker; import com.podzilla.mq.events.CourierRegisteredEvent; +import org.springframework.beans.factory.annotation.Autowired; +import com.Podzilla.analytics.messaging.commands.CommandFactory; +import com.Podzilla.analytics.messaging.commands.user.RegisterCourierCommand; + public class CourierRegisteredInvoker implements Invoker { + @Autowired + private final CommandFactory commandFactory; + public CourierRegisteredInvoker(final CommandFactory commandFactory) { + this.commandFactory = commandFactory; + } + @Override public void invoke(final CourierRegisteredEvent event) { - // create a command and call its execute method - System.out.println("Courier Registered Event Invoked: " + event); + RegisterCourierCommand command = commandFactory + .createRegisterCourierCommand( + event.getCourierId(), + event.getName() + ); + command.execute(); } } diff --git a/src/main/java/com/Podzilla/analytics/models/InventorySnapshot.java b/src/main/java/com/Podzilla/analytics/models/InventorySnapshot.java index 3546f72..cb82d4f 100644 --- a/src/main/java/com/Podzilla/analytics/models/InventorySnapshot.java +++ b/src/main/java/com/Podzilla/analytics/models/InventorySnapshot.java @@ -12,6 +12,8 @@ import lombok.NoArgsConstructor; import java.util.UUID; +import jakarta.persistence.GeneratedValue; + @Entity @Table(name = "inventory_snapshots") @Data @@ -20,6 +22,7 @@ @AllArgsConstructor public class InventorySnapshot { @Id + @GeneratedValue(generator = "uuid") private UUID id; private LocalDateTime timestamp; diff --git a/src/main/java/com/Podzilla/analytics/models/SalesLineItem.java b/src/main/java/com/Podzilla/analytics/models/SalesLineItem.java index e7efea8..f93f7a3 100644 --- a/src/main/java/com/Podzilla/analytics/models/SalesLineItem.java +++ b/src/main/java/com/Podzilla/analytics/models/SalesLineItem.java @@ -2,6 +2,7 @@ import java.math.BigDecimal; import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; @@ -20,6 +21,7 @@ @AllArgsConstructor public class SalesLineItem { @Id + @GeneratedValue(generator = "uuid") private UUID id; private int quantity; diff --git a/src/main/java/com/Podzilla/analytics/repositories/CourierRepository.java b/src/main/java/com/Podzilla/analytics/repositories/CourierRepository.java index eae7c5e..b50cada 100644 --- a/src/main/java/com/Podzilla/analytics/repositories/CourierRepository.java +++ b/src/main/java/com/Podzilla/analytics/repositories/CourierRepository.java @@ -9,8 +9,9 @@ import com.Podzilla.analytics.api.projections.courier.CourierPerformanceProjection; import com.Podzilla.analytics.models.Courier; +import java.util.UUID; -public interface CourierRepository extends JpaRepository { +public interface CourierRepository extends JpaRepository { @Query(value = "SELECT c.id AS courierId, " + "c.name AS courierName, " diff --git a/src/main/java/com/Podzilla/analytics/repositories/CustomerRepository.java b/src/main/java/com/Podzilla/analytics/repositories/CustomerRepository.java index 79bd7f8..f8195df 100644 --- a/src/main/java/com/Podzilla/analytics/repositories/CustomerRepository.java +++ b/src/main/java/com/Podzilla/analytics/repositories/CustomerRepository.java @@ -11,9 +11,10 @@ import com.Podzilla.analytics.models.Customer; import java.time.LocalDateTime; +import java.util.UUID; @Repository -public interface CustomerRepository extends JpaRepository { +public interface CustomerRepository extends JpaRepository { @Query(value = "SELECT c.id as customerId, c.name as customerName, " + "SUM(o.total_amount) as totalSpending " diff --git a/src/main/java/com/Podzilla/analytics/repositories/InventorySnapshotRepository.java b/src/main/java/com/Podzilla/analytics/repositories/InventorySnapshotRepository.java index 219a3fc..dbd30c1 100644 --- a/src/main/java/com/Podzilla/analytics/repositories/InventorySnapshotRepository.java +++ b/src/main/java/com/Podzilla/analytics/repositories/InventorySnapshotRepository.java @@ -11,10 +11,11 @@ import com.Podzilla.analytics.api.projections.inventory.InventoryValueByCategoryProjection; import com.Podzilla.analytics.api.projections.inventory.LowStockProductProjection; import com.Podzilla.analytics.models.InventorySnapshot; +import java.util.UUID; @Repository public interface InventorySnapshotRepository - extends JpaRepository { + extends JpaRepository { @Query(value = "SELECT p.category as category, " + "SUM(s.quantity * p.cost) as totalStockValue " diff --git a/src/main/java/com/Podzilla/analytics/repositories/OrderRepository.java b/src/main/java/com/Podzilla/analytics/repositories/OrderRepository.java index ef7b506..7ee3630 100644 --- a/src/main/java/com/Podzilla/analytics/repositories/OrderRepository.java +++ b/src/main/java/com/Podzilla/analytics/repositories/OrderRepository.java @@ -16,8 +16,9 @@ import com.Podzilla.analytics.api.projections.revenue.RevenueByCategoryProjection; import com.Podzilla.analytics.api.projections.revenue.RevenueSummaryProjection; import com.Podzilla.analytics.models.Order; +import java.util.UUID; -public interface OrderRepository extends JpaRepository { +public interface OrderRepository extends JpaRepository { @Query(value = "SELECT 'OVERALL' as groupByValue, " + "AVG(EXTRACT(EPOCH FROM (o.shipped_timestamp - " diff --git a/src/main/java/com/Podzilla/analytics/repositories/ProductRepository.java b/src/main/java/com/Podzilla/analytics/repositories/ProductRepository.java index 425e6c8..4d15304 100644 --- a/src/main/java/com/Podzilla/analytics/repositories/ProductRepository.java +++ b/src/main/java/com/Podzilla/analytics/repositories/ProductRepository.java @@ -9,8 +9,8 @@ import com.Podzilla.analytics.api.projections.product.TopSellingProductProjection; import com.Podzilla.analytics.models.Product; - -public interface ProductRepository extends JpaRepository { +import java.util.UUID; +public interface ProductRepository extends JpaRepository { // Query to find top-selling products by revenue or units @Query(value = "SELECT " diff --git a/src/main/java/com/Podzilla/analytics/repositories/RegionRepository.java b/src/main/java/com/Podzilla/analytics/repositories/RegionRepository.java index 64a5c44..5aa30d8 100644 --- a/src/main/java/com/Podzilla/analytics/repositories/RegionRepository.java +++ b/src/main/java/com/Podzilla/analytics/repositories/RegionRepository.java @@ -3,6 +3,7 @@ import org.springframework.data.jpa.repository.JpaRepository; import com.Podzilla.analytics.models.Region; +import java.util.UUID; -public interface RegionRepository extends JpaRepository { +public interface RegionRepository extends JpaRepository { } diff --git a/src/main/java/com/Podzilla/analytics/repositories/SalesLineItemRepository.java b/src/main/java/com/Podzilla/analytics/repositories/SalesLineItemRepository.java index b4b9ac8..d9bcd5c 100644 --- a/src/main/java/com/Podzilla/analytics/repositories/SalesLineItemRepository.java +++ b/src/main/java/com/Podzilla/analytics/repositories/SalesLineItemRepository.java @@ -9,9 +9,10 @@ import java.time.LocalDateTime; import java.util.List; +import java.util.UUID; public interface SalesLineItemRepository - extends JpaRepository { + extends JpaRepository { @Query("SELECT sli.product.category as category, " + "SUM(sli.quantity * sli.pricePerUnit) as totalRevenue, " + "SUM(sli.quantity * sli.product.cost) as totalCost " diff --git a/src/main/java/com/Podzilla/analytics/services/CourierAnalyticsService.java b/src/main/java/com/Podzilla/analytics/services/CourierAnalyticsService.java index 7da075b..558a7b7 100644 --- a/src/main/java/com/Podzilla/analytics/services/CourierAnalyticsService.java +++ b/src/main/java/com/Podzilla/analytics/services/CourierAnalyticsService.java @@ -4,6 +4,7 @@ import java.time.LocalDateTime; import java.time.LocalTime; import java.util.List; +import java.util.UUID; import org.springframework.stereotype.Service; @@ -12,8 +13,11 @@ import com.Podzilla.analytics.api.dtos.courier.CourierPerformanceReportResponse; import com.Podzilla.analytics.api.dtos.courier.CourierSuccessRateResponse; import com.Podzilla.analytics.api.projections.courier.CourierPerformanceProjection; +import com.Podzilla.analytics.models.Courier; +import com.Podzilla.analytics.models.Courier.CourierStatus; import com.Podzilla.analytics.repositories.CourierRepository; import com.Podzilla.analytics.util.MetricCalculator; +import com.Podzilla.analytics.util.StringToUUIDParser; import lombok.RequiredArgsConstructor; @@ -22,10 +26,10 @@ public class CourierAnalyticsService { private final CourierRepository courierRepository; - private List getCourierPerformanceData( + private List getCourierPerformanceData( final LocalDate startDate, final LocalDate endDate -) { + ) { LocalDateTime startDateTime = startDate.atStartOfDay(); LocalDateTime endDateTime = endDate.atTime(LocalTime.MAX); @@ -35,59 +39,77 @@ private List getCourierPerformanceData( ); } - public List getCourierDeliveryCounts( + public List getCourierDeliveryCounts( final LocalDate startDate, - final LocalDate endDate) { - return getCourierPerformanceData(startDate, endDate).stream() - .map(data -> CourierDeliveryCountResponse.builder() - .courierId(data.getCourierId()) - .courierName(data.getCourierName()) - .deliveryCount(data.getDeliveryCount()) - .build()) - .toList(); - } + final LocalDate endDate + ) { + return getCourierPerformanceData(startDate, endDate).stream() + .map(data -> CourierDeliveryCountResponse.builder() + .courierId(data.getCourierId()) + .courierName(data.getCourierName()) + .deliveryCount(data.getDeliveryCount()) + .build()) + .toList(); + } - public List getCourierSuccessRate( + public List getCourierSuccessRate( final LocalDate startDate, - final LocalDate endDate) { - return getCourierPerformanceData(startDate, endDate).stream() - .map(data -> CourierSuccessRateResponse.builder() - .courierId(data.getCourierId()) - .courierName(data.getCourierName()) - .successRate( - MetricCalculator.calculateRate( - data.getCompletedCount(), - data.getDeliveryCount())) - .build()) - .toList(); - } + final LocalDate endDate + ) { + return getCourierPerformanceData(startDate, endDate).stream() + .map(data -> CourierSuccessRateResponse.builder() + .courierId(data.getCourierId()) + .courierName(data.getCourierName()) + .successRate( + MetricCalculator.calculateRate( + data.getCompletedCount(), + data.getDeliveryCount())) + .build()) + .toList(); + } - public List getCourierAverageRating( + public List getCourierAverageRating( final LocalDate startDate, - final LocalDate endDate) { - return getCourierPerformanceData(startDate, endDate).stream() - .map(data -> CourierAverageRatingResponse.builder() - .courierId(data.getCourierId()) - .courierName(data.getCourierName()) - .averageRating(data.getAverageRating()) - .build()) - .toList(); - } + final LocalDate endDate + ) { + return getCourierPerformanceData(startDate, endDate).stream() + .map(data -> CourierAverageRatingResponse.builder() + .courierId(data.getCourierId()) + .courierName(data.getCourierName()) + .averageRating(data.getAverageRating()) + .build()) + .toList(); + } - public List getCourierPerformanceReport( + public List + getCourierPerformanceReport( final LocalDate startDate, - final LocalDate endDate) { - return getCourierPerformanceData(startDate, endDate).stream() - .map(data -> CourierPerformanceReportResponse.builder() - .courierId(data.getCourierId()) - .courierName(data.getCourierName()) - .deliveryCount(data.getDeliveryCount()) - .successRate( - MetricCalculator.calculateRate( - data.getCompletedCount(), - data.getDeliveryCount())) - .averageRating(data.getAverageRating()) - .build()) - .toList(); + final LocalDate endDate + ) { + return getCourierPerformanceData(startDate, endDate).stream() + .map(data -> CourierPerformanceReportResponse.builder() + .courierId(data.getCourierId()) + .courierName(data.getCourierName()) + .deliveryCount(data.getDeliveryCount()) + .successRate( + MetricCalculator.calculateRate( + data.getCompletedCount(), + data.getDeliveryCount())) + .averageRating(data.getAverageRating()) + .build()) + .toList(); } + + public void saveCourier( + final String courierId, + final String courierName + ) { + UUID id = StringToUUIDParser.parseStringToUUID(courierId); + Courier courier = Courier.builder() + .id(id) + .name(courierName) + .status(CourierStatus.ACTIVE) + .build(); + courierRepository.save(courier); + } } diff --git a/src/main/java/com/Podzilla/analytics/services/InventoryAnalyticsService.java b/src/main/java/com/Podzilla/analytics/services/InventoryAnalyticsService.java index 7bcefa3..e684739 100644 --- a/src/main/java/com/Podzilla/analytics/services/InventoryAnalyticsService.java +++ b/src/main/java/com/Podzilla/analytics/services/InventoryAnalyticsService.java @@ -6,15 +6,24 @@ import com.Podzilla.analytics.api.dtos.inventory.InventoryValueByCategoryResponse; import com.Podzilla.analytics.api.dtos.inventory.LowStockProductResponse; +import com.Podzilla.analytics.models.InventorySnapshot; import com.Podzilla.analytics.repositories.InventorySnapshotRepository; +import com.Podzilla.analytics.repositories.ProductRepository; +import com.Podzilla.analytics.models.Product; +import com.Podzilla.analytics.util.StringToUUIDParser; +import com.Podzilla.analytics.util.DatetimeFormatter; import java.util.List; +import java.util.UUID; +import java.time.Instant; +import java.time.LocalDateTime; import lombok.RequiredArgsConstructor; @Service @RequiredArgsConstructor public class InventoryAnalyticsService { private final InventorySnapshotRepository inventoryRepo; + private final ProductRepository productRepository; public List getInventoryValueByCategory() { List invVByCy = inventoryRepo @@ -41,4 +50,25 @@ public Page getLowStockProducts(final int page, .build()); return lowStockPro; } + + public void saveInventorySnapshot( + final String productId, + final Integer quantity, + final Instant timestamp + ) { + UUID productUUID = StringToUUIDParser.parseStringToUUID(productId); + Product product = productRepository.findById(productUUID) + .orElseThrow( + () -> new IllegalArgumentException("Product not found") + ); + LocalDateTime snapshotTimestamp = DatetimeFormatter + .convertIntsantToDateTime(timestamp); + InventorySnapshot inventorySnapshot = InventorySnapshot.builder() + .product(product) + .quantity(quantity) + .timestamp(snapshotTimestamp) + .build(); + + inventoryRepo.save(inventorySnapshot); + } } diff --git a/src/main/java/com/Podzilla/analytics/services/ProductAnalyticsService.java b/src/main/java/com/Podzilla/analytics/services/ProductAnalyticsService.java index da64c69..350f005 100644 --- a/src/main/java/com/Podzilla/analytics/services/ProductAnalyticsService.java +++ b/src/main/java/com/Podzilla/analytics/services/ProductAnalyticsService.java @@ -13,6 +13,9 @@ import com.Podzilla.analytics.api.dtos.product.TopSellerResponse; import com.Podzilla.analytics.repositories.ProductRepository; import lombok.RequiredArgsConstructor; +import com.Podzilla.analytics.models.Product; +import com.Podzilla.analytics.util.StringToUUIDParser; +import java.util.UUID; @RequiredArgsConstructor @Service @@ -73,4 +76,22 @@ public List getTopSellers( return topSellersList; } + + public void saveProduct( + final String productId, + final String productName, + final String productCategory, + final BigDecimal productCost, + final Integer productLowStockThreshold + ) { + UUID id = StringToUUIDParser.parseStringToUUID(productId); + Product product = Product.builder() + .id(id) + .name(productName) + .category(productCategory) + .cost(productCost) + .lowStockThreshold(productLowStockThreshold) + .build(); + productRepository.save(product); + } } diff --git a/src/main/java/com/Podzilla/analytics/util/DatetimeFormatter.java b/src/main/java/com/Podzilla/analytics/util/DatetimeFormatter.java index 8a3c110..83b7f67 100644 --- a/src/main/java/com/Podzilla/analytics/util/DatetimeFormatter.java +++ b/src/main/java/com/Podzilla/analytics/util/DatetimeFormatter.java @@ -3,6 +3,8 @@ import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; +import java.time.Instant; +import java.time.ZoneId; public class DatetimeFormatter { public static LocalDateTime convertStartDateToDatetime( @@ -15,4 +17,12 @@ public static LocalDateTime convertEndDateToDatetime( ) { return endDate.atTime(LocalTime.MAX); } + public static LocalDateTime convertIntsantToDateTime( + final Instant timestamp + ) { + return LocalDateTime.ofInstant( + timestamp, + ZoneId.systemDefault() + ); + } } From 23651df24df117a2f415e0ced4e973b8bbd9f92c Mon Sep 17 00:00:00 2001 From: Mohamed Hassan Date: Sun, 18 May 2025 06:30:08 +0300 Subject: [PATCH 06/10] refactor: update entity IDs to UUID and enhance database seeding --- .../analytics/config/DatabaseSeeder.java | 8 - .../analytics/models/InventorySnapshot.java | 2 + .../analytics/models/SalesLineItem.java | 2 + .../repositories/CourierRepository.java | 17 +- .../repositories/SalesLineItemRepository.java | 3 +- .../CourierAnalyticsControllerTest.java | 664 +++++++++--------- .../ProfitReportControllerTest.java | 565 +++++++-------- .../resources/application-test.properties | 14 +- src/test/resources/application.properties | 26 +- 9 files changed, 650 insertions(+), 651 deletions(-) diff --git a/src/main/java/com/Podzilla/analytics/config/DatabaseSeeder.java b/src/main/java/com/Podzilla/analytics/config/DatabaseSeeder.java index 9fed9ad..4a3cf3c 100644 --- a/src/main/java/com/Podzilla/analytics/config/DatabaseSeeder.java +++ b/src/main/java/com/Podzilla/analytics/config/DatabaseSeeder.java @@ -237,11 +237,9 @@ private void seedOrders( .courierRating(RATING_GOOD) .build(); SalesLineItem itemFirstOrderFirst = SalesLineItem.builder() - .id(UUID.randomUUID()) .order(order1).product(products.get(0)).quantity(1) .pricePerUnit(PRICE_PROD1).build(); SalesLineItem itemFirstOrderSecond = SalesLineItem.builder() - .id(UUID.randomUUID()) .order(order1).product(products.get(2)).quantity(2) .pricePerUnit(PRICE_PROD3).build(); order1.setSalesLineItems(Arrays.asList(itemFirstOrderFirst, @@ -272,7 +270,6 @@ private void seedOrders( .courierRating(null).failureReason(null) .build(); SalesLineItem itemSecondOrderFirst = SalesLineItem.builder() - .id(UUID.randomUUID()) .order(order2).product(products.get(1)).quantity(1) .pricePerUnit(PRICE_PROD2).build(); order2.setSalesLineItems(List.of(itemSecondOrderFirst)); @@ -301,7 +298,6 @@ private void seedOrders( .courierRating(RATING_POOR) .build(); SalesLineItem itemThirdOrderFirst = SalesLineItem.builder() - .id(UUID.randomUUID()) .order(order3).product(products.get(INDEX_THREE)).quantity(1) .pricePerUnit(PRICE_PROD4).build(); order3.setSalesLineItems(List.of(itemThirdOrderFirst)); @@ -328,11 +324,9 @@ private void seedOrders( .courierRating(RATING_EXCELLENT) .build(); SalesLineItem itemFourthOrderFirst = SalesLineItem.builder() - .id(UUID.randomUUID()) .order(order4).product(products.get(0)).quantity(1) .pricePerUnit(PRICE_PROD1).build(); SalesLineItem itemFourthOrderSecond = SalesLineItem.builder() - .id(UUID.randomUUID()) .order(order4).product(products.get(INDEX_THREE)).quantity(1) .pricePerUnit(PRICE_PROD4).build(); order4.setSalesLineItems(Arrays.asList(itemFourthOrderFirst, @@ -364,7 +358,6 @@ private void seedInventorySnapshot( final Product product, final int range, final int quantity) { inventorySnapshotRepository.save( InventorySnapshot.builder() - .id(UUID.randomUUID()) .product(product) .quantity(random.nextInt(range) + product.getLowStockThreshold()) @@ -373,7 +366,6 @@ private void seedInventorySnapshot( .build()); inventorySnapshotRepository.save( InventorySnapshot.builder() - .id(UUID.randomUUID()) .product(product) .quantity(random.nextInt(quantity) + product.getLowStockThreshold()) diff --git a/src/main/java/com/Podzilla/analytics/models/InventorySnapshot.java b/src/main/java/com/Podzilla/analytics/models/InventorySnapshot.java index 3546f72..0f272db 100644 --- a/src/main/java/com/Podzilla/analytics/models/InventorySnapshot.java +++ b/src/main/java/com/Podzilla/analytics/models/InventorySnapshot.java @@ -2,6 +2,7 @@ import java.time.LocalDateTime; import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; @@ -20,6 +21,7 @@ @AllArgsConstructor public class InventorySnapshot { @Id + @GeneratedValue(generator = "uuid") private UUID id; private LocalDateTime timestamp; diff --git a/src/main/java/com/Podzilla/analytics/models/SalesLineItem.java b/src/main/java/com/Podzilla/analytics/models/SalesLineItem.java index e7efea8..f93f7a3 100644 --- a/src/main/java/com/Podzilla/analytics/models/SalesLineItem.java +++ b/src/main/java/com/Podzilla/analytics/models/SalesLineItem.java @@ -2,6 +2,7 @@ import java.math.BigDecimal; import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; @@ -20,6 +21,7 @@ @AllArgsConstructor public class SalesLineItem { @Id + @GeneratedValue(generator = "uuid") private UUID id; private int quantity; diff --git a/src/main/java/com/Podzilla/analytics/repositories/CourierRepository.java b/src/main/java/com/Podzilla/analytics/repositories/CourierRepository.java index eae7c5e..bee39da 100644 --- a/src/main/java/com/Podzilla/analytics/repositories/CourierRepository.java +++ b/src/main/java/com/Podzilla/analytics/repositories/CourierRepository.java @@ -2,6 +2,7 @@ import java.time.LocalDateTime; import java.util.List; +import java.util.UUID; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; @@ -10,21 +11,21 @@ import com.Podzilla.analytics.api.projections.courier.CourierPerformanceProjection; import com.Podzilla.analytics.models.Courier; -public interface CourierRepository extends JpaRepository { +public interface CourierRepository extends JpaRepository { - @Query(value = "SELECT c.id AS courierId, " + @Query("SELECT c.id AS courierId, " + "c.name AS courierName, " + "COUNT(o.id) AS deliveryCount, " + "SUM(CASE WHEN o.status = 'COMPLETED' THEN 1 ELSE 0 END) " + "AS completedCount, " - + "AVG(CASE WHEN o.status = 'COMPLETED' THEN o.courier_rating " + + "AVG(CASE WHEN o.status = 'COMPLETED' THEN o.courierRating " + "ELSE NULL END) AS averageRating " - + "FROM couriers c " - + "LEFT JOIN orders o " - + "ON c.id = o.courier_id " - + "AND o.final_status_timestamp BETWEEN :startDate AND :endDate " + + "FROM Courier c " + + "LEFT JOIN Order o " + + "ON c.id = o.courier.id " + + "AND o.finalStatusTimestamp BETWEEN :startDate AND :endDate " + "GROUP BY c.id, c.name " - + "ORDER BY courierId", nativeQuery = true) + + "ORDER BY c.id") List findCourierPerformanceBetweenDates( @Param("startDate") LocalDateTime startDate, @Param("endDate") LocalDateTime endDate); diff --git a/src/main/java/com/Podzilla/analytics/repositories/SalesLineItemRepository.java b/src/main/java/com/Podzilla/analytics/repositories/SalesLineItemRepository.java index b4b9ac8..d9bcd5c 100644 --- a/src/main/java/com/Podzilla/analytics/repositories/SalesLineItemRepository.java +++ b/src/main/java/com/Podzilla/analytics/repositories/SalesLineItemRepository.java @@ -9,9 +9,10 @@ import java.time.LocalDateTime; import java.util.List; +import java.util.UUID; public interface SalesLineItemRepository - extends JpaRepository { + extends JpaRepository { @Query("SELECT sli.product.category as category, " + "SUM(sli.quantity * sli.pricePerUnit) as totalRevenue, " + "SUM(sli.quantity * sli.product.cost) as totalCost " diff --git a/src/test/java/com/Podzilla/analytics/controllers/CourierAnalyticsControllerTest.java b/src/test/java/com/Podzilla/analytics/controllers/CourierAnalyticsControllerTest.java index 8cb17a8..5de69a6 100644 --- a/src/test/java/com/Podzilla/analytics/controllers/CourierAnalyticsControllerTest.java +++ b/src/test/java/com/Podzilla/analytics/controllers/CourierAnalyticsControllerTest.java @@ -1,332 +1,332 @@ -// package com.Podzilla.analytics.controllers; - -// import java.math.BigDecimal; -// import java.time.LocalDate; -// import java.time.LocalDateTime; -// import java.time.format.DateTimeFormatter; -// import java.util.UUID; - -// import jakarta.persistence.EntityManager; - -// import org.junit.jupiter.api.AfterEach; -// import org.junit.jupiter.api.BeforeEach; -// import org.junit.jupiter.api.Test; -// import org.springframework.beans.factory.annotation.Autowired; -// import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -// import org.springframework.boot.test.context.SpringBootTest; -// import org.springframework.test.context.ActiveProfiles; -// import org.springframework.test.web.servlet.MockMvc; -// import org.springframework.test.web.servlet.result.MockMvcResultHandlers; -// import org.springframework.transaction.annotation.Transactional; - -// import com.Podzilla.analytics.models.Courier; -// import com.Podzilla.analytics.models.Customer; -// import com.Podzilla.analytics.models.Order; -// import com.Podzilla.analytics.models.Region; -// import com.Podzilla.analytics.repositories.CourierRepository; -// import com.Podzilla.analytics.repositories.CustomerRepository; -// import com.Podzilla.analytics.repositories.OrderRepository; -// import com.Podzilla.analytics.repositories.RegionRepository; - -// import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -// import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; -// import static org.hamcrest.Matchers.*; - -// @SpringBootTest -// @AutoConfigureMockMvc -// @Transactional -// @ActiveProfiles("test") -// public class CourierAnalyticsControllerTest { - -// @Autowired -// private MockMvc mockMvc; - -// @Autowired -// private CourierRepository courierRepository; -// @Autowired -// private CustomerRepository customerRepository; -// @Autowired -// private RegionRepository regionRepository; -// @Autowired -// private OrderRepository orderRepository; - -// @Autowired -// private EntityManager entityManager; - -// private static final DateTimeFormatter ISO_LOCAL_DATE = DateTimeFormatter.ISO_LOCAL_DATE; - -// private Customer customer1; -// private Region region1; -// private Courier courierJane; -// private Courier courierJohn; - -// @BeforeEach -// void setUp() { -// orderRepository.deleteAll(); -// courierRepository.deleteAll(); -// customerRepository.deleteAll(); -// regionRepository.deleteAll(); - -// entityManager.flush(); -// entityManager.clear(); - -// customer1 = customerRepository.save(Customer.builder() -// .id(UUID.randomUUID()) -// .name("John Doe").build()); -// region1 = regionRepository.save(Region.builder() -// .id(UUID.randomUUID()) -// .city("Sample City") -// .state("Sample State") -// .country("Sample Country") -// .postalCode("12345") -// .build()); - -// courierJane = courierRepository.save(Courier.builder() -// .id(UUID.randomUUID()) -// .name("Jane Smith") -// .status(Courier.CourierStatus.ACTIVE) -// .build()); - -// courierJohn = courierRepository.save(Courier.builder() -// .id(UUID.randomUUID()) -// .name("John Doe") -// .status(Courier.CourierStatus.ACTIVE) -// .build()); - -// orderRepository.save(Order.builder() -// .id(UUID.randomUUID()) -// .totalAmount(new BigDecimal("50.00")) -// .finalStatusTimestamp(LocalDateTime.now().minusDays(3)) -// .status(Order.OrderStatus.COMPLETED) -// .numberOfItems(1) -// .courierRating(new BigDecimal("4.0")) -// .customer(customer1) -// .courier(courierJane) -// .region(region1) -// .build()); - -// orderRepository.save(Order.builder() -// .id(UUID.randomUUID()) -// .totalAmount(new BigDecimal("75.00")) -// .finalStatusTimestamp(LocalDateTime.now().minusDays(3)) -// .status(Order.OrderStatus.COMPLETED) -// .numberOfItems(1) -// .courierRating(new BigDecimal("4.0")) -// .customer(customer1) -// .courier(courierJane) -// .region(region1) -// .build()); - -// orderRepository.save(Order.builder() -// .id(UUID.randomUUID()) -// .totalAmount(new BigDecimal("120.00")) -// .finalStatusTimestamp(LocalDateTime.now().minusDays(1)) -// .status(Order.OrderStatus.COMPLETED) -// .numberOfItems(2) -// .courierRating(new BigDecimal("5.0")) -// .customer(customer1) -// .courier(courierJane) -// .region(region1) -// .build()); - -// orderRepository.save(Order.builder() -// .id(UUID.randomUUID()) -// .totalAmount(new BigDecimal("30.00")) -// .finalStatusTimestamp(LocalDateTime.now().minusDays(2)) -// .status(Order.OrderStatus.FAILED) -// .numberOfItems(1) -// .courierRating(null) -// .customer(customer1) -// .courier(courierJohn) -// .region(region1) -// .build()); - -// orderRepository.save(Order.builder() -// .id(UUID.randomUUID()) -// .totalAmount(new BigDecimal("90.00")) -// .finalStatusTimestamp(LocalDateTime.now().minusDays(2)) -// .status(Order.OrderStatus.COMPLETED) -// .numberOfItems(1) -// .courierRating(new BigDecimal("3.0")) -// .customer(customer1) -// .courier(courierJohn) -// .region(region1) -// .build()); - -// entityManager.flush(); -// entityManager.clear(); -// } - -// @AfterEach -// void tearDown() { -// orderRepository.deleteAll(); -// courierRepository.deleteAll(); -// customerRepository.deleteAll(); -// regionRepository.deleteAll(); -// entityManager.flush(); -// entityManager.clear(); -// } - -// @Test -// void contextLoads() { -// } - -// @Test -// void getCourierDeliveryCounts_shouldReturnCountsForSpecificDateRange() throws Exception { -// LocalDate startDate = LocalDate.now().minusDays(4); -// LocalDate endDate = LocalDate.now().minusDays(2); - -// mockMvc.perform(get("/courier-analytics/delivery-counts") -// .param("startDate", startDate.format( -// ISO_LOCAL_DATE)) -// .param("endDate", endDate.format( -// ISO_LOCAL_DATE))) -// .andDo(MockMvcResultHandlers.print()) -// .andExpect(status().isOk()) -// .andExpect(jsonPath("$", hasSize(2))) -// .andExpect(jsonPath("$[?(@.courierName == 'Jane Smith')].deliveryCount").value(hasItem(2))) -// .andExpect(jsonPath("$[?(@.courierName == 'John Doe')].deliveryCount").value(hasItem(2))); -// } - -// @Test -// void getCourierDeliveryCounts_shouldReturnZeroCountsWhenNoDeliveriesInDateRange() -// throws Exception { -// LocalDate startDate = LocalDate.now().plusDays(1); -// LocalDate endDate = LocalDate.now().plusDays(2); - -// mockMvc.perform(get("/courier-analytics/delivery-counts") -// .param("startDate", startDate.format(ISO_LOCAL_DATE)) -// .param("endDate", endDate.format(ISO_LOCAL_DATE))) -// .andDo(MockMvcResultHandlers.print()) -// .andExpect(status().isOk()) -// .andExpect(jsonPath("$", hasSize(2))) -// .andExpect(jsonPath("$[?(@.courierName == 'Jane Smith')].courierName").value(hasItem("Jane Smith"))) -// .andExpect(jsonPath("$[?(@.courierName == 'Jane Smith')].deliveryCount").value(hasItem(0))) -// .andExpect(jsonPath("$[?(@.courierName == 'John Doe')].courierName").value(hasItem("John Doe"))) -// .andExpect(jsonPath("$[?(@.courierName == 'John Doe')].deliveryCount").value(hasItem(0))); -// } - -// @Test -// void getCourierDeliveryCounts_shouldHandleInvalidDateRange_startDateAfterEndDate() -// throws Exception { -// LocalDateTime startDate = LocalDateTime.now().minusDays(1); -// LocalDateTime endDate = LocalDateTime.now().minusDays(3); - -// mockMvc.perform(get("/courier-analytics/delivery-counts") -// .param("startDate", startDate.format( -// ISO_LOCAL_DATE)) -// .param("endDate", endDate.format( -// ISO_LOCAL_DATE))) -// .andDo(MockMvcResultHandlers.print()) -// .andExpect(status().isBadRequest()); -// } - -// @Test -// void getCourierSuccessRate_shouldReturnSuccessRatesForSpecificDateRange() -// throws Exception { -// LocalDateTime startDate = LocalDateTime.now().minusDays(4); -// LocalDateTime endDate = LocalDateTime.now().minusDays(2); - -// mockMvc.perform(get("/courier-analytics/success-rate") -// .param("startDate", startDate.format( -// ISO_LOCAL_DATE)) -// .param("endDate", endDate.format( -// ISO_LOCAL_DATE))) -// .andDo(MockMvcResultHandlers.print()) -// .andExpect(status().isOk()) -// .andExpect(jsonPath("$", hasSize(2))) -// .andExpect( -// jsonPath("$[?(@.courierName == 'Jane Smith')].successRate").value(hasItem(closeTo(1.0, 0.001)))) -// .andExpect( -// jsonPath("$[?(@.courierName == 'John Doe')].successRate").value(hasItem(closeTo(0.5, 0.001)))); -// } - -// @Test -// void getCourierAverageRating_shouldReturnAllAverageRatingsWhenNoDateRangeProvided() -// throws Exception { -// LocalDateTime startDate = LocalDateTime.now().minusDays(4); -// LocalDateTime endDate = LocalDateTime.now(); -// mockMvc.perform(get("/courier-analytics/average-rating") -// .param("startDate", startDate.format( -// ISO_LOCAL_DATE)) -// .param("endDate", endDate.format( -// ISO_LOCAL_DATE))) -// .andDo(MockMvcResultHandlers.print()) -// .andExpect(status().isOk()) -// .andExpect(jsonPath("$", hasSize(2))) -// .andExpect(jsonPath("$[?(@.courierName == 'Jane Smith')].averageRating") -// .value(hasItem(closeTo(4.333, 0.001)))) -// .andExpect(jsonPath("$[?(@.courierName == 'John Doe')].averageRating") -// .value(hasItem(closeTo(3.0, 0.001)))); -// } - -// @Test -// void getCourierAverageRating_shouldReturnAverageRatingsForSpecificDateRange() -// throws Exception { - -// LocalDateTime startDate = LocalDateTime.now().minusDays(4); -// LocalDateTime endDate = LocalDateTime.now().minusDays(2); - -// mockMvc.perform(get("/courier-analytics/average-rating") -// .param("startDate", startDate.format(ISO_LOCAL_DATE)) -// .param("endDate", endDate.format(ISO_LOCAL_DATE))) -// .andDo(MockMvcResultHandlers.print()) -// .andExpect(status().isOk()) -// .andExpect(jsonPath("$", hasSize(2))) -// .andExpect(jsonPath("$[?(@.courierName == 'Jane Smith')].averageRating") -// .value(hasItem(closeTo(4.0, 0.001)))) -// .andExpect(jsonPath("$[?(@.courierName == 'John Doe')].averageRating") -// .value(hasItem(closeTo(3.0, 0.001)))); -// } - -// @Test -// void getCourierPerformanceReport_shouldReturnAllPerformanceReportsWhenNoDateRangeProvided() -// throws Exception { -// LocalDateTime startDate = LocalDateTime.now().minusDays(4); -// LocalDateTime endDate = LocalDateTime.now(); -// mockMvc.perform(get("/courier-analytics/performance-report") -// .param("startDate", startDate.format( -// ISO_LOCAL_DATE)) -// .param("endDate", endDate.format( -// ISO_LOCAL_DATE))) -// .andDo(MockMvcResultHandlers.print()) -// .andExpect(status().isOk()) -// .andExpect(jsonPath("$", hasSize(2))) -// .andExpect(jsonPath("$[?(@.courierName == 'Jane Smith')].deliveryCount").value(hasItem(3))) -// .andExpect(jsonPath("$[?(@.courierName == 'Jane Smith')].successRate") -// .value(hasItem(closeTo(1.0, 0.001)))) -// .andExpect(jsonPath("$[?(@.courierName == 'Jane Smith')].averageRating") -// .value(hasItem(closeTo(4.333, 0.001)))) - -// .andExpect(jsonPath("$[?(@.courierName == 'John Doe')].deliveryCount").value(hasItem(2))) -// .andExpect(jsonPath("$[?(@.courierName == 'John Doe')].successRate") -// .value(hasItem(closeTo(0.5, 0.001)))) -// .andExpect(jsonPath("$[?(@.courierName == 'John Doe')].averageRating") -// .value(hasItem(closeTo(3.0, 0.001)))); -// } - -// @Test -// void getCourierPerformanceReport_shouldReturnPerformanceReportsForSpecificDateRange() -// throws Exception { -// LocalDateTime startDate = LocalDateTime.now().minusDays(4); -// LocalDateTime endDate = LocalDateTime.now().minusDays(2); - -// mockMvc.perform(get("/courier-analytics/performance-report") -// .param("startDate", startDate.format(ISO_LOCAL_DATE)) -// .param("endDate", endDate.format(ISO_LOCAL_DATE))) -// .andDo(MockMvcResultHandlers.print()) -// .andExpect(status().isOk()) -// .andExpect(jsonPath("$", hasSize(2))) -// .andExpect(jsonPath("$[?(@.courierName == 'Jane Smith')].deliveryCount").value(hasItem(2))) -// .andExpect(jsonPath("$[?(@.courierName == 'Jane Smith')].successRate") -// .value(hasItem(closeTo(1.0, 0.001)))) -// .andExpect(jsonPath("$[?(@.courierName == 'Jane Smith')].averageRating") -// .value(hasItem(closeTo(4.0, 0.001)))) - -// .andExpect(jsonPath("$[?(@.courierName == 'John Doe')].deliveryCount").value(hasItem(2))) -// .andExpect(jsonPath("$[?(@.courierName == 'John Doe')].successRate") -// .value(hasItem(closeTo(0.5, 0.001)))) -// .andExpect(jsonPath("$[?(@.courierName == 'John Doe')].averageRating") -// .value(hasItem(closeTo(3.0, 0.001)))); -// } -// } +package com.Podzilla.analytics.controllers; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.UUID; + +import jakarta.persistence.EntityManager; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.result.MockMvcResultHandlers; +import org.springframework.transaction.annotation.Transactional; + +import com.Podzilla.analytics.models.Courier; +import com.Podzilla.analytics.models.Customer; +import com.Podzilla.analytics.models.Order; +import com.Podzilla.analytics.models.Region; +import com.Podzilla.analytics.repositories.CourierRepository; +import com.Podzilla.analytics.repositories.CustomerRepository; +import com.Podzilla.analytics.repositories.OrderRepository; +import com.Podzilla.analytics.repositories.RegionRepository; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import static org.hamcrest.Matchers.*; + +@SpringBootTest +@AutoConfigureMockMvc +@Transactional +@ActiveProfiles("test") +public class CourierAnalyticsControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private CourierRepository courierRepository; + @Autowired + private CustomerRepository customerRepository; + @Autowired + private RegionRepository regionRepository; + @Autowired + private OrderRepository orderRepository; + + @Autowired + private EntityManager entityManager; + + private static final DateTimeFormatter ISO_LOCAL_DATE = DateTimeFormatter.ISO_LOCAL_DATE; + + private Customer customer1; + private Region region1; + private Courier courierJane; + private Courier courierJohn; + + @BeforeEach + void setUp() { + orderRepository.deleteAll(); + courierRepository.deleteAll(); + customerRepository.deleteAll(); + regionRepository.deleteAll(); + + entityManager.flush(); + entityManager.clear(); + + customer1 = customerRepository.save(Customer.builder() + .id(UUID.randomUUID()) + .name("John Doe").build()); + region1 = regionRepository.save(Region.builder() + .id(UUID.randomUUID()) + .city("Sample City") + .state("Sample State") + .country("Sample Country") + .postalCode("12345") + .build()); + + courierJane = courierRepository.save(Courier.builder() + .id(UUID.randomUUID()) + .name("Jane Smith") + .status(Courier.CourierStatus.ACTIVE) + .build()); + + courierJohn = courierRepository.save(Courier.builder() + .id(UUID.randomUUID()) + .name("John Doe") + .status(Courier.CourierStatus.ACTIVE) + .build()); + + orderRepository.save(Order.builder() + .id(UUID.randomUUID()) + .totalAmount(new BigDecimal("50.00")) + .finalStatusTimestamp(LocalDateTime.now().minusDays(3)) + .status(Order.OrderStatus.COMPLETED) + .numberOfItems(1) + .courierRating(new BigDecimal("4.0")) + .customer(customer1) + .courier(courierJane) + .region(region1) + .build()); + + orderRepository.save(Order.builder() + .id(UUID.randomUUID()) + .totalAmount(new BigDecimal("75.00")) + .finalStatusTimestamp(LocalDateTime.now().minusDays(3)) + .status(Order.OrderStatus.COMPLETED) + .numberOfItems(1) + .courierRating(new BigDecimal("4.0")) + .customer(customer1) + .courier(courierJane) + .region(region1) + .build()); + + orderRepository.save(Order.builder() + .id(UUID.randomUUID()) + .totalAmount(new BigDecimal("120.00")) + .finalStatusTimestamp(LocalDateTime.now().minusDays(1)) + .status(Order.OrderStatus.COMPLETED) + .numberOfItems(2) + .courierRating(new BigDecimal("5.0")) + .customer(customer1) + .courier(courierJane) + .region(region1) + .build()); + + orderRepository.save(Order.builder() + .id(UUID.randomUUID()) + .totalAmount(new BigDecimal("30.00")) + .finalStatusTimestamp(LocalDateTime.now().minusDays(2)) + .status(Order.OrderStatus.FAILED) + .numberOfItems(1) + .courierRating(null) + .customer(customer1) + .courier(courierJohn) + .region(region1) + .build()); + + orderRepository.save(Order.builder() + .id(UUID.randomUUID()) + .totalAmount(new BigDecimal("90.00")) + .finalStatusTimestamp(LocalDateTime.now().minusDays(2)) + .status(Order.OrderStatus.COMPLETED) + .numberOfItems(1) + .courierRating(new BigDecimal("3.0")) + .customer(customer1) + .courier(courierJohn) + .region(region1) + .build()); + + entityManager.flush(); + entityManager.clear(); + } + + @AfterEach + void tearDown() { + orderRepository.deleteAll(); + courierRepository.deleteAll(); + customerRepository.deleteAll(); + regionRepository.deleteAll(); + entityManager.flush(); + entityManager.clear(); + } + + @Test + void contextLoads() { + } + + @Test + void getCourierDeliveryCounts_shouldReturnCountsForSpecificDateRange() throws Exception { + LocalDate startDate = LocalDate.now().minusDays(4); + LocalDate endDate = LocalDate.now().minusDays(2); + + mockMvc.perform(get("/courier-analytics/delivery-counts") + .param("startDate", startDate.format( + ISO_LOCAL_DATE)) + .param("endDate", endDate.format( + ISO_LOCAL_DATE))) + .andDo(MockMvcResultHandlers.print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasSize(2))) + .andExpect(jsonPath("$[?(@.courierName == 'Jane Smith')].deliveryCount").value(hasItem(2))) + .andExpect(jsonPath("$[?(@.courierName == 'John Doe')].deliveryCount").value(hasItem(2))); + } + + @Test + void getCourierDeliveryCounts_shouldReturnZeroCountsWhenNoDeliveriesInDateRange() + throws Exception { + LocalDate startDate = LocalDate.now().plusDays(1); + LocalDate endDate = LocalDate.now().plusDays(2); + + mockMvc.perform(get("/courier-analytics/delivery-counts") + .param("startDate", startDate.format(ISO_LOCAL_DATE)) + .param("endDate", endDate.format(ISO_LOCAL_DATE))) + .andDo(MockMvcResultHandlers.print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasSize(2))) + .andExpect(jsonPath("$[?(@.courierName == 'Jane Smith')].courierName").value(hasItem("Jane Smith"))) + .andExpect(jsonPath("$[?(@.courierName == 'Jane Smith')].deliveryCount").value(hasItem(0))) + .andExpect(jsonPath("$[?(@.courierName == 'John Doe')].courierName").value(hasItem("John Doe"))) + .andExpect(jsonPath("$[?(@.courierName == 'John Doe')].deliveryCount").value(hasItem(0))); + } + + @Test + void getCourierDeliveryCounts_shouldHandleInvalidDateRange_startDateAfterEndDate() + throws Exception { + LocalDateTime startDate = LocalDateTime.now().minusDays(1); + LocalDateTime endDate = LocalDateTime.now().minusDays(3); + + mockMvc.perform(get("/courier-analytics/delivery-counts") + .param("startDate", startDate.format( + ISO_LOCAL_DATE)) + .param("endDate", endDate.format( + ISO_LOCAL_DATE))) + .andDo(MockMvcResultHandlers.print()) + .andExpect(status().isBadRequest()); + } + + @Test + void getCourierSuccessRate_shouldReturnSuccessRatesForSpecificDateRange() + throws Exception { + LocalDateTime startDate = LocalDateTime.now().minusDays(4); + LocalDateTime endDate = LocalDateTime.now().minusDays(2); + + mockMvc.perform(get("/courier-analytics/success-rate") + .param("startDate", startDate.format( + ISO_LOCAL_DATE)) + .param("endDate", endDate.format( + ISO_LOCAL_DATE))) + .andDo(MockMvcResultHandlers.print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasSize(2))) + .andExpect( + jsonPath("$[?(@.courierName == 'Jane Smith')].successRate").value(hasItem(closeTo(1.0, 0.001)))) + .andExpect( + jsonPath("$[?(@.courierName == 'John Doe')].successRate").value(hasItem(closeTo(0.5, 0.001)))); + } + + @Test + void getCourierAverageRating_shouldReturnAllAverageRatingsWhenNoDateRangeProvided() + throws Exception { + LocalDateTime startDate = LocalDateTime.now().minusDays(4); + LocalDateTime endDate = LocalDateTime.now(); + mockMvc.perform(get("/courier-analytics/average-rating") + .param("startDate", startDate.format( + ISO_LOCAL_DATE)) + .param("endDate", endDate.format( + ISO_LOCAL_DATE))) + .andDo(MockMvcResultHandlers.print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasSize(2))) + .andExpect(jsonPath("$[?(@.courierName == 'Jane Smith')].averageRating") + .value(hasItem(closeTo(4.333, 0.001)))) + .andExpect(jsonPath("$[?(@.courierName == 'John Doe')].averageRating") + .value(hasItem(closeTo(3.0, 0.001)))); + } + + @Test + void getCourierAverageRating_shouldReturnAverageRatingsForSpecificDateRange() + throws Exception { + + LocalDateTime startDate = LocalDateTime.now().minusDays(4); + LocalDateTime endDate = LocalDateTime.now().minusDays(2); + + mockMvc.perform(get("/courier-analytics/average-rating") + .param("startDate", startDate.format(ISO_LOCAL_DATE)) + .param("endDate", endDate.format(ISO_LOCAL_DATE))) + .andDo(MockMvcResultHandlers.print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasSize(2))) + .andExpect(jsonPath("$[?(@.courierName == 'Jane Smith')].averageRating") + .value(hasItem(closeTo(4.0, 0.001)))) + .andExpect(jsonPath("$[?(@.courierName == 'John Doe')].averageRating") + .value(hasItem(closeTo(3.0, 0.001)))); + } + + @Test + void getCourierPerformanceReport_shouldReturnAllPerformanceReportsWhenNoDateRangeProvided() + throws Exception { + LocalDateTime startDate = LocalDateTime.now().minusDays(4); + LocalDateTime endDate = LocalDateTime.now(); + mockMvc.perform(get("/courier-analytics/performance-report") + .param("startDate", startDate.format( + ISO_LOCAL_DATE)) + .param("endDate", endDate.format( + ISO_LOCAL_DATE))) + .andDo(MockMvcResultHandlers.print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasSize(2))) + .andExpect(jsonPath("$[?(@.courierName == 'Jane Smith')].deliveryCount").value(hasItem(3))) + .andExpect(jsonPath("$[?(@.courierName == 'Jane Smith')].successRate") + .value(hasItem(closeTo(1.0, 0.001)))) + .andExpect(jsonPath("$[?(@.courierName == 'Jane Smith')].averageRating") + .value(hasItem(closeTo(4.333, 0.001)))) + + .andExpect(jsonPath("$[?(@.courierName == 'John Doe')].deliveryCount").value(hasItem(2))) + .andExpect(jsonPath("$[?(@.courierName == 'John Doe')].successRate") + .value(hasItem(closeTo(0.5, 0.001)))) + .andExpect(jsonPath("$[?(@.courierName == 'John Doe')].averageRating") + .value(hasItem(closeTo(3.0, 0.001)))); + } + + @Test + void getCourierPerformanceReport_shouldReturnPerformanceReportsForSpecificDateRange() + throws Exception { + LocalDateTime startDate = LocalDateTime.now().minusDays(4); + LocalDateTime endDate = LocalDateTime.now().minusDays(2); + + mockMvc.perform(get("/courier-analytics/performance-report") + .param("startDate", startDate.format(ISO_LOCAL_DATE)) + .param("endDate", endDate.format(ISO_LOCAL_DATE))) + .andDo(MockMvcResultHandlers.print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasSize(2))) + .andExpect(jsonPath("$[?(@.courierName == 'Jane Smith')].deliveryCount").value(hasItem(2))) + .andExpect(jsonPath("$[?(@.courierName == 'Jane Smith')].successRate") + .value(hasItem(closeTo(1.0, 0.001)))) + .andExpect(jsonPath("$[?(@.courierName == 'Jane Smith')].averageRating") + .value(hasItem(closeTo(4.0, 0.001)))) + + .andExpect(jsonPath("$[?(@.courierName == 'John Doe')].deliveryCount").value(hasItem(2))) + .andExpect(jsonPath("$[?(@.courierName == 'John Doe')].successRate") + .value(hasItem(closeTo(0.5, 0.001)))) + .andExpect(jsonPath("$[?(@.courierName == 'John Doe')].averageRating") + .value(hasItem(closeTo(3.0, 0.001)))); + } +} diff --git a/src/test/java/com/Podzilla/analytics/controllers/ProfitReportControllerTest.java b/src/test/java/com/Podzilla/analytics/controllers/ProfitReportControllerTest.java index 4dd471a..fab6d70 100644 --- a/src/test/java/com/Podzilla/analytics/controllers/ProfitReportControllerTest.java +++ b/src/test/java/com/Podzilla/analytics/controllers/ProfitReportControllerTest.java @@ -1,282 +1,283 @@ -// package com.Podzilla.analytics.controllers; - -// import static org.assertj.core.api.Assertions.assertThat; -// import static org.mockito.ArgumentMatchers.any; -// import static org.mockito.Mockito.when; - -// import java.math.BigDecimal; -// import java.time.LocalDate; -// import java.util.Arrays; -// import java.util.Collections; -// import java.util.List; - -// import org.junit.jupiter.api.BeforeEach; -// import org.junit.jupiter.api.Test; -// import org.springframework.beans.factory.annotation.Autowired; -// import org.springframework.boot.test.context.SpringBootTest; -// import org.springframework.boot.test.mock.mockito.MockBean; -// import org.springframework.boot.test.web.client.TestRestTemplate; -// import org.springframework.core.ParameterizedTypeReference; -// import org.springframework.http.HttpMethod; -// import org.springframework.http.HttpStatus; -// import org.springframework.http.ResponseEntity; -// import org.springframework.web.util.UriComponentsBuilder; - -// import com.Podzilla.analytics.api.dtos.profit.ProfitByCategory; -// import com.Podzilla.analytics.services.ProfitAnalyticsService; -// import com.fasterxml.jackson.databind.ObjectMapper; -// import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; - -// @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -// public class ProfitReportControllerTest { - -// @Autowired -// private TestRestTemplate restTemplate; - -// @MockBean -// private ProfitAnalyticsService mockService; - -// private ObjectMapper objectMapper; -// private LocalDate startDate; -// private LocalDate endDate; -// private List profitData; - -// @BeforeEach -// public void setup() { -// objectMapper = new ObjectMapper(); -// objectMapper.registerModule(new JavaTimeModule()); - -// startDate = LocalDate.of(2024, 1, 1); -// endDate = LocalDate.of(2024, 1, 31); - -// // Setup test data -// profitData = Arrays.asList( -// ProfitByCategory.builder() -// .category("Electronics") -// .totalRevenue(BigDecimal.valueOf(10000.50)) -// .totalCost(BigDecimal.valueOf(7500.25)) -// .grossProfit(BigDecimal.valueOf(2500.25)) -// .grossProfitMargin(BigDecimal.valueOf(25.00)) -// .build(), -// ProfitByCategory.builder() -// .category("Clothing") -// .totalRevenue(BigDecimal.valueOf(5500.75)) -// .totalCost(BigDecimal.valueOf(3000.50)) -// .grossProfit(BigDecimal.valueOf(2500.25)) -// .grossProfitMargin(BigDecimal.valueOf(45.45)) -// .build()); -// } - -// @Test -// public void testGetProfitByCategory_Success() { -// // Configure mock service -// when(mockService.getProfitByCategory(startDate, endDate)) -// .thenReturn(profitData); - -// // Build URL with query parameters -// String url = UriComponentsBuilder.fromPath("/profit-analytics/by-category") -// .queryParam("startDate", startDate.toString()) -// .queryParam("endDate", endDate.toString()) -// .toUriString(); - -// // Execute request -// ResponseEntity> response = restTemplate.exchange( -// url, -// HttpMethod.GET, -// null, -// new ParameterizedTypeReference>() {}); - -// // Verify -// assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); -// assertThat(response.getBody()).isNotNull(); -// assertThat(response.getBody().size()).isEqualTo(2); -// assertThat(response.getBody().get(0).getCategory()).isEqualTo("Electronics"); -// assertThat(response.getBody().get(0).getTotalRevenue()).isEqualTo(BigDecimal.valueOf(10000.50)); -// assertThat(response.getBody().get(0).getGrossProfit()).isEqualTo(BigDecimal.valueOf(2500.25)); -// assertThat(response.getBody().get(1).getCategory()).isEqualTo("Clothing"); -// assertThat(response.getBody().get(1).getGrossProfitMargin()).isEqualTo(BigDecimal.valueOf(45.45)); -// } - -// @Test -// public void testGetProfitByCategory_EmptyResult() { -// // Configure mock service to return empty list -// when(mockService.getProfitByCategory(startDate, endDate)) -// .thenReturn(Collections.emptyList()); - -// // Build URL with query parameters -// String url = UriComponentsBuilder.fromPath("/profit-analytics/by-category") -// .queryParam("startDate", startDate.toString()) -// .queryParam("endDate", endDate.toString()) -// .toUriString(); - -// // Execute request -// ResponseEntity> response = restTemplate.exchange( -// url, -// HttpMethod.GET, -// null, -// new ParameterizedTypeReference>() {}); - -// // Verify -// assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); -// assertThat(response.getBody()).isNotNull(); -// assertThat(response.getBody()).isEmpty(); -// } - -// @Test -// public void testGetProfitByCategory_MissingStartDate() { -// // Build URL with missing required parameter -// String url = UriComponentsBuilder.fromPath("/profit-analytics/by-category") -// .queryParam("endDate", endDate.toString()) -// .toUriString(); - -// // Execute request -// ResponseEntity response = restTemplate.exchange( -// url, -// HttpMethod.GET, -// null, -// String.class); - -// // Verify -// assertThat(response.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST); -// } - -// @Test -// public void testGetProfitByCategory_MissingEndDate() { -// // Build URL with missing required parameter -// String url = UriComponentsBuilder.fromPath("/profit-analytics/by-category") -// .queryParam("startDate", startDate.toString()) -// .toUriString(); - -// // Execute request -// ResponseEntity response = restTemplate.exchange( -// url, -// HttpMethod.GET, -// null, -// String.class); - -// // Verify -// assertThat(response.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST); -// } - -// @Test -// public void testGetProfitByCategory_InvalidDateFormat() { -// // Build URL with invalid date format -// String url = UriComponentsBuilder.fromPath("/profit-analytics/by-category") -// .queryParam("startDate", "2024-01-01") -// .queryParam("endDate", "invalid-date") -// .toUriString(); - -// // Execute request -// ResponseEntity response = restTemplate.exchange( -// url, -// HttpMethod.GET, -// null, -// String.class); - -// // Verify -// assertThat(response.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST); -// } - -// @Test -// public void testGetProfitByCategory_StartDateAfterEndDate() { -// // Set up dates where start is after end -// LocalDate invalidStart = LocalDate.of(2024, 2, 1); -// LocalDate invalidEnd = LocalDate.of(2024, 1, 1); - -// // Build URL with invalid date range -// String url = UriComponentsBuilder.fromPath("/profit-analytics/by-category") -// .queryParam("startDate", invalidStart.toString()) -// .queryParam("endDate", invalidEnd.toString()) -// .toUriString(); - -// // Execute request -// ResponseEntity response = restTemplate.exchange( -// url, -// HttpMethod.GET, -// null, -// String.class); - -// // Verify -// assertThat(response.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST); -// } - -// @Test -// public void testGetProfitByCategory_FutureDateRange() { -// // Set up future dates -// LocalDate futureStart = LocalDate.now().plusDays(1); -// LocalDate futureEnd = LocalDate.now().plusDays(30); - -// // Configure mock service - should return empty data for future dates -// when(mockService.getProfitByCategory(futureStart, futureEnd)) -// .thenReturn(Collections.emptyList()); - -// // Build URL with future date range -// String url = UriComponentsBuilder.fromPath("/profit-analytics/by-category") -// .queryParam("startDate", futureStart.toString()) -// .queryParam("endDate", futureEnd.toString()) -// .toUriString(); - -// // Execute request -// ResponseEntity> response = restTemplate.exchange( -// url, -// HttpMethod.GET, -// null, -// new ParameterizedTypeReference>() {}); - -// // Verify -// assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); -// assertThat(response.getBody()).isNotNull(); -// assertThat(response.getBody()).isEmpty(); -// } - -// @Test -// public void testGetProfitByCategory_SameDayRange() { -// // Test same start and end date -// LocalDate sameDate = LocalDate.of(2024, 1, 1); - -// // Configure mock service -// when(mockService.getProfitByCategory(sameDate, sameDate)) -// .thenReturn(profitData); - -// // Build URL with same day for start and end -// String url = UriComponentsBuilder.fromPath("/profit-analytics/by-category") -// .queryParam("startDate", sameDate.toString()) -// .queryParam("endDate", sameDate.toString()) -// .toUriString(); - -// // Execute request -// ResponseEntity> response = restTemplate.exchange( -// url, -// HttpMethod.GET, -// null, -// new ParameterizedTypeReference>() {}); - -// // Verify -// assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); -// assertThat(response.getBody()).isNotNull(); -// assertThat(response.getBody().get(0).getCategory()).isEqualTo("Electronics"); -// } - -// @Test -// public void testGetProfitByCategory_ServiceException() { -// // Configure mock service to throw exception -// when(mockService.getProfitByCategory(any(), any())) -// .thenThrow(new RuntimeException("Service error")); - -// // Build URL with query parameters -// String url = UriComponentsBuilder.fromPath("/profit-analytics/by-category") -// .queryParam("startDate", startDate.toString()) -// .queryParam("endDate", endDate.toString()) -// .toUriString(); - -// // Execute request -// ResponseEntity response = restTemplate.exchange( -// url, -// HttpMethod.GET, -// null, -// String.class); - -// // Verify -// assertThat(response.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR); -// } -// } \ No newline at end of file +package com.Podzilla.analytics.controllers; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.util.UriComponentsBuilder; + +import com.Podzilla.analytics.api.dtos.profit.ProfitByCategory; +import com.Podzilla.analytics.services.ProfitAnalyticsService; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class ProfitReportControllerTest { + + @Autowired + private TestRestTemplate restTemplate; + + @MockBean + private ProfitAnalyticsService mockService; + + private ObjectMapper objectMapper; + private LocalDate startDate; + private LocalDate endDate; + private List profitData; + + @BeforeEach + public void setup() { + objectMapper = new ObjectMapper(); + objectMapper.registerModule(new JavaTimeModule()); + + startDate = LocalDate.of(2024, 1, 1); + endDate = LocalDate.of(2024, 1, 31); + + // Setup test data + profitData = Arrays.asList( + ProfitByCategory.builder() + .category("Electronics") + .totalRevenue(BigDecimal.valueOf(10000.50)) + .totalCost(BigDecimal.valueOf(7500.25)) + .grossProfit(BigDecimal.valueOf(2500.25)) + .grossProfitMargin(BigDecimal.valueOf(25.00)) + .build(), + ProfitByCategory.builder() + .category("Clothing") + .totalRevenue(BigDecimal.valueOf(5500.75)) + .totalCost(BigDecimal.valueOf(3000.50)) + .grossProfit(BigDecimal.valueOf(2500.25)) + .grossProfitMargin(BigDecimal.valueOf(45.45)) + .build()); + } + + @Test + public void testGetProfitByCategory_Success() { + // Configure mock service + when(mockService.getProfitByCategory(startDate, endDate)) + .thenReturn(profitData); + + // Build URL with query parameters + String url = UriComponentsBuilder.fromPath("/profit-analytics/by-category") + .queryParam("startDate", startDate.toString()) + .queryParam("endDate", endDate.toString()) + .toUriString(); + + // Execute request + ResponseEntity> response = restTemplate.exchange( + url, + HttpMethod.GET, + null, + new ParameterizedTypeReference>() {}); + + // Verify + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(response.getBody()).isNotNull(); + assertThat(response.getBody().size()).isEqualTo(2); + assertThat(response.getBody().get(0).getCategory()).isEqualTo("Electronics"); + assertThat(response.getBody().get(0).getTotalRevenue()).isEqualTo(BigDecimal.valueOf(10000.50)); + assertThat(response.getBody().get(0).getGrossProfit()).isEqualTo(BigDecimal.valueOf(2500.25)); + assertThat(response.getBody().get(1).getCategory()).isEqualTo("Clothing"); + assertThat(response.getBody().get(1).getGrossProfitMargin()).isEqualTo(BigDecimal.valueOf(45.45)); + } + + @Test + public void testGetProfitByCategory_EmptyResult() { + // Configure mock service to return empty list + when(mockService.getProfitByCategory(startDate, endDate)) + .thenReturn(Collections.emptyList()); + + // Build URL with query parameters + String url = UriComponentsBuilder.fromPath("/profit-analytics/by-category") + .queryParam("startDate", startDate.toString()) + .queryParam("endDate", endDate.toString()) + .toUriString(); + + // Execute request + ResponseEntity> response = restTemplate.exchange( + url, + HttpMethod.GET, + null, + new ParameterizedTypeReference>() {}); + + // Verify + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(response.getBody()).isNotNull(); + assertThat(response.getBody()).isEmpty(); + } + + @Test + public void testGetProfitByCategory_MissingStartDate() { + // Build URL with missing required parameter + String url = UriComponentsBuilder.fromPath("/profit-analytics/by-category") + .queryParam("endDate", endDate.toString()) + .toUriString(); + + // Execute request + ResponseEntity response = restTemplate.exchange( + url, + HttpMethod.GET, + null, + String.class); + + // Verify + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST); + } + + @Test + public void testGetProfitByCategory_MissingEndDate() { + // Build URL with missing required parameter + String url = UriComponentsBuilder.fromPath("/profit-analytics/by-category") + .queryParam("startDate", startDate.toString()) + .toUriString(); + + // Execute request + ResponseEntity response = restTemplate.exchange( + url, + HttpMethod.GET, + null, + String.class); + + // Verify + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST); + } + + @Test + public void testGetProfitByCategory_InvalidDateFormat() { + // Build URL with invalid date format + String url = UriComponentsBuilder.fromPath("/profit-analytics/by-category") + .queryParam("startDate", "2024-01-01") + .queryParam("endDate", "invalid-date") + .toUriString(); + + // Execute request + ResponseEntity response = restTemplate.exchange( + url, + HttpMethod.GET, + null, + String.class); + + // Verify + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST); + } + + @Test + public void testGetProfitByCategory_StartDateAfterEndDate() { + // Set up dates where start is after end + LocalDate invalidStart = LocalDate.of(2024, 2, 1); + LocalDate invalidEnd = LocalDate.of(2024, 1, 1); + + // Build URL with invalid date range + String url = UriComponentsBuilder.fromPath("/profit-analytics/by-category") + .queryParam("startDate", invalidStart.toString()) + .queryParam("endDate", invalidEnd.toString()) + .toUriString(); + + // Execute request + ResponseEntity response = restTemplate.exchange( + url, + HttpMethod.GET, + null, + String.class); + + // Verify + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST); + } + + @Test + public void testGetProfitByCategory_FutureDateRange() { + // Set up future dates + LocalDate futureStart = LocalDate.now().plusDays(1); + LocalDate futureEnd = LocalDate.now().plusDays(30); + + // Configure mock service - should return empty data for future dates + when(mockService.getProfitByCategory(futureStart, futureEnd)) + .thenReturn(Collections.emptyList()); + + // Build URL with future date range + String url = UriComponentsBuilder.fromPath("/profit-analytics/by-category") + .queryParam("startDate", futureStart.toString()) + .queryParam("endDate", futureEnd.toString()) + .toUriString(); + + // Execute request + ResponseEntity> response = restTemplate.exchange( + url, + HttpMethod.GET, + null, + new ParameterizedTypeReference>() {}); + + // Verify + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(response.getBody()).isNotNull(); + assertThat(response.getBody()).isEmpty(); + } + + @Test + public void testGetProfitByCategory_ConsecutiveDayRange() { + // Test consecutive dates (1 day range) + LocalDate startDate = LocalDate.of(2024, 1, 1); + LocalDate endDate = LocalDate.of(2024, 1, 2); + + // Configure mock service + when(mockService.getProfitByCategory(startDate, endDate)) + .thenReturn(profitData); + + // Build URL with consecutive days for start and end + String url = UriComponentsBuilder.fromPath("/profit-analytics/by-category") + .queryParam("startDate", startDate.toString()) + .queryParam("endDate", endDate.toString()) + .toUriString(); + + // Execute request + ResponseEntity> response = restTemplate.exchange( + url, + HttpMethod.GET, + null, + new ParameterizedTypeReference>() {}); + + // Verify + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(response.getBody()).isNotNull(); + assertThat(response.getBody().get(0).getCategory()).isEqualTo("Electronics"); + } + + @Test + public void testGetProfitByCategory_ServiceException() { + // Configure mock service to throw exception + when(mockService.getProfitByCategory(any(), any())) + .thenThrow(new RuntimeException("Service error")); + + // Build URL with query parameters + String url = UriComponentsBuilder.fromPath("/profit-analytics/by-category") + .queryParam("startDate", startDate.toString()) + .queryParam("endDate", endDate.toString()) + .toUriString(); + + // Execute request + ResponseEntity response = restTemplate.exchange( + url, + HttpMethod.GET, + null, + String.class); + + // Verify + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR); + } +} \ No newline at end of file diff --git a/src/test/resources/application-test.properties b/src/test/resources/application-test.properties index 31fc6e4..454c37d 100644 --- a/src/test/resources/application-test.properties +++ b/src/test/resources/application-test.properties @@ -1,7 +1,7 @@ -# # H2 Database Configuration for Tests -# spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE -# spring.datasource.driver-class-name=org.h2.Driver -# spring.datasource.username=sa -# spring.datasource.password= -# spring.jpa.hibernate.ddl-auto=update -# spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect \ No newline at end of file +# H2 Database Configuration for Tests +spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE +spring.datasource.driver-class-name=org.h2.Driver +spring.datasource.username=sa +spring.datasource.password= +spring.jpa.hibernate.ddl-auto=update +spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect \ No newline at end of file diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties index 70a1b4f..0139a73 100644 --- a/src/test/resources/application.properties +++ b/src/test/resources/application.properties @@ -1,14 +1,14 @@ -# # Test Database Configuration -# spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;MODE=PostgreSQL -# spring.datasource.username=sa -# spring.datasource.password= -# spring.datasource.driver-class-name=org.h2.Driver -# # JPA/Hibernate Configuration -# spring.jpa.hibernate.ddl-auto=create-drop -# spring.jpa.show-sql=true -# spring.jpa.properties.hibernate.format_sql=true -# spring.jpa.database-platform=org.hibernate.dialect.H2Dialect +# Test Database Configuration +spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;MODE=PostgreSQL +spring.datasource.username=sa +spring.datasource.password= +spring.datasource.driver-class-name=org.h2.Driver +# JPA/Hibernate Configuration +spring.jpa.hibernate.ddl-auto=create-drop +spring.jpa.show-sql=true +spring.jpa.properties.hibernate.format_sql=true +spring.jpa.database-platform=org.hibernate.dialect.H2Dialect -# # H2 Console (optional, for debugging) -# spring.h2.console.enabled=true -# spring.h2.console.path=/h2-console +# H2 Console (optional, for debugging) +spring.h2.console.enabled=true +spring.h2.console.path=/h2-console From e15c20df7588a83ee36370720f07216adb10e19d Mon Sep 17 00:00:00 2001 From: Mohamed Date: Sun, 18 May 2025 15:57:03 +0300 Subject: [PATCH 07/10] WIP: Save progress before merge --- .../analytics/config/DatabaseSeeder.java | 72 +- .../Podzilla/analytics/models/Courier.java | 30 +- .../Podzilla/analytics/models/Customer.java | 17 +- .../com/Podzilla/analytics/models/Order.java | 79 +- .../{SalesLineItem.java => OrderItem.java} | 29 +- .../Podzilla/analytics/models/Product.java | 2 - ...torySnapshot.java => ProductSnapshot.java} | 11 +- .../com/Podzilla/analytics/models/Region.java | 2 - .../repositories/CourierRepository.java | 7 +- .../repositories/CustomerRepository.java | 24 +- .../InventorySnapshotRepository.java | 39 - ...pository.java => OrderItemRepository.java} | 20 +- .../repositories/OrderRepository.java | 195 ++--- .../repositories/ProductRepository.java | 44 +- .../ProductSnapshotRepository.java | 40 + .../services/FulfillmentAnalyticsService.java | 9 +- .../services/InventoryAnalyticsService.java | 4 +- .../services/ProfitAnalyticsService.java | 6 +- .../CourierAnalyticsControllerTest.java | 12 +- .../FulfillmentReportControllerTest.java | 773 ++++++++++-------- .../RevenueReportControllerTest.java | 236 +++--- .../services/CourierAnalyticsServiceTest.java | 714 ++++++++-------- .../services/ProductAnalyticsServiceTest.java | 474 +++++------ .../services/RevenueReportServiceTest.java | 432 +++++----- 24 files changed, 1686 insertions(+), 1585 deletions(-) rename src/main/java/com/Podzilla/analytics/models/{SalesLineItem.java => OrderItem.java} (80%) rename src/main/java/com/Podzilla/analytics/models/{InventorySnapshot.java => ProductSnapshot.java} (86%) delete mode 100644 src/main/java/com/Podzilla/analytics/repositories/InventorySnapshotRepository.java rename src/main/java/com/Podzilla/analytics/repositories/{SalesLineItemRepository.java => OrderItemRepository.java} (53%) create mode 100644 src/main/java/com/Podzilla/analytics/repositories/ProductSnapshotRepository.java diff --git a/src/main/java/com/Podzilla/analytics/config/DatabaseSeeder.java b/src/main/java/com/Podzilla/analytics/config/DatabaseSeeder.java index 4a3cf3c..5500ad5 100644 --- a/src/main/java/com/Podzilla/analytics/config/DatabaseSeeder.java +++ b/src/main/java/com/Podzilla/analytics/config/DatabaseSeeder.java @@ -2,14 +2,14 @@ import com.Podzilla.analytics.models.Courier; import com.Podzilla.analytics.models.Customer; -import com.Podzilla.analytics.models.InventorySnapshot; import com.Podzilla.analytics.models.Order; import com.Podzilla.analytics.models.Product; +import com.Podzilla.analytics.models.ProductSnapshot; import com.Podzilla.analytics.models.Region; -import com.Podzilla.analytics.models.SalesLineItem; +import com.Podzilla.analytics.models.OrderItem; import com.Podzilla.analytics.repositories.CourierRepository; import com.Podzilla.analytics.repositories.CustomerRepository; -import com.Podzilla.analytics.repositories.InventorySnapshotRepository; +import com.Podzilla.analytics.repositories.ProductSnapshotRepository; import com.Podzilla.analytics.repositories.OrderRepository; import com.Podzilla.analytics.repositories.ProductRepository; import com.Podzilla.analytics.repositories.RegionRepository; @@ -26,7 +26,6 @@ import java.util.Random; import java.util.UUID; - @Component @RequiredArgsConstructor public class DatabaseSeeder implements CommandLineRunner { @@ -36,7 +35,7 @@ public class DatabaseSeeder implements CommandLineRunner { private final ProductRepository productRepository; private final RegionRepository regionRepository; private final OrderRepository orderRepository; - private final InventorySnapshotRepository inventorySnapshotRepository; + private final ProductSnapshotRepository productSnapshotRepository; private final Random random = new Random(); private static final int LOW_STOCK_PROD1 = 10; @@ -123,9 +122,9 @@ public void run(final String... args) { System.out.println("Seeded Orders: " + orderRepository.count()); System.out.println("Seeding Inventory Snapshots..."); - seedInventorySnapshots(products); - System.out.println("Seeded Inventory Snapshots: " - + inventorySnapshotRepository.count()); + seedProductSnapshots(products); + System.out.println("Seeded Product Snapshots: " + + productSnapshotRepository.count()); System.out.println("Database seeding finished."); } @@ -180,18 +179,15 @@ private List seedCouriers() { Courier courier1 = courierRepository.save( Courier.builder() .id(UUID.randomUUID()) - .name("Speedy Delivery Inc.") - .status(Courier.CourierStatus.ACTIVE).build()); + .name("Speedy Delivery Inc.").build()); Courier courier2 = courierRepository.save( Courier.builder() .id(UUID.randomUUID()) - .name("Reliable Couriers Co.") - .status(Courier.CourierStatus.ACTIVE).build()); + .name("Reliable Couriers Co.").build()); Courier courier3 = courierRepository.save( Courier.builder() .id(UUID.randomUUID()) - .name("Overnight Express") - .status(Courier.CourierStatus.INACTIVE).build()); + .name("Overnight Express").build()); return Arrays.asList(courier1, courier2, courier3); } @@ -225,7 +221,7 @@ private void seedOrders( .id(UUID.randomUUID()) .customer(customers.get(0)).courier(couriers.get(0)) .region(regions.get(0)) - .status(Order.OrderStatus.COMPLETED) + .status(Order.OrderStatus.DELIVERED) .orderPlacedTimestamp(placed1) .shippedTimestamp(placed1.plusHours(ORDER_1_SHIP_HOURS)) .deliveredTimestamp(placed1.plusDays(ORDER_1_DELIVER_DAYS) @@ -236,13 +232,13 @@ private void seedOrders( .totalAmount(BigDecimal.ZERO) .courierRating(RATING_GOOD) .build(); - SalesLineItem itemFirstOrderFirst = SalesLineItem.builder() + OrderItem itemFirstOrderFirst = OrderItem.builder() .order(order1).product(products.get(0)).quantity(1) .pricePerUnit(PRICE_PROD1).build(); - SalesLineItem itemFirstOrderSecond = SalesLineItem.builder() + OrderItem itemFirstOrderSecond = OrderItem.builder() .order(order1).product(products.get(2)).quantity(2) .pricePerUnit(PRICE_PROD3).build(); - order1.setSalesLineItems(Arrays.asList(itemFirstOrderFirst, + order1.setOrderItems(Arrays.asList(itemFirstOrderFirst, itemFirstOrderSecond)); order1.setNumberOfItems(itemFirstOrderFirst.getQuantity() + itemFirstOrderSecond.getQuantity()); @@ -269,10 +265,10 @@ private void seedOrders( .plusHours(ORDER_2_SHIP_HOURS)) .courierRating(null).failureReason(null) .build(); - SalesLineItem itemSecondOrderFirst = SalesLineItem.builder() + OrderItem itemSecondOrderFirst = OrderItem.builder() .order(order2).product(products.get(1)).quantity(1) .pricePerUnit(PRICE_PROD2).build(); - order2.setSalesLineItems(List.of(itemSecondOrderFirst)); + order2.setOrderItems(List.of(itemSecondOrderFirst)); order2.setNumberOfItems(itemSecondOrderFirst.getQuantity()); order2.setTotalAmount( itemSecondOrderFirst.getPricePerUnit().multiply( @@ -287,9 +283,9 @@ private void seedOrders( .id(UUID.randomUUID()) .customer(customers.get(0)).courier(couriers.get(0)) .region(regions.get(2)) - .status(Order.OrderStatus.FAILED) + .status(Order.OrderStatus.DELIVERY_FAILED) .orderPlacedTimestamp(placed3) - .status(Order.OrderStatus.FAILED) + .status(Order.OrderStatus.DELIVERY_FAILED) .orderPlacedTimestamp(placed3) .shippedTimestamp(placed3.plusHours(ORDER_3_SHIP_HOURS)) .deliveredTimestamp(null) @@ -297,10 +293,10 @@ private void seedOrders( .failureReason("Delivery address incorrect") .courierRating(RATING_POOR) .build(); - SalesLineItem itemThirdOrderFirst = SalesLineItem.builder() + OrderItem itemThirdOrderFirst = OrderItem.builder() .order(order3).product(products.get(INDEX_THREE)).quantity(1) .pricePerUnit(PRICE_PROD4).build(); - order3.setSalesLineItems(List.of(itemThirdOrderFirst)); + order3.setOrderItems(List.of(itemThirdOrderFirst)); order3.setNumberOfItems(itemThirdOrderFirst.getQuantity()); order3.setTotalAmount( itemThirdOrderFirst.getPricePerUnit().multiply( @@ -314,7 +310,7 @@ private void seedOrders( .id(UUID.randomUUID()) .customer(customers.get(2)).courier(couriers.get(1)) .region(regions.get(0)) - .status(Order.OrderStatus.COMPLETED) + .status(Order.OrderStatus.DELIVERED) .orderPlacedTimestamp(placed4) .shippedTimestamp(placed4.plusHours(ORDER_4_SHIP_HOURS)) .deliveredTimestamp(placed4.plusHours(ORDER_4_DELIVER_HOURS)) @@ -323,13 +319,13 @@ private void seedOrders( .totalAmount(BigDecimal.ZERO) .courierRating(RATING_EXCELLENT) .build(); - SalesLineItem itemFourthOrderFirst = SalesLineItem.builder() + OrderItem itemFourthOrderFirst = OrderItem.builder() .order(order4).product(products.get(0)).quantity(1) .pricePerUnit(PRICE_PROD1).build(); - SalesLineItem itemFourthOrderSecond = SalesLineItem.builder() + OrderItem itemFourthOrderSecond = OrderItem.builder() .order(order4).product(products.get(INDEX_THREE)).quantity(1) .pricePerUnit(PRICE_PROD4).build(); - order4.setSalesLineItems(Arrays.asList(itemFourthOrderFirst, + order4.setOrderItems(Arrays.asList(itemFourthOrderFirst, itemFourthOrderSecond)); order4.setNumberOfItems( itemFourthOrderFirst.getQuantity() + itemFourthOrderSecond @@ -343,29 +339,29 @@ private void seedOrders( orderRepository.save(order4); } - private void seedInventorySnapshots(final List products) { - seedInventorySnapshot(products.get(0), INVENTORY_RANGE_PROD1, + private void seedProductSnapshots(final List products) { + seedProductSnapshot(products.get(0), INVENTORY_RANGE_PROD1, INVENTORY_QUANTITY_PROD1); - seedInventorySnapshot(products.get(1), INVENTORY_RANGE_PROD2, + seedProductSnapshot(products.get(1), INVENTORY_RANGE_PROD2, INVENTORY_QUANTITY_PROD2); - seedInventorySnapshot(products.get(2), INVENTORY_RANGE_PROD3, + seedProductSnapshot(products.get(2), INVENTORY_RANGE_PROD3, INVENTORY_QUANTITY_PROD3); - seedInventorySnapshot(products.get(INDEX_THREE), INVENTORY_RANGE_PROD4, + seedProductSnapshot(products.get(INDEX_THREE), INVENTORY_RANGE_PROD4, INVENTORY_QUANTITY_PROD4); } - private void seedInventorySnapshot( + private void seedProductSnapshot( final Product product, final int range, final int quantity) { - inventorySnapshotRepository.save( - InventorySnapshot.builder() + productSnapshotRepository.save( + ProductSnapshot.builder() .product(product) .quantity(random.nextInt(range) + product.getLowStockThreshold()) .timestamp(LocalDateTime.now().minusDays( INVENTORY_SNAPSHOT_DAYS_PRIOR_1)) .build()); - inventorySnapshotRepository.save( - InventorySnapshot.builder() + productSnapshotRepository.save( + ProductSnapshot.builder() .product(product) .quantity(random.nextInt(quantity) + product.getLowStockThreshold()) diff --git a/src/main/java/com/Podzilla/analytics/models/Courier.java b/src/main/java/com/Podzilla/analytics/models/Courier.java index 9bb70da..e1fd7fa 100644 --- a/src/main/java/com/Podzilla/analytics/models/Courier.java +++ b/src/main/java/com/Podzilla/analytics/models/Courier.java @@ -1,20 +1,20 @@ package com.Podzilla.analytics.models; +import jakarta.persistence.CascadeType; import jakarta.persistence.Entity; -import jakarta.persistence.EnumType; -import jakarta.persistence.Enumerated; import jakarta.persistence.Id; +import jakarta.persistence.OneToMany; import jakarta.persistence.Table; import lombok.AllArgsConstructor; -// import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; + +import java.util.List; import java.util.UUID; @Entity @Table(name = "couriers") @Data -// @Builder @NoArgsConstructor @AllArgsConstructor public class Courier { @@ -22,14 +22,8 @@ public class Courier { private UUID id; private String name; - @Enumerated(EnumType.STRING) - private CourierStatus status; - - public enum CourierStatus { - ACTIVE, - INACTIVE, - SUSPENDED - } + @OneToMany(mappedBy = "courier", cascade = CascadeType.ALL) + private List orders; public static Builder builder() { return new Builder(); @@ -38,9 +32,11 @@ public static Builder builder() { public static class Builder { private UUID id; private String name; - private CourierStatus status; + private List orders; + + public Builder() { + } - public Builder() { } public Builder id(final UUID id) { this.id = id; return this; @@ -51,13 +47,13 @@ public Builder name(final String name) { return this; } - public Builder status(final CourierStatus status) { - this.status = status; + public Builder orders(final List orders) { + this.orders = orders; return this; } public Courier build() { - return new Courier(id, name, status); + return new Courier(id, name, orders); } } } diff --git a/src/main/java/com/Podzilla/analytics/models/Customer.java b/src/main/java/com/Podzilla/analytics/models/Customer.java index 19839c5..123ca36 100644 --- a/src/main/java/com/Podzilla/analytics/models/Customer.java +++ b/src/main/java/com/Podzilla/analytics/models/Customer.java @@ -1,18 +1,20 @@ package com.Podzilla.analytics.models; +import jakarta.persistence.CascadeType; import jakarta.persistence.Entity; import jakarta.persistence.Id; +import jakarta.persistence.OneToMany; import jakarta.persistence.Table; import lombok.AllArgsConstructor; -// import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; + +import java.util.List; import java.util.UUID; @Entity @Table(name = "customers") @Data -// @Builder @NoArgsConstructor @AllArgsConstructor public class Customer { @@ -20,12 +22,16 @@ public class Customer { private UUID id; private String name; + @OneToMany(mappedBy = "customer", cascade = CascadeType.ALL) + private List orders; + public static Builder builder() { return new Builder(); } public static class Builder { private UUID id; private String name; + private List orders; public Builder() { } @@ -39,8 +45,13 @@ public Builder name(final String name) { return this; } + public Builder orders(final List orders) { + this.orders = orders; + return this; + } + public Customer build() { - return new Customer(id, name); + return new Customer(id, name, orders); } } } diff --git a/src/main/java/com/Podzilla/analytics/models/Order.java b/src/main/java/com/Podzilla/analytics/models/Order.java index 6929a9b..daff52a 100644 --- a/src/main/java/com/Podzilla/analytics/models/Order.java +++ b/src/main/java/com/Podzilla/analytics/models/Order.java @@ -15,7 +15,6 @@ import jakarta.persistence.OneToMany; import jakarta.persistence.Table; import lombok.AllArgsConstructor; -// import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import java.util.UUID; @@ -23,7 +22,6 @@ @Entity @Table(name = "orders") @Data -// @Builder @NoArgsConstructor @AllArgsConstructor public class Order { @@ -31,9 +29,12 @@ public class Order { private UUID id; private BigDecimal totalAmount; + private LocalDateTime orderPlacedTimestamp; + private LocalDateTime orderCancelledTimestamp; private LocalDateTime shippedTimestamp; private LocalDateTime deliveredTimestamp; + private LocalDateTime orderDeliveryFailedTimestamp; private LocalDateTime finalStatusTimestamp; @Enumerated(EnumType.STRING) @@ -58,14 +59,14 @@ public class Order { private Region region; @OneToMany(mappedBy = "order", cascade = CascadeType.ALL) - private List salesLineItems; + private List orderItems; public enum OrderStatus { PLACED, + CANCELLED, SHIPPED, - DELIVERED_PENDING_PAYMENT, - COMPLETED, - FAILED + DELIVERED, + DELIVERY_FAILED } public static Builder builder() { @@ -76,8 +77,10 @@ public static class Builder { private UUID id; private BigDecimal totalAmount; private LocalDateTime orderPlacedTimestamp; + private LocalDateTime orderCancelledTimestamp; private LocalDateTime shippedTimestamp; private LocalDateTime deliveredTimestamp; + private LocalDateTime orderDeliveryFailedTimestamp; private LocalDateTime finalStatusTimestamp; private OrderStatus status; private String failureReason; @@ -86,9 +89,10 @@ public static class Builder { private Customer customer; private Courier courier; private Region region; - private List salesLineItems; + private List orderItems; - public Builder() { } + public Builder() { + } public Builder id(final UUID id) { this.id = id; @@ -101,27 +105,36 @@ public Builder totalAmount(final BigDecimal totalAmount) { } public Builder orderPlacedTimestamp( - final LocalDateTime orderPlacedTimestamp - ) { + final LocalDateTime orderPlacedTimestamp) { this.orderPlacedTimestamp = orderPlacedTimestamp; return this; } + public Builder orderCancelledTimestamp( + final LocalDateTime orderCancelledTimestamp) { + this.orderCancelledTimestamp = orderCancelledTimestamp; + return this; + } + public Builder shippedTimestamp(final LocalDateTime shippedTimestamp) { this.shippedTimestamp = shippedTimestamp; return this; } public Builder deliveredTimestamp( - final LocalDateTime deliveredTimestamp - ) { + final LocalDateTime deliveredTimestamp) { this.deliveredTimestamp = deliveredTimestamp; return this; } + public Builder orderDeliveryFailedTimestamp( + final LocalDateTime orderDeliveryFailedTimestamp) { + this.orderDeliveryFailedTimestamp = orderDeliveryFailedTimestamp; + return this; + } + public Builder finalStatusTimestamp( - final LocalDateTime finalStatusTimestamp - ) { + final LocalDateTime finalStatusTimestamp) { this.finalStatusTimestamp = finalStatusTimestamp; return this; } @@ -161,30 +174,30 @@ public Builder region(final Region region) { return this; } - public Builder salesLineItems( - final List salesLineItems - ) { - this.salesLineItems = salesLineItems; + public Builder orderItems( + final List orderItems) { + this.orderItems = orderItems; return this; } public Order build() { return new Order( - id, - totalAmount, - orderPlacedTimestamp, - shippedTimestamp, - deliveredTimestamp, - finalStatusTimestamp, - status, - failureReason, - numberOfItems, - courierRating, - customer, - courier, - region, - salesLineItems - ); + id, + totalAmount, + orderPlacedTimestamp, + orderCancelledTimestamp, + shippedTimestamp, + deliveredTimestamp, + orderDeliveryFailedTimestamp, + finalStatusTimestamp, + status, + failureReason, + numberOfItems, + courierRating, + customer, + courier, + region, + orderItems); } } } diff --git a/src/main/java/com/Podzilla/analytics/models/SalesLineItem.java b/src/main/java/com/Podzilla/analytics/models/OrderItem.java similarity index 80% rename from src/main/java/com/Podzilla/analytics/models/SalesLineItem.java rename to src/main/java/com/Podzilla/analytics/models/OrderItem.java index f93f7a3..06e07a4 100644 --- a/src/main/java/com/Podzilla/analytics/models/SalesLineItem.java +++ b/src/main/java/com/Podzilla/analytics/models/OrderItem.java @@ -3,25 +3,26 @@ import java.math.BigDecimal; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; + import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; import lombok.AllArgsConstructor; -// import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import java.util.UUID; @Entity -@Table(name = "sales_line_items") +@Table(name = "order_items") @Data -// @Builder @NoArgsConstructor @AllArgsConstructor -public class SalesLineItem { +public class OrderItem { + @Id - @GeneratedValue(generator = "uuid") + @GeneratedValue(strategy = GenerationType.UUID) private UUID id; private int quantity; @@ -46,7 +47,8 @@ public static class Builder { private Product product; private Order order; - public Builder() { } + public Builder() { + } public Builder id(final UUID id) { this.id = id; @@ -73,14 +75,13 @@ public Builder order(final Order order) { return this; } - public SalesLineItem build() { - return new SalesLineItem( - id, - quantity, - pricePerUnit, - product, - order - ); + public OrderItem build() { + return new OrderItem( + id, + quantity, + pricePerUnit, + product, + order); } } } diff --git a/src/main/java/com/Podzilla/analytics/models/Product.java b/src/main/java/com/Podzilla/analytics/models/Product.java index c5fe59c..fc6223e 100644 --- a/src/main/java/com/Podzilla/analytics/models/Product.java +++ b/src/main/java/com/Podzilla/analytics/models/Product.java @@ -6,7 +6,6 @@ import jakarta.persistence.Id; import jakarta.persistence.Table; import lombok.AllArgsConstructor; -// import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import java.util.UUID; @@ -14,7 +13,6 @@ @Entity @Table(name = "products") @Data -// @Builder @NoArgsConstructor @AllArgsConstructor public class Product { diff --git a/src/main/java/com/Podzilla/analytics/models/InventorySnapshot.java b/src/main/java/com/Podzilla/analytics/models/ProductSnapshot.java similarity index 86% rename from src/main/java/com/Podzilla/analytics/models/InventorySnapshot.java rename to src/main/java/com/Podzilla/analytics/models/ProductSnapshot.java index 0f272db..0dac74d 100644 --- a/src/main/java/com/Podzilla/analytics/models/InventorySnapshot.java +++ b/src/main/java/com/Podzilla/analytics/models/ProductSnapshot.java @@ -8,18 +8,17 @@ import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; import lombok.AllArgsConstructor; -// import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import java.util.UUID; @Entity -@Table(name = "inventory_snapshots") +@Table(name = "product_snapshots") @Data -// @Builder @NoArgsConstructor @AllArgsConstructor -public class InventorySnapshot { +public class ProductSnapshot { + @Id @GeneratedValue(generator = "uuid") private UUID id; @@ -63,8 +62,8 @@ public Builder quantity(final int quantity) { return this; } - public InventorySnapshot build() { - return new InventorySnapshot(id, timestamp, product, quantity); + public ProductSnapshot build() { + return new ProductSnapshot(id, timestamp, product, quantity); } } diff --git a/src/main/java/com/Podzilla/analytics/models/Region.java b/src/main/java/com/Podzilla/analytics/models/Region.java index 5d17373..d83075f 100644 --- a/src/main/java/com/Podzilla/analytics/models/Region.java +++ b/src/main/java/com/Podzilla/analytics/models/Region.java @@ -4,7 +4,6 @@ import jakarta.persistence.Id; import jakarta.persistence.Table; import lombok.AllArgsConstructor; -// import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import java.util.UUID; @@ -12,7 +11,6 @@ @Entity @Table(name = "regions") @Data -// @Builder @NoArgsConstructor @AllArgsConstructor public class Region { diff --git a/src/main/java/com/Podzilla/analytics/repositories/CourierRepository.java b/src/main/java/com/Podzilla/analytics/repositories/CourierRepository.java index bee39da..56925c1 100644 --- a/src/main/java/com/Podzilla/analytics/repositories/CourierRepository.java +++ b/src/main/java/com/Podzilla/analytics/repositories/CourierRepository.java @@ -16,16 +16,15 @@ public interface CourierRepository extends JpaRepository { @Query("SELECT c.id AS courierId, " + "c.name AS courierName, " + "COUNT(o.id) AS deliveryCount, " - + "SUM(CASE WHEN o.status = 'COMPLETED' THEN 1 ELSE 0 END) " + + "SUM(CASE WHEN o.status = 'DELIVERED' THEN 1 ELSE 0 END) " + "AS completedCount, " - + "AVG(CASE WHEN o.status = 'COMPLETED' THEN o.courierRating " + + "AVG(CASE WHEN o.status = 'DELIVERED' THEN o.courierRating " + "ELSE NULL END) AS averageRating " + "FROM Courier c " + "LEFT JOIN Order o " + "ON c.id = o.courier.id " + "AND o.finalStatusTimestamp BETWEEN :startDate AND :endDate " - + "GROUP BY c.id, c.name " - + "ORDER BY c.id") + + "GROUP BY c.id, c.name ") List findCourierPerformanceBetweenDates( @Param("startDate") LocalDateTime startDate, @Param("endDate") LocalDateTime endDate); diff --git a/src/main/java/com/Podzilla/analytics/repositories/CustomerRepository.java b/src/main/java/com/Podzilla/analytics/repositories/CustomerRepository.java index 79bd7f8..fc7b0d0 100644 --- a/src/main/java/com/Podzilla/analytics/repositories/CustomerRepository.java +++ b/src/main/java/com/Podzilla/analytics/repositories/CustomerRepository.java @@ -15,16 +15,16 @@ @Repository public interface CustomerRepository extends JpaRepository { - @Query(value = "SELECT c.id as customerId, c.name as customerName, " - + "SUM(o.total_amount) as totalSpending " - + "FROM customers c " - + "JOIN orders o ON c.id = o.customer_id " - + "WHERE o.order_placed_timestamp " - + "BETWEEN :startDate AND :endDate " - + "GROUP BY c.id, c.name " - + "ORDER BY totalSpending DESC", nativeQuery = true) - Page findTopSpenders( - @Param("startDate") LocalDateTime startDate, - @Param("endDate") LocalDateTime endDate, - Pageable pageable); + @Query("SELECT c.id AS customerId, c.name AS customerName, " + + "COALESCE(SUM(o.totalAmount), 0) AS totalSpending " + + "FROM Customer c " + + "LEFT JOIN c.orders o " + + "WITH o.finalStatusTimestamp BETWEEN :startDate AND :endDate " + + "AND o.status = 'DELIVERED' " + + "GROUP BY c.id, c.name " + + "ORDER BY totalSpending DESC") + Page findTopSpenders( + @Param("startDate") LocalDateTime startDate, + @Param("endDate") LocalDateTime endDate, + Pageable pageable); } diff --git a/src/main/java/com/Podzilla/analytics/repositories/InventorySnapshotRepository.java b/src/main/java/com/Podzilla/analytics/repositories/InventorySnapshotRepository.java deleted file mode 100644 index 219a3fc..0000000 --- a/src/main/java/com/Podzilla/analytics/repositories/InventorySnapshotRepository.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.Podzilla.analytics.repositories; - -import java.util.List; - -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.stereotype.Repository; - -import com.Podzilla.analytics.api.projections.inventory.InventoryValueByCategoryProjection; -import com.Podzilla.analytics.api.projections.inventory.LowStockProductProjection; -import com.Podzilla.analytics.models.InventorySnapshot; - -@Repository -public interface InventorySnapshotRepository - extends JpaRepository { - - @Query(value = "SELECT p.category as category, " - + "SUM(s.quantity * p.cost) as totalStockValue " - + "FROM inventory_snapshots s " - + "JOIN products p ON s.product_id = p.id " - + "WHERE s.timestamp = (SELECT MAX(s2.timestamp) " - + "FROM inventory_snapshots s2 WHERE " - + "s2.product_id = s.product_id) " - + "GROUP BY p.category", nativeQuery = true) - List getInventoryValueByCategory(); - - @Query(value = "SELECT p.id as productId, p.name as productName, " - + "s.quantity as currentQuantity, " - + "p.low_stock_threshold as threshold " - + "FROM inventory_snapshots s " - + "JOIN products p ON s.product_id = p.id " - + "WHERE s.timestamp = (SELECT MAX(s2.timestamp) " - + "FROM inventory_snapshots s2 WHERE " - + "s2.product_id = s.product_id) " -+ "AND s.quantity <= p.low_stock_threshold", nativeQuery = true) - Page getLowStockProducts(Pageable pageable); -} diff --git a/src/main/java/com/Podzilla/analytics/repositories/SalesLineItemRepository.java b/src/main/java/com/Podzilla/analytics/repositories/OrderItemRepository.java similarity index 53% rename from src/main/java/com/Podzilla/analytics/repositories/SalesLineItemRepository.java rename to src/main/java/com/Podzilla/analytics/repositories/OrderItemRepository.java index d9bcd5c..94ddd06 100644 --- a/src/main/java/com/Podzilla/analytics/repositories/SalesLineItemRepository.java +++ b/src/main/java/com/Podzilla/analytics/repositories/OrderItemRepository.java @@ -4,23 +4,23 @@ import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; -import com.Podzilla.analytics.models.SalesLineItem; +import com.Podzilla.analytics.models.OrderItem; import com.Podzilla.analytics.api.projections.profit.ProfitByCategoryProjection; import java.time.LocalDateTime; import java.util.List; import java.util.UUID; -public interface SalesLineItemRepository - extends JpaRepository { - @Query("SELECT sli.product.category as category, " - + "SUM(sli.quantity * sli.pricePerUnit) as totalRevenue, " - + "SUM(sli.quantity * sli.product.cost) as totalCost " - + "FROM SalesLineItem sli " - + "WHERE sli.order.orderPlacedTimestamp BETWEEN " +public interface OrderItemRepository + extends JpaRepository { + @Query("SELECT oi.product.category as category, " + + "SUM(oi.quantity * oi.pricePerUnit) as totalRevenue, " + + "SUM(oi.quantity * oi.product.cost) as totalCost " + + "FROM OrderItem oi " + + "WHERE oi.order.finalStatusTimestamp BETWEEN " + ":startDate AND :endDate " - + "AND sli.order.status = 'COMPLETED' " - + "GROUP BY sli.product.category") + + "AND oi.order.status = 'DELIVERED' " + + "GROUP BY oi.product.category") List findSalesByCategoryBetweenDates( @Param("startDate") LocalDateTime startDate, @Param("endDate") LocalDateTime endDate); diff --git a/src/main/java/com/Podzilla/analytics/repositories/OrderRepository.java b/src/main/java/com/Podzilla/analytics/repositories/OrderRepository.java index ef7b506..9a8c844 100644 --- a/src/main/java/com/Podzilla/analytics/repositories/OrderRepository.java +++ b/src/main/java/com/Podzilla/analytics/repositories/OrderRepository.java @@ -19,146 +19,149 @@ public interface OrderRepository extends JpaRepository { - @Query(value = "SELECT 'OVERALL' as groupByValue, " - + "AVG(EXTRACT(EPOCH FROM (o.shipped_timestamp - " - + "o.order_placed_timestamp)) / 86400) as averageDuration " - + "FROM orders o " - + "WHERE o.order_placed_timestamp BETWEEN :startDate AND :endDate " - + "AND o.shipped_timestamp IS NOT NULL", nativeQuery = true) + @Query("SELECT 'OVERALL' AS groupByValue, " + + "AVG( (EXTRACT(EPOCH FROM o.shippedTimestamp) - " + + "EXTRACT(EPOCH FROM o.orderPlacedTimestamp)) / 3600) " + + "AS averageDuration " + + "FROM Order o " + + "WHERE o.orderPlacedTimestamp BETWEEN :startDate AND :endDate " + + "AND o.shippedTimestamp IS NOT NULL") FulfillmentTimeProjection findPlaceToShipTimeOverall( @Param("startDate") LocalDateTime startDate, @Param("endDate") LocalDateTime endDate); - @Query(value = "SELECT CONCAT('RegionID_', o.region_id) as groupByValue, " - + "AVG(EXTRACT(EPOCH FROM (o.shipped_timestamp - " - + "o.order_placed_timestamp)) / 86400) as averageDuration " - + "FROM orders o " - + "WHERE o.order_placed_timestamp BETWEEN :startDate AND :endDate " - + "AND o.shipped_timestamp IS NOT NULL " - + "GROUP BY o.region_id", nativeQuery = true) + @Query("SELECT CONCAT('RegionID_', r.id) AS groupByValue, " + + "AVG( (EXTRACT(EPOCH FROM o.shippedTimestamp) - " + + "EXTRACT(EPOCH FROM o.orderPlacedTimestamp)) / 3600) " + + "AS averageDuration " + + "FROM Order o " + + "JOIN o.region r " + + "WHERE o.orderPlacedTimestamp BETWEEN :startDate AND :endDate " + + "AND o.shippedTimestamp IS NOT NULL " + + "GROUP BY r.id") List findPlaceToShipTimeByRegion( @Param("startDate") LocalDateTime startDate, @Param("endDate") LocalDateTime endDate); - @Query(value = "SELECT 'OVERALL' as groupByValue, " - + "AVG(EXTRACT(EPOCH FROM (o.delivered_timestamp " - + "- o.shipped_timestamp)) / 86400) as averageDuration " - + "FROM orders o " - + "WHERE o.shipped_timestamp BETWEEN :startDate AND :endDate " - + "AND o.delivered_timestamp IS NOT NULL " - + "AND o.status = 'COMPLETED'", nativeQuery = true) + @Query("SELECT 'OVERALL' AS groupByValue, " + + "AVG( (EXTRACT(EPOCH FROM o.deliveredTimestamp) - " + + "EXTRACT(EPOCH FROM o.shippedTimestamp)) / 3600) " + + "AS averageDuration " + + "FROM Order o " + + "WHERE o.shippedTimestamp BETWEEN :startDate AND :endDate " + + "AND o.deliveredTimestamp IS NOT NULL " + + "AND o.status = 'DELIVERED'") FulfillmentTimeProjection findShipToDeliverTimeOverall( @Param("startDate") LocalDateTime startDate, @Param("endDate") LocalDateTime endDate); - @Query(value = "SELECT CONCAT('RegionID_', o.region_id) as groupByValue, " - + "AVG(EXTRACT(EPOCH FROM (o.delivered_timestamp - " - + "o.shipped_timestamp)) / 86400) as averageDuration " - + "FROM orders o " - + "WHERE o.shipped_timestamp BETWEEN :startDate AND :endDate " - + "AND o.delivered_timestamp IS NOT NULL " - + "AND o.status = 'COMPLETED' " - + "GROUP BY o.region_id", nativeQuery = true) + @Query("SELECT CONCAT('RegionID_', r.id) AS groupByValue, " + + "AVG( (EXTRACT(EPOCH FROM o.deliveredTimestamp) - " + + "EXTRACT(EPOCH FROM o.shippedTimestamp)) / 3600) " + + "AS averageDuration " + + "FROM Order o " + + "JOIN o.region r " + + "WHERE o.shippedTimestamp BETWEEN :startDate AND :endDate " + + "AND o.deliveredTimestamp IS NOT NULL " + + "AND o.status = 'DELIVERED' " + + "GROUP BY r.id") List findShipToDeliverTimeByRegion( @Param("startDate") LocalDateTime startDate, @Param("endDate") LocalDateTime endDate); - @Query(value = "SELECT CONCAT('CourierID_', o.courier_id) as groupByValue, " - + "AVG(EXTRACT(EPOCH FROM (o.delivered_timestamp - " - + "o.shipped_timestamp)) / 86400) as averageDuration " - + "FROM orders o " - + "WHERE o.shipped_timestamp BETWEEN :startDate AND :endDate " - + "AND o.delivered_timestamp IS NOT NULL " - + "AND o.status = 'COMPLETED' " - + "GROUP BY o.courier_id", nativeQuery = true) + @Query("SELECT CONCAT('CourierID_', c.id) AS groupByValue, " + + "AVG( (EXTRACT(EPOCH FROM o.deliveredTimestamp) - " + + "EXTRACT(EPOCH FROM o.shippedTimestamp)) / 3600) " + + "AS averageDuration " + + "FROM Order o " + + "JOIN o.courier c " + + "WHERE o.shippedTimestamp BETWEEN :startDate AND :endDate " + + "AND o.deliveredTimestamp IS NOT NULL " + + "AND o.status = 'DELIVERED' " + + "GROUP BY c.id") List findShipToDeliverTimeByCourier( @Param("startDate") LocalDateTime startDate, @Param("endDate") LocalDateTime endDate); - @Query(value = "SELECT o.region_id as regionId, " - + "r.city as city, " - + "r.country as country, " - + "count(o.id) as orderCount, " - + "avg(o.total_amount) as averageOrderValue " - + "FROM orders o " - + "INNER JOIN regions r on o.region_id = r.id " - + "WHERE o.final_status_timestamp BETWEEN :startDate AND :endDate " - + "GROUP BY o.region_id, r.city, r.country " - + "ORDER BY orderCount desc, averageOrderValue desc", - nativeQuery = true) + @Query("SELECT r.id AS regionId, " + + "r.city AS city, " + + "r.country AS country, " + + "COUNT(o) AS orderCount, " + + "AVG(o.totalAmount) AS averageOrderValue " + + "FROM Order o " + + "JOIN o.region r " + + "WHERE o.finalStatusTimestamp BETWEEN :startDate AND :endDate " + + "AND o.status = 'DELIVERED' " + + "GROUP BY r.id, r.city, r.country " + + "ORDER BY orderCount DESC, averageOrderValue DESC") List findOrdersByRegion( @Param("startDate") LocalDateTime startDate, @Param("endDate") LocalDateTime endDate); - @Query(value = "SELECT o.status as status, " - + "count(o.id) as count " - + "FROM orders o " - + "WHERE o.final_status_timestamp BETWEEN :startDate AND :endDate " + @Query("SELECT o.status AS status, " + + "COUNT(o) AS count " + + "FROM Order o " + + "WHERE o.finalStatusTimestamp BETWEEN :startDate AND :endDate " + "GROUP BY o.status " - + "ORDER BY count desc", - nativeQuery = true) + + "ORDER BY count DESC") List findOrderStatusCounts( @Param("startDate") LocalDateTime startDate, @Param("endDate") LocalDateTime endDate); - @Query(value = "SELECT o.failure_reason as reason, " - + "count(o.id) as count " - + "FROM orders o " - + "WHERE o.final_status_timestamp BETWEEN :startDate AND :endDate " - + "AND o.status = 'FAILED' " - + "GROUP BY o.failure_reason " - + "ORDER BY count desc", - nativeQuery = true) + @Query("SELECT o.failureReason AS reason, " + + "COUNT(o) AS count " + + "FROM Order o " + + "WHERE o.finalStatusTimestamp BETWEEN :startDate AND :endDate " + + "AND o.status IN ('DELIVERY_FAILED', 'CANCELLED') " + + "GROUP BY o.failureReason " + + "ORDER BY count DESC") List findFailureReasons( @Param("startDate") LocalDateTime startDate, @Param("endDate") LocalDateTime endDate); - @Query(value = "SELECT(SUM(CASE WHEN o.status = 'FAILED' THEN 1 ELSE 0 END)" - + " / (count(*)*1.0) ) as failureRate " - + "FROM orders o " - + "WHERE o.final_status_timestamp BETWEEN :startDate" - + " AND :endDate", nativeQuery = true) + @Query("SELECT (SUM(CASE WHEN o.status = 'DELIVERY_FAILED' " + + "THEN 1 ELSE 0 END) * 1.0 " + + "/ COUNT(o)) AS failureRate " + + "FROM Order o " + + "WHERE o.finalStatusTimestamp BETWEEN :startDate AND :endDate") OrderFailureRateProjection calculateFailureRate( @Param("startDate") LocalDateTime startDate, @Param("endDate") LocalDateTime endDate); - @Query(value = "SELECT " - + "t.period, " - + "SUM(t.total_amount) as totalRevenue " + @Query("SELECT period AS period, " + + "SUM(rev) AS totalRevenue " + "FROM ( " - + "SELECT " - + "CASE :reportPeriod " - + "WHEN 'DAILY' THEN CAST(o.order_placed_timestamp AS DATE) " - + "WHEN 'WEEKLY' THEN" - + " date_trunc('week', o.order_placed_timestamp)::date " - + "WHEN 'MONTHLY' THEN" - + " date_trunc('month', o.order_placed_timestamp)::date " - + "END as period, " - + "o.total_amount " - + "FROM orders o " - + "WHERE o.order_placed_timestamp >= :startDate " - + "AND o.order_placed_timestamp < :endDate " - + "AND o.status IN ('COMPLETED') " - + ") t " - + "GROUP BY t.period " - + "ORDER BY t.period", nativeQuery = true) + + " SELECT CASE " + + " WHEN :reportPeriod = 'DAILY' " + + " THEN CAST(o.orderPlacedTimestamp AS date) " + + " WHEN :reportPeriod = 'WEEKLY' " + + " THEN FUNCTION('DATE_TRUNC','week',o.orderPlacedTimestamp) " + + " WHEN :reportPeriod = 'MONTHLY' " + + " THEN FUNCTION('DATE_TRUNC','month',o.orderPlacedTimestamp) " + + " END AS period, " + + " o.totalAmount AS rev " + + "FROM Order o " + + "WHERE o.finalStatusTimestamp >= :startDate " + + "AND o.finalStatusTimestamp < :endDate " + + "AND o.status = 'DELIVERED' " + + ") x " + + "GROUP BY period " + + "ORDER BY totalRevenue DESC") List findRevenueSummaryByPeriod( @Param("startDate") LocalDate startDate, @Param("endDate") LocalDate endDate, @Param("reportPeriod") String reportPeriod); - @Query(value = "SELECT " - + "p.category, " - + "SUM(sli.quantity * sli.price_per_unit) as totalRevenue " - + "FROM orders o " - + "JOIN sales_line_items sli ON o.id = sli.order_id " - + "JOIN products p ON sli.product_id = p.id " - + "WHERE o.order_placed_timestamp >= :startDate " - + "AND o.order_placed_timestamp < :endDate " - + "AND o.status IN ('COMPLETED') " + @Query("SELECT p.category AS category, " + + "SUM(oi.quantity * oi.pricePerUnit) AS totalRevenue " + + "FROM OrderItem oi " + + "JOIN oi.order o " + + "JOIN oi.product p " + + "WHERE o.finalStatusTimestamp >= :startDate " + + "AND o.finalStatusTimestamp < :endDate " + + "AND o.status = 'DELIVERED' " + "GROUP BY p.category " - + "ORDER BY SUM(sli.quantity * sli.price_per_unit) DESC", - nativeQuery = true) + + "ORDER BY totalRevenue DESC") List findRevenueByCategory( @Param("startDate") LocalDate startDate, @Param("endDate") LocalDate endDate); diff --git a/src/main/java/com/Podzilla/analytics/repositories/ProductRepository.java b/src/main/java/com/Podzilla/analytics/repositories/ProductRepository.java index 425e6c8..b23191f 100644 --- a/src/main/java/com/Podzilla/analytics/repositories/ProductRepository.java +++ b/src/main/java/com/Podzilla/analytics/repositories/ProductRepository.java @@ -12,34 +12,22 @@ public interface ProductRepository extends JpaRepository { - // Query to find top-selling products by revenue or units - @Query(value = "SELECT " - + "p.id, " - + "p.name, " - + "p.category, " - + "SUM(sli.quantity * sli.price_per_unit) AS total_revenue, " - + "SUM(sli.quantity) AS total_units " - + "FROM orders o " - + "JOIN sales_line_items sli ON o.id = sli.order_id " - + "JOIN products p ON sli.product_id = p.id " - + "WHERE o.final_status_timestamp >= :startDate " - + "AND o.final_status_timestamp < :endDate " - + "AND o.status = 'COMPLETED' " - + "GROUP BY p.id, p.name, p.category " - + "ORDER BY " - + "CASE :sortBy " - + "WHEN 'REVENUE' THEN SUM(sli.quantity * sli.price_per_unit) " - + "WHEN 'UNITS' THEN SUM(sli.quantity) " - + "ELSE SUM(sli.quantity * sli.price_per_unit) " - + "END DESC, " - + "CASE :sortBy " - + "WHEN 'REVENUE' THEN SUM(sli.quantity) " - + "WHEN 'UNITS' THEN SUM(sli.quantity * sli.price_per_unit) " - + "ELSE SUM(sli.quantity) " - + "END DESC " - + "LIMIT COALESCE(:limit , 10)", -nativeQuery = true) - + @Query("SELECT p.id AS id, " + + "p.name AS name, " + + "p.category AS category, " + + "SUM(oi.quantity * oi.pricePerUnit) AS totalRevenue, " + + "SUM(oi.quantity) AS totalUnits " + + "FROM OrderItem oi " + + "JOIN oi.order o " + + "JOIN oi.product p " + + "WHERE o.finalStatusTimestamp >= :startDate " + + "AND o.finalStatusTimestamp < :endDate " + + "AND o.status = 'DELIVERED' " + + "GROUP BY p.id, p.name, p.category " + + "ORDER BY CASE WHEN :sortBy = 'REVENUE' " + + "THEN SUM(oi.quantity * oi.pricePerUnit) " + + " WHEN :sortBy = 'UNITS' THEN SUM(oi.quantity) " + + " ELSE SUM(oi.quantity * oi.pricePerUnit) END DESC") List findTopSellers( @Param("startDate") LocalDateTime startDate, @Param("endDate") LocalDateTime endDate, diff --git a/src/main/java/com/Podzilla/analytics/repositories/ProductSnapshotRepository.java b/src/main/java/com/Podzilla/analytics/repositories/ProductSnapshotRepository.java new file mode 100644 index 0000000..6759b77 --- /dev/null +++ b/src/main/java/com/Podzilla/analytics/repositories/ProductSnapshotRepository.java @@ -0,0 +1,40 @@ +package com.Podzilla.analytics.repositories; + +import java.util.List; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; + +import com.Podzilla.analytics.api.projections.inventory.InventoryValueByCategoryProjection; +import com.Podzilla.analytics.api.projections.inventory.LowStockProductProjection; +import com.Podzilla.analytics.models.ProductSnapshot; + +@Repository +public interface ProductSnapshotRepository + extends JpaRepository { + + @Query("SELECT p.category AS category, " + + "SUM(s.quantity * p.cost) AS totalStockValue " + + "FROM ProductSnapshot s " + + "JOIN s.product p " + + "WHERE s.timestamp = (SELECT MAX(s2.timestamp) " + + " FROM ProductSnapshot s2 " + + " WHERE s2.product.id = s.product.id) " + + "GROUP BY p.category") + List getInventoryValueByCategory(); + + @Query("SELECT p.id AS productId, " + + "p.name AS productName, " + + "s.quantity AS currentQuantity, " + + "p.lowStockThreshold AS threshold " + + "FROM ProductSnapshot s " + + "JOIN s.product p " + + "WHERE s.timestamp = (SELECT MAX(s2.timestamp) " + + " FROM ProductSnapshot s2 " + + " WHERE s2.product.id = s.product.id) " + + "AND s.quantity <= p.lowStockThreshold") + Page getLowStockProducts(Pageable pageable); +} diff --git a/src/main/java/com/Podzilla/analytics/services/FulfillmentAnalyticsService.java b/src/main/java/com/Podzilla/analytics/services/FulfillmentAnalyticsService.java index e701a78..5fca47b 100644 --- a/src/main/java/com/Podzilla/analytics/services/FulfillmentAnalyticsService.java +++ b/src/main/java/com/Podzilla/analytics/services/FulfillmentAnalyticsService.java @@ -4,12 +4,9 @@ import com.Podzilla.analytics.repositories.OrderRepository; import com.Podzilla.analytics.util.DatetimeFormatter; import com.Podzilla.analytics.api.dtos.fulfillment.FulfillmentTimeResponse; -import com.Podzilla.analytics.api.dtos.fulfillment -.FulfillmentPlaceToShipRequest.PlaceToShipGroupBy; -import com.Podzilla.analytics.api.dtos.fulfillment -.FulfillmentShipToDeliverRequest.ShipToDeliverGroupBy; -import com.Podzilla.analytics.api.projections.fulfillment -.FulfillmentTimeProjection; +import com.Podzilla.analytics.api.dtos.fulfillment.FulfillmentPlaceToShipRequest.PlaceToShipGroupBy; +import com.Podzilla.analytics.api.dtos.fulfillment.FulfillmentShipToDeliverRequest.ShipToDeliverGroupBy; +import com.Podzilla.analytics.api.projections.fulfillment.FulfillmentTimeProjection; import lombok.RequiredArgsConstructor; import java.math.BigDecimal; diff --git a/src/main/java/com/Podzilla/analytics/services/InventoryAnalyticsService.java b/src/main/java/com/Podzilla/analytics/services/InventoryAnalyticsService.java index 7bcefa3..0bf250e 100644 --- a/src/main/java/com/Podzilla/analytics/services/InventoryAnalyticsService.java +++ b/src/main/java/com/Podzilla/analytics/services/InventoryAnalyticsService.java @@ -6,7 +6,7 @@ import com.Podzilla.analytics.api.dtos.inventory.InventoryValueByCategoryResponse; import com.Podzilla.analytics.api.dtos.inventory.LowStockProductResponse; -import com.Podzilla.analytics.repositories.InventorySnapshotRepository; +import com.Podzilla.analytics.repositories.ProductSnapshotRepository; import java.util.List; import lombok.RequiredArgsConstructor; @@ -14,7 +14,7 @@ @Service @RequiredArgsConstructor public class InventoryAnalyticsService { - private final InventorySnapshotRepository inventoryRepo; + private final ProductSnapshotRepository inventoryRepo; public List getInventoryValueByCategory() { List invVByCy = inventoryRepo diff --git a/src/main/java/com/Podzilla/analytics/services/ProfitAnalyticsService.java b/src/main/java/com/Podzilla/analytics/services/ProfitAnalyticsService.java index 85d3fb3..0daf15e 100644 --- a/src/main/java/com/Podzilla/analytics/services/ProfitAnalyticsService.java +++ b/src/main/java/com/Podzilla/analytics/services/ProfitAnalyticsService.java @@ -4,7 +4,7 @@ import com.Podzilla.analytics.api.dtos.profit.ProfitByCategory; import com.Podzilla.analytics.api.projections.profit.ProfitByCategoryProjection; -import com.Podzilla.analytics.repositories.SalesLineItemRepository; +import com.Podzilla.analytics.repositories.OrderItemRepository; import lombok.RequiredArgsConstructor; @@ -19,14 +19,12 @@ @RequiredArgsConstructor @Service public class ProfitAnalyticsService { - private final SalesLineItemRepository salesLineItemRepository; - // Precision constant for percentage calculations + private final OrderItemRepository salesLineItemRepository; private static final int PERCENTAGE_PRECISION = 4; public List getProfitByCategory( final LocalDate startDate, final LocalDate endDate) { - // Convert LocalDate to LocalDateTime for start of day and end of day LocalDateTime startDateTime = startDate.atStartOfDay(); LocalDateTime endDateTime = endDate.atTime(LocalTime.MAX); diff --git a/src/test/java/com/Podzilla/analytics/controllers/CourierAnalyticsControllerTest.java b/src/test/java/com/Podzilla/analytics/controllers/CourierAnalyticsControllerTest.java index 5de69a6..e758879 100644 --- a/src/test/java/com/Podzilla/analytics/controllers/CourierAnalyticsControllerTest.java +++ b/src/test/java/com/Podzilla/analytics/controllers/CourierAnalyticsControllerTest.java @@ -84,20 +84,18 @@ void setUp() { courierJane = courierRepository.save(Courier.builder() .id(UUID.randomUUID()) .name("Jane Smith") - .status(Courier.CourierStatus.ACTIVE) .build()); courierJohn = courierRepository.save(Courier.builder() .id(UUID.randomUUID()) .name("John Doe") - .status(Courier.CourierStatus.ACTIVE) .build()); orderRepository.save(Order.builder() .id(UUID.randomUUID()) .totalAmount(new BigDecimal("50.00")) .finalStatusTimestamp(LocalDateTime.now().minusDays(3)) - .status(Order.OrderStatus.COMPLETED) + .status(Order.OrderStatus.DELIVERED) .numberOfItems(1) .courierRating(new BigDecimal("4.0")) .customer(customer1) @@ -109,7 +107,7 @@ void setUp() { .id(UUID.randomUUID()) .totalAmount(new BigDecimal("75.00")) .finalStatusTimestamp(LocalDateTime.now().minusDays(3)) - .status(Order.OrderStatus.COMPLETED) + .status(Order.OrderStatus.DELIVERED) .numberOfItems(1) .courierRating(new BigDecimal("4.0")) .customer(customer1) @@ -121,7 +119,7 @@ void setUp() { .id(UUID.randomUUID()) .totalAmount(new BigDecimal("120.00")) .finalStatusTimestamp(LocalDateTime.now().minusDays(1)) - .status(Order.OrderStatus.COMPLETED) + .status(Order.OrderStatus.DELIVERED) .numberOfItems(2) .courierRating(new BigDecimal("5.0")) .customer(customer1) @@ -133,7 +131,7 @@ void setUp() { .id(UUID.randomUUID()) .totalAmount(new BigDecimal("30.00")) .finalStatusTimestamp(LocalDateTime.now().minusDays(2)) - .status(Order.OrderStatus.FAILED) + .status(Order.OrderStatus.DELIVERY_FAILED) .numberOfItems(1) .courierRating(null) .customer(customer1) @@ -145,7 +143,7 @@ void setUp() { .id(UUID.randomUUID()) .totalAmount(new BigDecimal("90.00")) .finalStatusTimestamp(LocalDateTime.now().minusDays(2)) - .status(Order.OrderStatus.COMPLETED) + .status(Order.OrderStatus.DELIVERED) .numberOfItems(1) .courierRating(new BigDecimal("3.0")) .customer(customer1) diff --git a/src/test/java/com/Podzilla/analytics/controllers/FulfillmentReportControllerTest.java b/src/test/java/com/Podzilla/analytics/controllers/FulfillmentReportControllerTest.java index 77f55f8..394b713 100644 --- a/src/test/java/com/Podzilla/analytics/controllers/FulfillmentReportControllerTest.java +++ b/src/test/java/com/Podzilla/analytics/controllers/FulfillmentReportControllerTest.java @@ -1,339 +1,434 @@ -// package com.Podzilla.analytics.controllers; - -// import static org.junit.jupiter.api.Assertions.assertEquals; -// import static org.junit.jupiter.api.Assertions.assertNotNull; -// import static org.junit.jupiter.api.Assertions.assertTrue; -// import static org.mockito.ArgumentMatchers.any; -// import static org.mockito.Mockito.mock; -// import static org.mockito.Mockito.when; - -// import java.math.BigDecimal; -// import java.time.LocalDate; -// import java.util.Arrays; -// import java.util.Collections; -// import java.util.List; - -// import org.junit.jupiter.api.BeforeEach; -// import org.junit.jupiter.api.Test; -// import org.springframework.http.HttpStatus; -// import org.springframework.http.ResponseEntity; - -// import com.Podzilla.analytics.api.controllers.FulfillmentReportController; -// import com.Podzilla.analytics.api.dtos.fulfillment.FulfillmentPlaceToShipRequest; -// import com.Podzilla.analytics.api.dtos.fulfillment.FulfillmentShipToDeliverRequest; -// import com.Podzilla.analytics.api.dtos.fulfillment.FulfillmentShipToDeliverRequest.ShipToDeliverGroupBy; -// import com.Podzilla.analytics.api.dtos.fulfillment.FulfillmentTimeResponse; -// import com.Podzilla.analytics.api.dtos.fulfillment.FulfillmentPlaceToShipRequest.PlaceToShipGroupBy; -// import com.Podzilla.analytics.services.FulfillmentAnalyticsService; - -// public class FulfillmentReportControllerTest { - -// private FulfillmentReportController controller; -// private FulfillmentAnalyticsService mockService; - -// private LocalDate startDate; -// private LocalDate endDate; -// private List overallTimeResponses; -// private List regionTimeResponses; -// private List courierTimeResponses; - -// @BeforeEach -// public void setup() { -// mockService = mock(FulfillmentAnalyticsService.class); -// controller = new FulfillmentReportController(mockService); - -// startDate = LocalDate.of(2024, 1, 1); -// endDate = LocalDate.of(2024, 1, 31); - -// // Setup test data -// overallTimeResponses = Arrays.asList( -// FulfillmentTimeResponse.builder() -// .groupByValue("OVERALL") -// .averageDuration(BigDecimal.valueOf(24.5)) -// .build()); - -// regionTimeResponses = Arrays.asList( -// FulfillmentTimeResponse.builder() -// .groupByValue("RegionID_1") -// .averageDuration(BigDecimal.valueOf(20.2)) -// .build(), -// FulfillmentTimeResponse.builder() -// .groupByValue("RegionID_2") -// .averageDuration(BigDecimal.valueOf(28.7)) -// .build()); - -// courierTimeResponses = Arrays.asList( -// FulfillmentTimeResponse.builder() -// .groupByValue("CourierID_1") -// .averageDuration(BigDecimal.valueOf(18.3)) -// .build(), -// FulfillmentTimeResponse.builder() -// .groupByValue("CourierID_2") -// .averageDuration(BigDecimal.valueOf(22.1)) -// .build()); -// } - -// @Test -// public void testGetPlaceToShipTime_Overall() { -// // Configure mock service -// when(mockService.getPlaceToShipTimeResponse( -// startDate, endDate, PlaceToShipGroupBy.OVERALL)) -// .thenReturn(overallTimeResponses); - -// // Create request -// FulfillmentPlaceToShipRequest request = new FulfillmentPlaceToShipRequest( -// startDate, endDate, PlaceToShipGroupBy.OVERALL); - -// // Execute the method -// ResponseEntity> response = controller.getPlaceToShipTime(request); - -// // Verify response -// assertEquals(HttpStatus.OK, response.getStatusCode()); -// assertEquals(overallTimeResponses, response.getBody()); -// assertEquals(PlaceToShipGroupBy.OVERALL.toString(), response.getBody().get(0).getGroupByValue().toString()); -// assertEquals(BigDecimal.valueOf(24.5), response.getBody().get(0).getAverageDuration()); -// } - -// @Test -// public void testGetPlaceToShipTime_ByRegion() { -// // Configure mock service -// when(mockService.getPlaceToShipTimeResponse( -// startDate, endDate, PlaceToShipGroupBy.REGION)) -// .thenReturn(regionTimeResponses); - -// // Create request -// FulfillmentPlaceToShipRequest request = new FulfillmentPlaceToShipRequest( -// startDate, endDate, PlaceToShipGroupBy.REGION); - -// // Execute the method -// ResponseEntity> response = controller.getPlaceToShipTime(request); - -// // Verify response -// assertEquals(HttpStatus.OK, response.getStatusCode()); -// assertEquals(regionTimeResponses, response.getBody()); -// assertEquals("RegionID_1", response.getBody().get(0).getGroupByValue()); -// assertEquals("RegionID_2", response.getBody().get(1).getGroupByValue()); -// } - -// @Test -// public void testGetShipToDeliverTime_Overall() { -// // Configure mock service -// when(mockService.getShipToDeliverTimeResponse( -// startDate, endDate, ShipToDeliverGroupBy.OVERALL)) -// .thenReturn(overallTimeResponses); - -// // Create request -// FulfillmentShipToDeliverRequest request = new FulfillmentShipToDeliverRequest( -// startDate, endDate, ShipToDeliverGroupBy.OVERALL); - -// // Execute the method -// ResponseEntity> response = controller.getShipToDeliverTime(request); - -// // Verify response -// assertEquals(HttpStatus.OK, response.getStatusCode()); -// assertEquals(overallTimeResponses, response.getBody()); -// assertEquals(ShipToDeliverGroupBy.OVERALL.toString(), response.getBody().get(0).getGroupByValue().toString()); -// } - -// @Test -// public void testGetShipToDeliverTime_ByRegion() { -// // Configure mock service -// when(mockService.getShipToDeliverTimeResponse( -// startDate, endDate, ShipToDeliverGroupBy.REGION)) -// .thenReturn(regionTimeResponses); - -// // Create request -// FulfillmentShipToDeliverRequest request = new FulfillmentShipToDeliverRequest( -// startDate, endDate, ShipToDeliverGroupBy.REGION); - -// // Execute the method -// ResponseEntity> response = controller.getShipToDeliverTime(request); - -// // Verify response -// assertEquals(HttpStatus.OK, response.getStatusCode()); -// assertEquals(regionTimeResponses, response.getBody()); -// assertEquals("RegionID_1", response.getBody().get(0).getGroupByValue()); -// assertEquals("RegionID_2", response.getBody().get(1).getGroupByValue()); -// } - -// @Test -// public void testGetShipToDeliverTime_ByCourier() { -// // Configure mock service -// when(mockService.getShipToDeliverTimeResponse( -// startDate, endDate, ShipToDeliverGroupBy.COURIER)) -// .thenReturn(courierTimeResponses); - -// // Create request -// FulfillmentShipToDeliverRequest request = new FulfillmentShipToDeliverRequest( -// startDate, endDate, ShipToDeliverGroupBy.COURIER); - -// // Execute the method -// ResponseEntity> response = controller.getShipToDeliverTime(request); - -// // Verify response -// assertEquals(HttpStatus.OK, response.getStatusCode()); -// assertEquals(courierTimeResponses, response.getBody()); -// assertEquals("CourierID_1", response.getBody().get(0).getGroupByValue()); -// assertEquals("CourierID_2", response.getBody().get(1).getGroupByValue()); -// } - -// // Edge case tests - -// @Test -// public void testGetPlaceToShipTime_EmptyResponse() { -// // Configure mock service to return empty list -// when(mockService.getPlaceToShipTimeResponse( -// startDate, endDate, PlaceToShipGroupBy.OVERALL)) -// .thenReturn(Collections.emptyList()); - -// // Create request -// FulfillmentPlaceToShipRequest request = new FulfillmentPlaceToShipRequest( -// startDate, endDate, PlaceToShipGroupBy.OVERALL); - -// // Execute the method -// ResponseEntity> response = controller.getPlaceToShipTime(request); - -// // Verify response -// assertEquals(HttpStatus.OK, response.getStatusCode()); -// assertNotNull(response.getBody()); -// assertTrue(response.getBody().isEmpty()); -// } - -// @Test -// public void testGetShipToDeliverTime_EmptyResponse() { -// // Configure mock service to return empty list -// when(mockService.getShipToDeliverTimeResponse( -// startDate, endDate, ShipToDeliverGroupBy.OVERALL)) -// .thenReturn(Collections.emptyList()); - -// // Create request -// FulfillmentShipToDeliverRequest request = new FulfillmentShipToDeliverRequest( -// startDate, endDate, ShipToDeliverGroupBy.OVERALL); - -// // Execute the method -// ResponseEntity> response = controller.getShipToDeliverTime(request); - -// // Verify response -// assertEquals(HttpStatus.OK, response.getStatusCode()); -// assertNotNull(response.getBody()); -// assertTrue(response.getBody().isEmpty()); -// } - -// // @Test -// // public void testGetPlaceToShipTime_InvalidGroupBy() { -// // // Create request with invalid groupBy -// // FulfillmentPlaceToShipRequest request = new FulfillmentPlaceToShipRequest( -// // startDate, endDate, null); - -// // // Execute the method - should return bad request due to validation error -// // ResponseEntity> response = -// // controller.getPlaceToShipTime(request); - -// // // Verify response -// // assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); -// // } - -// // @Test -// // public void testGetShipToDeliverTime_InvalidGroupBy() { -// // // Create request with invalid groupBy -// // FulfillmentShipToDeliverRequest request = new -// // FulfillmentShipToDeliverRequest( -// // startDate, endDate, null); - -// // // Execute the method - should return bad request due to validation error -// // ResponseEntity> response = -// // controller.getShipToDeliverTime(request); - -// // // Verify response -// // assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); -// // } - -// @Test -// public void testGetPlaceToShipTime_SameDayRange() { -// // Test same start and end date -// LocalDate sameDate = LocalDate.of(2024, 1, 1); - -// // Configure mock service -// when(mockService.getPlaceToShipTimeResponse( -// sameDate, sameDate, PlaceToShipGroupBy.OVERALL)) -// .thenReturn(overallTimeResponses); - -// // Create request with same start and end date -// FulfillmentPlaceToShipRequest request = new FulfillmentPlaceToShipRequest( -// sameDate, sameDate, PlaceToShipGroupBy.OVERALL); - -// // Execute the method -// ResponseEntity> response = controller.getPlaceToShipTime(request); - -// // Verify response -// assertEquals(HttpStatus.OK, response.getStatusCode()); -// assertEquals(overallTimeResponses, response.getBody()); -// } - -// @Test -// public void testGetShipToDeliverTime_SameDayRange() { -// // Test same start and end date -// LocalDate sameDate = LocalDate.of(2024, 1, 1); - -// // Configure mock service -// when(mockService.getShipToDeliverTimeResponse( -// sameDate, sameDate, ShipToDeliverGroupBy.OVERALL)) -// .thenReturn(overallTimeResponses); - -// // Create request with same start and end date -// FulfillmentShipToDeliverRequest request = new FulfillmentShipToDeliverRequest( -// sameDate, sameDate, ShipToDeliverGroupBy.OVERALL); - -// // Execute the method -// ResponseEntity> response = controller.getShipToDeliverTime(request); - -// // Verify response -// assertEquals(HttpStatus.OK, response.getStatusCode()); -// assertEquals(overallTimeResponses, response.getBody()); -// } - -// @Test -// public void testGetPlaceToShipTime_FutureDates() { -// // Test future dates -// LocalDate futureStart = LocalDate.now().plusDays(1); -// LocalDate futureEnd = LocalDate.now().plusDays(30); - -// // Configure mock service - should return empty for future dates -// when(mockService.getPlaceToShipTimeResponse( -// futureStart, futureEnd, PlaceToShipGroupBy.OVERALL)) -// .thenReturn(Collections.emptyList()); - -// // Create request with future dates -// FulfillmentPlaceToShipRequest request = new FulfillmentPlaceToShipRequest( -// futureStart, futureEnd, PlaceToShipGroupBy.OVERALL); - -// // Execute the method -// ResponseEntity> response = controller.getPlaceToShipTime(request); - -// // Verify response -// assertEquals(HttpStatus.OK, response.getStatusCode()); -// assertNotNull(response.getBody()); -// assertTrue(response.getBody().isEmpty()); -// } - -// @Test -// public void testGetShipToDeliverTime_ServiceException() { -// // Configure mock service to throw exception -// when(mockService.getShipToDeliverTimeResponse( -// any(), any(), any())) -// .thenThrow(new RuntimeException("Service error")); - -// // Create request -// FulfillmentShipToDeliverRequest request = new FulfillmentShipToDeliverRequest( -// startDate, endDate, ShipToDeliverGroupBy.OVERALL); - -// // Execute the method - controller should handle exception -// // Note: Actual behavior depends on how controller handles exceptions -// // This might need adjustment based on actual implementation -// try { -// controller.getShipToDeliverTime(request); -// } catch (RuntimeException e) { -// assertEquals("Service error", e.getMessage()); -// } -// } -// } \ No newline at end of file +package com.Podzilla.analytics.controllers; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.util.UriComponentsBuilder; + +import com.Podzilla.analytics.api.dtos.fulfillment.FulfillmentPlaceToShipRequest.PlaceToShipGroupBy; +import com.Podzilla.analytics.api.dtos.fulfillment.FulfillmentShipToDeliverRequest.ShipToDeliverGroupBy; +import com.Podzilla.analytics.api.dtos.fulfillment.FulfillmentTimeResponse; +import com.Podzilla.analytics.services.FulfillmentAnalyticsService; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class FulfillmentReportControllerTest { + + @Autowired + private TestRestTemplate restTemplate; + + @MockBean + private FulfillmentAnalyticsService mockService; + + private ObjectMapper objectMapper; + private LocalDate startDate; + private LocalDate endDate; + private List overallTimeResponses; + private List regionTimeResponses; + private List courierTimeResponses; + + @BeforeEach + public void setup() { + objectMapper = new ObjectMapper(); + objectMapper.registerModule(new JavaTimeModule()); + + startDate = LocalDate.of(2024, 1, 1); + endDate = LocalDate.of(2024, 1, 31); + + // Setup test data + overallTimeResponses = Arrays.asList( + FulfillmentTimeResponse.builder() + .groupByValue("OVERALL") + .averageDuration(BigDecimal.valueOf(24.5)) + .build()); + + regionTimeResponses = Arrays.asList( + FulfillmentTimeResponse.builder() + .groupByValue("RegionID_1") + .averageDuration(BigDecimal.valueOf(20.2)) + .build(), + FulfillmentTimeResponse.builder() + .groupByValue("RegionID_2") + .averageDuration(BigDecimal.valueOf(28.7)) + .build()); + + courierTimeResponses = Arrays.asList( + FulfillmentTimeResponse.builder() + .groupByValue("CourierID_1") + .averageDuration(BigDecimal.valueOf(18.3)) + .build(), + FulfillmentTimeResponse.builder() + .groupByValue("CourierID_2") + .averageDuration(BigDecimal.valueOf(22.1)) + .build()); + } + + @Test + public void testGetPlaceToShipTime_Overall() { + // Configure mock service + when(mockService.getPlaceToShipTimeResponse( + startDate, endDate, PlaceToShipGroupBy.OVERALL)) + .thenReturn(overallTimeResponses); + + // Build URL with query parameters + String url = UriComponentsBuilder.fromPath("/fulfillment-analytics/place-to-ship-time") + .queryParam("startDate", startDate.toString()) + .queryParam("endDate", endDate.toString()) + .queryParam("groupBy", PlaceToShipGroupBy.OVERALL.toString()) + .toUriString(); + + // Execute request + ResponseEntity> response = restTemplate.exchange( + url, + HttpMethod.GET, + null, + new ParameterizedTypeReference>() {}); + + // Verify + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(response.getBody()).isNotNull(); + assertThat(response.getBody().size()).isEqualTo(1); + assertThat(response.getBody().get(0).getGroupByValue()).isEqualTo("OVERALL"); + assertThat(response.getBody().get(0).getAverageDuration()).isEqualTo(BigDecimal.valueOf(24.5)); + } + + @Test + public void testGetPlaceToShipTime_ByRegion() { + // Configure mock service + when(mockService.getPlaceToShipTimeResponse( + startDate, endDate, PlaceToShipGroupBy.REGION)) + .thenReturn(regionTimeResponses); + + // Build URL with query parameters + String url = UriComponentsBuilder.fromPath("/fulfillment-analytics/place-to-ship-time") + .queryParam("startDate", startDate.toString()) + .queryParam("endDate", endDate.toString()) + .queryParam("groupBy", PlaceToShipGroupBy.REGION.toString()) + .toUriString(); + + // Execute request + ResponseEntity> response = restTemplate.exchange( + url, + HttpMethod.GET, + null, + new ParameterizedTypeReference>() {}); + + // Verify + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(response.getBody()).isNotNull(); + assertThat(response.getBody().size()).isEqualTo(2); + assertThat(response.getBody().get(0).getGroupByValue()).isEqualTo("RegionID_1"); + assertThat(response.getBody().get(1).getGroupByValue()).isEqualTo("RegionID_2"); + } + + @Test + public void testGetShipToDeliverTime_Overall() { + // Configure mock service + when(mockService.getShipToDeliverTimeResponse( + startDate, endDate, ShipToDeliverGroupBy.OVERALL)) + .thenReturn(overallTimeResponses); + + // Build URL with query parameters + String url = UriComponentsBuilder.fromPath("/fulfillment-analytics/ship-to-deliver-time") + .queryParam("startDate", startDate.toString()) + .queryParam("endDate", endDate.toString()) + .queryParam("groupBy", ShipToDeliverGroupBy.OVERALL.toString()) + .toUriString(); + + // Execute request + ResponseEntity> response = restTemplate.exchange( + url, + HttpMethod.GET, + null, + new ParameterizedTypeReference>() {}); + + // Verify + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(response.getBody()).isNotNull(); + assertThat(response.getBody().get(0).getGroupByValue()).isEqualTo("OVERALL"); + } + + @Test + public void testGetShipToDeliverTime_ByRegion() { + // Configure mock service + when(mockService.getShipToDeliverTimeResponse( + startDate, endDate, ShipToDeliverGroupBy.REGION)) + .thenReturn(regionTimeResponses); + + // Build URL with query parameters + String url = UriComponentsBuilder.fromPath("/fulfillment-analytics/ship-to-deliver-time") + .queryParam("startDate", startDate.toString()) + .queryParam("endDate", endDate.toString()) + .queryParam("groupBy", ShipToDeliverGroupBy.REGION.toString()) + .toUriString(); + + // Execute request + ResponseEntity> response = restTemplate.exchange( + url, + HttpMethod.GET, + null, + new ParameterizedTypeReference>() {}); + + // Verify + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(response.getBody()).isNotNull(); + assertThat(response.getBody().get(0).getGroupByValue()).isEqualTo("RegionID_1"); + assertThat(response.getBody().get(1).getGroupByValue()).isEqualTo("RegionID_2"); + } + + @Test + public void testGetShipToDeliverTime_ByCourier() { + // Configure mock service + when(mockService.getShipToDeliverTimeResponse( + startDate, endDate, ShipToDeliverGroupBy.COURIER)) + .thenReturn(courierTimeResponses); + + // Build URL with query parameters + String url = UriComponentsBuilder.fromPath("/fulfillment-analytics/ship-to-deliver-time") + .queryParam("startDate", startDate.toString()) + .queryParam("endDate", endDate.toString()) + .queryParam("groupBy", ShipToDeliverGroupBy.COURIER.toString()) + .toUriString(); + + // Execute request + ResponseEntity> response = restTemplate.exchange( + url, + HttpMethod.GET, + null, + new ParameterizedTypeReference>() {}); + + // Verify + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(response.getBody()).isNotNull(); + assertThat(response.getBody().get(0).getGroupByValue()).isEqualTo("CourierID_1"); + assertThat(response.getBody().get(1).getGroupByValue()).isEqualTo("CourierID_2"); + } + + // Edge case tests + + @Test + public void testGetPlaceToShipTime_EmptyResponse() { + // Configure mock service to return empty list + when(mockService.getPlaceToShipTimeResponse( + startDate, endDate, PlaceToShipGroupBy.OVERALL)) + .thenReturn(Collections.emptyList()); + + // Build URL with query parameters + String url = UriComponentsBuilder.fromPath("/fulfillment-analytics/place-to-ship-time") + .queryParam("startDate", startDate.toString()) + .queryParam("endDate", endDate.toString()) + .queryParam("groupBy", PlaceToShipGroupBy.OVERALL.toString()) + .toUriString(); + + // Execute request + ResponseEntity> response = restTemplate.exchange( + url, + HttpMethod.GET, + null, + new ParameterizedTypeReference>() {}); + + // Verify + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(response.getBody()).isNotNull(); + assertThat(response.getBody()).isEmpty(); + } + + @Test + public void testGetShipToDeliverTime_EmptyResponse() { + // Configure mock service to return empty list + when(mockService.getShipToDeliverTimeResponse( + startDate, endDate, ShipToDeliverGroupBy.OVERALL)) + .thenReturn(Collections.emptyList()); + + // Build URL with query parameters + String url = UriComponentsBuilder.fromPath("/fulfillment-analytics/ship-to-deliver-time") + .queryParam("startDate", startDate.toString()) + .queryParam("endDate", endDate.toString()) + .queryParam("groupBy", ShipToDeliverGroupBy.OVERALL.toString()) + .toUriString(); + + // Execute request + ResponseEntity> response = restTemplate.exchange( + url, + HttpMethod.GET, + null, + new ParameterizedTypeReference>() {}); + + // Verify + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(response.getBody()).isNotNull(); + assertThat(response.getBody()).isEmpty(); + } + + @Test + public void testGetPlaceToShipTime_InvalidGroupBy() { + // Build URL without groupBy param + String url = UriComponentsBuilder.fromPath("/fulfillment-analytics/place-to-ship-time") + .queryParam("startDate", startDate.toString()) + .queryParam("endDate", endDate.toString()) + .toUriString(); + + // Execute request + ResponseEntity response = restTemplate.exchange( + url, + HttpMethod.GET, + null, + String.class); + + // Verify + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST); + } + + @Test + public void testGetShipToDeliverTime_InvalidGroupBy() { + // Build URL without groupBy param + String url = UriComponentsBuilder.fromPath("/fulfillment-analytics/ship-to-deliver-time") + .queryParam("startDate", startDate.toString()) + .queryParam("endDate", endDate.toString()) + .toUriString(); + + // Execute request + ResponseEntity response = restTemplate.exchange( + url, + HttpMethod.GET, + null, + String.class); + + // Verify + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST); + } + + // @Test + // public void testGetPlaceToShipTime_SameDayRange() { + // // Test same start and end date + // LocalDate sameDate = LocalDate.of(2024, 1, 1); + + // // Configure mock service + // when(mockService.getPlaceToShipTimeResponse( + // sameDate, sameDate, PlaceToShipGroupBy.OVERALL)) + // .thenReturn(overallTimeResponses); + + // // Build URL with query parameters + // String url = UriComponentsBuilder.fromPath("/fulfillment-analytics/place-to-ship-time") + // .queryParam("startDate", sameDate.toString()) + // .queryParam("endDate", sameDate.toString()) + // .queryParam("groupBy", PlaceToShipGroupBy.OVERALL.toString()) + // .toUriString(); + + // // Execute request + // ResponseEntity> response = restTemplate.exchange( + // url, + // HttpMethod.GET, + // null, + // new ParameterizedTypeReference>() {}); + + // // Verify + // assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + // assertThat(response.getBody()).isNotNull(); + // assertThat(response.getBody().get(0).getGroupByValue()).isEqualTo("OVERALL"); + // } + + // @Test + // public void testGetShipToDeliverTime_SameDayRange() { + // // Test same start and end date + // LocalDate sameDate = LocalDate.of(2024, 1, 1); + + // // Configure mock service + // when(mockService.getShipToDeliverTimeResponse( + // sameDate, sameDate, ShipToDeliverGroupBy.OVERALL)) + // .thenReturn(overallTimeResponses); + + // // Build URL with query parameters + // String url = UriComponentsBuilder.fromPath("/fulfillment-analytics/ship-to-deliver-time") + // .queryParam("startDate", sameDate.toString()) + // .queryParam("endDate", sameDate.toString()) + // .queryParam("groupBy", ShipToDeliverGroupBy.OVERALL.toString()) + // .toUriString(); + + // // Execute request + // ResponseEntity> response = restTemplate.exchange( + // url, + // HttpMethod.GET, + // null, + // new ParameterizedTypeReference>() {}); + + // // Verify + // assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + // assertThat(response.getBody()).isNotNull(); + // assertThat(response.getBody().get(0).getGroupByValue()).isEqualTo("OVERALL"); + // } + + @Test + public void testGetPlaceToShipTime_FutureDates() { + // Test future dates + LocalDate futureStart = LocalDate.now().plusDays(1); + LocalDate futureEnd = LocalDate.now().plusDays(30); + + // Configure mock service - should return empty for future dates + when(mockService.getPlaceToShipTimeResponse( + futureStart, futureEnd, PlaceToShipGroupBy.OVERALL)) + .thenReturn(Collections.emptyList()); + + // Build URL with query parameters + String url = UriComponentsBuilder.fromPath("/fulfillment-analytics/place-to-ship-time") + .queryParam("startDate", futureStart.toString()) + .queryParam("endDate", futureEnd.toString()) + .queryParam("groupBy", PlaceToShipGroupBy.OVERALL.toString()) + .toUriString(); + + // Execute request + ResponseEntity> response = restTemplate.exchange( + url, + HttpMethod.GET, + null, + new ParameterizedTypeReference>() {}); + + // Verify + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(response.getBody()).isNotNull(); + assertThat(response.getBody()).isEmpty(); + } + + @Test + public void testGetShipToDeliverTime_ServiceException() { + // Configure mock service to throw exception + when(mockService.getShipToDeliverTimeResponse( + any(), any(), any())) + .thenThrow(new RuntimeException("Service error")); + + // Build URL with query parameters + String url = UriComponentsBuilder.fromPath("/fulfillment-analytics/ship-to-deliver-time") + .queryParam("startDate", startDate.toString()) + .queryParam("endDate", endDate.toString()) + .queryParam("groupBy", ShipToDeliverGroupBy.OVERALL.toString()) + .toUriString(); + + // Execute request + ResponseEntity response = restTemplate.exchange( + url, + HttpMethod.GET, + null, + String.class); + + // Verify + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR); + } +} \ No newline at end of file diff --git a/src/test/java/com/Podzilla/analytics/controllers/RevenueReportControllerTest.java b/src/test/java/com/Podzilla/analytics/controllers/RevenueReportControllerTest.java index b7adcc7..ac9d401 100644 --- a/src/test/java/com/Podzilla/analytics/controllers/RevenueReportControllerTest.java +++ b/src/test/java/com/Podzilla/analytics/controllers/RevenueReportControllerTest.java @@ -1,119 +1,117 @@ -// package com.Podzilla.analytics.controllers; - -// import java.math.BigDecimal; -// import java.time.LocalDate; -// import java.util.Collections; -// import java.util.List; - -// import static org.hamcrest.Matchers.hasSize; -// import static org.hamcrest.Matchers.is; -// import org.junit.jupiter.api.Test; -// import static org.mockito.ArgumentMatchers.any; -// import static org.mockito.Mockito.when; -// import org.springframework.beans.factory.annotation.Autowired; -// import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; // Changed from @WebMvcTest -// import org.springframework.boot.test.context.SpringBootTest; // Added -// import org.springframework.http.MediaType; -// import org.springframework.test.context.bean.override.mockito.MockitoBean; -// import org.springframework.test.web.servlet.MockMvc; -// import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -// import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -// import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -// import com.Podzilla.analytics.api.dtos.RevenueSummaryRequest.Period; -// import com.Podzilla.analytics.api.dtos.RevenueSummaryResponse; -// import com.Podzilla.analytics.services.RevenueReportService; - -// // Using @SpringBootTest loads the full application context -// @SpringBootTest -// // @AutoConfigureMockMvc sets up MockMvc to test the web layer within the full context -// @AutoConfigureMockMvc -// class RevenueReportControllerTest { - -// @Autowired -// private MockMvc mockMvc; -// // Keep @MockitoBean to mock the service as per your original test logic -// @MockitoBean -// private RevenueReportService revenueReportService; - -// // Helper method to create a valid URL with parameters -// private String buildSummaryUrl(LocalDate startDate, LocalDate endDate, Period period) { -// return String.format("/revenue/summary?startDate=%s&endDate=%s&period=%s", -// startDate, endDate, period); -// } - -// @Test -// void getRevenueSummary_ValidRequest_ReturnsOkAndSummaryList() throws Exception { -// // Arrange: Define test data and mock service behavior -// LocalDate startDate = LocalDate.of(2023, 1, 1); -// LocalDate endDate = LocalDate.of(2023, 1, 31); -// Period period = Period.MONTHLY; - -// RevenueSummaryResponse mockResponse = RevenueSummaryResponse.builder() -// .periodStartDate(startDate) -// .totalRevenue(BigDecimal.valueOf(1500.50)) -// .build(); -// List mockSummaryList = Collections.singletonList(mockResponse); - -// // Mock the service call - expect any RevenueSummaryRequest and return the mock list -// when(revenueReportService.getRevenueSummary(any())) -// .thenReturn(mockSummaryList); - -// // Act: Perform the HTTP GET request -// mockMvc.perform(get(buildSummaryUrl(startDate, endDate, period)) -// .contentType(MediaType.APPLICATION_JSON)) // Although GET, setting content type is harmless -// .andExpect(status().isOk()) // Assert: Expect HTTP 200 OK -// .andExpect(jsonPath("$", hasSize(1))) // Expect a JSON array with one element -// .andExpect(jsonPath("$[0].periodStartDate", is(startDate.toString()))) // Check response fields -// .andExpect(jsonPath("$[0].totalRevenue", is(1500.50))); -// } - -// @Test -// void getRevenueSummary_MissingStartDate_ReturnsBadRequest() throws Exception { -// // Arrange: Missing startDate parameter -// LocalDate endDate = LocalDate.of(2023, 1, 31); -// Period period = Period.MONTHLY; - -// // Act & Assert: Perform request and expect HTTP 400 Bad Request due to @NotNull -// mockMvc.perform(get("/revenue/summary?endDate=" + endDate + "&period=" + period) -// .contentType(MediaType.APPLICATION_JSON)) -// .andExpect(status().isBadRequest()); -// // You could add more assertions here to check the response body for validation error details -// } - -// @Test -// void getRevenueSummary_EndDateBeforeStartDate_ReturnsBadRequest() throws Exception { -// // Arrange: Invalid date range (endDate before startDate) - testing @AssertTrue -// LocalDate startDate = LocalDate.of(2023, 1, 31); -// LocalDate endDate = LocalDate.of(2023, 1, 1); // Invalid date range -// Period period = Period.MONTHLY; - -// // Act & Assert: Perform request and expect HTTP 400 Bad Request due to @AssertTrue -// mockMvc.perform(get(buildSummaryUrl(startDate, endDate, period)) -// .contentType(MediaType.APPLICATION_JSON)) -// .andExpect(status().isBadRequest()); -// // Again, check response body for specific validation error message if needed -// } - -// @Test -// void getRevenueSummary_ServiceReturnsEmptyList_ReturnsOkAndEmptyList() throws Exception { -// // Arrange: Service returns an empty list -// LocalDate startDate = LocalDate.of(2023, 1, 1); -// LocalDate endDate = LocalDate.of(2023, 1, 31); -// Period period = Period.MONTHLY; - -// List mockSummaryList = Collections.emptyList(); - -// when(revenueReportService.getRevenueSummary(any())) -// .thenReturn(mockSummaryList); - -// // Act & Assert: Perform request and expect HTTP 200 OK with an empty JSON array -// mockMvc.perform(get(buildSummaryUrl(startDate, endDate, period)) -// .contentType(MediaType.APPLICATION_JSON)) -// .andExpect(status().isOk()) -// .andExpect(jsonPath("$", hasSize(0))); // Expect an empty JSON array -// } - -// // Add similar tests for other scenarios: missing parameters, invalid format, etc. -// // And add tests for the /revenue/by-category endpoint here as well. -// } +package com.Podzilla.analytics.controllers; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.Collections; +import java.util.List; + +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; +import org.junit.jupiter.api.Test; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; // Added +import org.springframework.http.MediaType; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.web.servlet.MockMvc; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.Podzilla.analytics.api.dtos.revenue.RevenueSummaryRequest.Period; +import com.Podzilla.analytics.api.dtos.revenue.RevenueSummaryResponse; +import com.Podzilla.analytics.services.RevenueReportService; + +@SpringBootTest +@AutoConfigureMockMvc +class RevenueReportControllerTest { + + @Autowired + private MockMvc mockMvc; + + @MockitoBean + private RevenueReportService revenueReportService; + + // Helper method to create a valid URL with parameters + private String buildSummaryUrl(LocalDate startDate, LocalDate endDate, Period period) { + return String.format("/revenue-analytics/summary?startDate=%s&endDate=%s&period=%s", + startDate, endDate, period); + } + + @Test + void getRevenueSummary_ValidRequest_ReturnsOkAndSummaryList() throws Exception { + // Arrange: Define test data and mock service behavior + LocalDate startDate = LocalDate.of(2023, 1, 1); + LocalDate endDate = LocalDate.of(2023, 1, 31); + Period period = Period.MONTHLY; + + RevenueSummaryResponse mockResponse = RevenueSummaryResponse.builder() + .periodStartDate(startDate) + .totalRevenue(BigDecimal.valueOf(1500.50)) + .build(); + List mockSummaryList = Collections.singletonList(mockResponse); + + // Mock the service call - expect any RevenueSummaryRequest and return the mock + // list + when(revenueReportService.getRevenueSummary(any(), any(), any())) + .thenReturn(mockSummaryList); + + // Act: Perform the HTTP GET request + mockMvc.perform(get(buildSummaryUrl(startDate, endDate, period)) + .contentType(MediaType.APPLICATION_JSON)) // Although GET, setting content type is harmless + .andExpect(status().isOk()) // Assert: Expect HTTP 200 OK + .andExpect(jsonPath("$", hasSize(1))) // Expect a JSON array with one element + .andExpect(jsonPath("$[0].periodStartDate", is(startDate.toString()))) // Check response fields + .andExpect(jsonPath("$[0].totalRevenue", is(1500.50))); + } + + @Test + void getRevenueSummary_MissingStartDate_ReturnsBadRequest() throws Exception { + // Arrange: Missing startDate parameter + LocalDate endDate = LocalDate.of(2023, 1, 31); + Period period = Period.MONTHLY; + + // Act & Assert: Perform request and expect HTTP 400 Bad Request due to @NotNull + mockMvc.perform(get("/revenue-analytics/summary?endDate=" + endDate + "&period=" + period) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + // You could add more assertions here to check the response body for validation + // error details + } + + @Test + void getRevenueSummary_EndDateBeforeStartDate_ReturnsBadRequest() throws Exception { + // Arrange: Invalid date range (endDate before startDate) - testing @AssertTrue + LocalDate startDate = LocalDate.of(2023, 1, 31); + LocalDate endDate = LocalDate.of(2023, 1, 1); // Invalid date range + Period period = Period.MONTHLY; + + // Act & Assert: Perform request and expect HTTP 400 Bad Request due to + // @AssertTrue + mockMvc.perform(get(buildSummaryUrl(startDate, endDate, period)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + // Again, check response body for specific validation error message if needed + } + + @Test + void getRevenueSummary_ServiceReturnsEmptyList_ReturnsOkAndEmptyList() throws Exception { + // Arrange: Service returns an empty list + LocalDate startDate = LocalDate.of(2023, 1, 1); + LocalDate endDate = LocalDate.of(2023, 1, 31); + Period period = Period.MONTHLY; + + List mockSummaryList = Collections.emptyList(); + + when(revenueReportService.getRevenueSummary(any(), any(), any())) + .thenReturn(mockSummaryList); + + // Act & Assert: Perform request and expect HTTP 200 OK with an empty JSON array + mockMvc.perform(get(buildSummaryUrl(startDate, endDate, period)) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$", hasSize(0))); // Expect an empty JSON array + } +} diff --git a/src/test/java/com/Podzilla/analytics/services/CourierAnalyticsServiceTest.java b/src/test/java/com/Podzilla/analytics/services/CourierAnalyticsServiceTest.java index 4bdce9e..52a5dba 100644 --- a/src/test/java/com/Podzilla/analytics/services/CourierAnalyticsServiceTest.java +++ b/src/test/java/com/Podzilla/analytics/services/CourierAnalyticsServiceTest.java @@ -1,357 +1,357 @@ -// package com.Podzilla.analytics.services; - -// import com.Podzilla.analytics.api.dtos.courier.CourierAverageRatingResponse; -// import com.Podzilla.analytics.api.dtos.courier.CourierDeliveryCountResponse; -// import com.Podzilla.analytics.api.dtos.courier.CourierPerformanceReportResponse; -// import com.Podzilla.analytics.api.dtos.courier.CourierSuccessRateResponse; -// import com.Podzilla.analytics.api.projections.courier.CourierPerformanceProjection; -// import com.Podzilla.analytics.repositories.CourierRepository; - -// import org.junit.jupiter.api.BeforeEach; -// import org.junit.jupiter.api.Test; -// import org.junit.jupiter.api.extension.ExtendWith; -// import org.mockito.InjectMocks; -// import org.mockito.Mock; -// import org.mockito.Mockito; -// import org.mockito.junit.jupiter.MockitoExtension; - -// import java.math.BigDecimal; -// import java.time.LocalDate; -// import java.time.LocalDateTime; -// import java.time.LocalTime; -// import java.util.Arrays; -// import java.util.Collections; -// import java.util.List; -// import java.util.UUID; - -// import static org.junit.jupiter.api.Assertions.*; -// import static org.mockito.ArgumentMatchers.any; -// import static org.mockito.Mockito.when; - -// @ExtendWith(MockitoExtension.class) -// public class CourierAnalyticsServiceTest { - -// @Mock -// private CourierRepository courierRepository; - -// @InjectMocks -// private CourierAnalyticsService courierAnalyticsService; - -// private LocalDate testStartDate; -// private LocalDate testEndDate; -// private LocalDateTime expectedStartDateTime; -// private LocalDateTime expectedEndDateTime; - -// @BeforeEach -// void setUp() { -// testStartDate = LocalDate.of(2024, 1, 1); -// testEndDate = LocalDate.of(2024, 1, 31); -// expectedStartDateTime = testStartDate.atStartOfDay(); -// expectedEndDateTime = testEndDate.atTime(LocalTime.MAX); -// } - -// private CourierPerformanceProjection createMockProjection( -// UUID courierId, String courierName, Long deliveryCount, Long completedCount, BigDecimal averageRating) { -// CourierPerformanceProjection mockProjection = Mockito.mock(CourierPerformanceProjection.class); -// Mockito.lenient().when(mockProjection.getCourierId()).thenReturn(courierId); -// Mockito.lenient().when(mockProjection.getCourierName()).thenReturn(courierName); -// Mockito.lenient().when(mockProjection.getDeliveryCount()).thenReturn(deliveryCount); -// Mockito.lenient().when(mockProjection.getCompletedCount()).thenReturn(completedCount); -// Mockito.lenient().when(mockProjection.getAverageRating()).thenReturn(averageRating); -// return mockProjection; -// } - -// @Test -// void getCourierDeliveryCounts_shouldReturnCorrectCountsForMultipleCouriers() { -// // Arrange -// UUID courierId1 = UUID.randomUUID(); -// UUID courierId2 = UUID.randomUUID(); -// CourierPerformanceProjection janeData = createMockProjection( -// courierId1, "Jane", 10L, 8L, new BigDecimal("4.5")); -// CourierPerformanceProjection johnData = createMockProjection( -// courierId2, "John", 5L, 5L, new BigDecimal("4.0")); - -// when(courierRepository.findCourierPerformanceBetweenDates( -// any(LocalDateTime.class), any(LocalDateTime.class))) -// .thenReturn(Arrays.asList(janeData, johnData)); - -// // Act -// List result = courierAnalyticsService -// .getCourierDeliveryCounts(testStartDate, testEndDate); - -// // Assert -// assertNotNull(result); -// assertEquals(2, result.size()); - -// CourierDeliveryCountResponse janeResponse = result.stream() -// .filter(r -> r.getCourierName().equals("Jane")) -// .findFirst().orElse(null); -// assertNotNull(janeResponse); -// assertEquals(courierId1, janeResponse.getCourierId()); -// assertEquals(10, janeResponse.getDeliveryCount()); - -// CourierDeliveryCountResponse johnResponse = result.stream() -// .filter(r -> r.getCourierName().equals("John")) -// .findFirst().orElse(null); -// assertNotNull(johnResponse); -// assertEquals(courierId2, johnResponse.getCourierId()); -// assertEquals(5, johnResponse.getDeliveryCount()); - -// // Verify repository method was called with correct LocalDateTime arguments -// Mockito.verify(courierRepository).findCourierPerformanceBetweenDates( -// expectedStartDateTime, expectedEndDateTime); -// } - -// @Test -// void getCourierDeliveryCounts_shouldReturnEmptyListWhenNoData() { -// // Arrange -// when(courierRepository.findCourierPerformanceBetweenDates( -// any(LocalDateTime.class), any(LocalDateTime.class))) -// .thenReturn(Collections.emptyList()); - -// // Act -// List result = courierAnalyticsService -// .getCourierDeliveryCounts(testStartDate, testEndDate); - -// // Assert -// assertNotNull(result); -// assertTrue(result.isEmpty()); - -// Mockito.verify(courierRepository).findCourierPerformanceBetweenDates( -// expectedStartDateTime, expectedEndDateTime); -// } - -// @Test -// void getCourierSuccessRate_shouldReturnCorrectRates() { -// // Arrange -// // Jane: 8 completed out of 10 deliveries = 80% -// UUID courierId1 = UUID.randomUUID(); -// UUID courierId2 = UUID.randomUUID(); -// UUID courierId3 = UUID.randomUUID(); -// CourierPerformanceProjection janeData = createMockProjection( -// courierId1, "Jane", 10L, 8L, new BigDecimal("4.5")); -// // John: 5 completed out of 5 deliveries = 100% -// CourierPerformanceProjection johnData = createMockProjection( -// courierId2, "John", 5L, 5L, new BigDecimal("4.0")); -// // Peter: 0 completed out of 2 deliveries = 0% -// CourierPerformanceProjection peterData = createMockProjection( -// courierId3, "Peter", 2L, 0L, new BigDecimal("3.0")); - -// when(courierRepository.findCourierPerformanceBetweenDates( -// any(LocalDateTime.class), any(LocalDateTime.class))) -// .thenReturn(Arrays.asList(janeData, johnData, peterData)); - -// // Act -// List result = courierAnalyticsService -// .getCourierSuccessRate(testStartDate, testEndDate); - -// // Assert -// assertNotNull(result); -// assertEquals(3, result.size()); - -// CourierSuccessRateResponse janeResponse = result.stream() -// .filter(r -> r.getCourierName().equals("Jane")) -// .findFirst().orElse(null); -// assertNotNull(janeResponse); -// assertEquals(courierId1, janeResponse.getCourierId()); -// assertTrue(janeResponse.getSuccessRate().compareTo(new BigDecimal("0.80")) == 0); - -// CourierSuccessRateResponse johnResponse = result.stream() -// .filter(r -> r.getCourierName().equals("John")) -// .findFirst().orElse(null); -// assertNotNull(johnResponse); -// assertEquals(courierId2, johnResponse.getCourierId()); -// assertTrue(johnResponse.getSuccessRate().compareTo(new BigDecimal("1.00")) == 0); - -// CourierSuccessRateResponse peterResponse = result.stream() -// .filter(r -> r.getCourierName().equals("Peter")) -// .findFirst().orElse(null); -// assertNotNull(peterResponse); -// assertEquals(courierId3, peterResponse.getCourierId()); -// assertTrue(peterResponse.getSuccessRate().compareTo(new BigDecimal("0.00")) == 0); - -// Mockito.verify(courierRepository).findCourierPerformanceBetweenDates( -// expectedStartDateTime, expectedEndDateTime); -// } - -// @Test -// void getCourierSuccessRate_shouldHandleZeroDeliveryCountGracefully() { -// // Arrange -// // Mark: 0 completed out of 0 deliveries. MetricCalculator should handle this -// // (e.g., return 0 or null) -// UUID MarkId = UUID.randomUUID(); -// CourierPerformanceProjection markData = createMockProjection( -// MarkId, "Mark", 0L, 0L, new BigDecimal("0.0")); - -// when(courierRepository.findCourierPerformanceBetweenDates( -// any(LocalDateTime.class), any(LocalDateTime.class))) -// .thenReturn(Collections.singletonList(markData)); - -// // Act -// List result = courierAnalyticsService -// .getCourierSuccessRate(testStartDate, testEndDate); - -// // Assert -// assertNotNull(result); -// assertEquals(1, result.size()); -// CourierSuccessRateResponse markResponse = result.get(0); -// assertEquals(MarkId, markResponse.getCourierId()); -// assertTrue(markResponse.getSuccessRate().compareTo(new BigDecimal("0.00")) == 0); - -// Mockito.verify(courierRepository).findCourierPerformanceBetweenDates( -// expectedStartDateTime, expectedEndDateTime); -// } - -// @Test -// void getCourierSuccessRate_shouldReturnEmptyListWhenNoData() { -// // Arrange -// when(courierRepository.findCourierPerformanceBetweenDates( -// any(LocalDateTime.class), any(LocalDateTime.class))) -// .thenReturn(Collections.emptyList()); - -// // Act -// List result = courierAnalyticsService -// .getCourierSuccessRate(testStartDate, testEndDate); - -// // Assert -// assertNotNull(result); -// assertTrue(result.isEmpty()); - -// Mockito.verify(courierRepository).findCourierPerformanceBetweenDates( -// expectedStartDateTime, expectedEndDateTime); -// } - -// @Test -// void getCourierAverageRating_shouldReturnCorrectAverageRatings() { -// UUID courierId1 = UUID.randomUUID(); -// UUID courierId2 = UUID.randomUUID(); -// UUID courierId3 = UUID.randomUUID(); -// // Arrange -// CourierPerformanceProjection janeData = createMockProjection( -// courierId1, "Jane", 10L, 8L, new BigDecimal("4.5")); -// CourierPerformanceProjection johnData = createMockProjection( -// courierId2, "John", 5L, 5L, new BigDecimal("4.0")); -// // Peter: No rating available or 0.0 rating (depends on projection and database) -// CourierPerformanceProjection peterData = createMockProjection( -// courierId3, "Peter", 2L, 0L, null); // Assuming null for no rating - -// when(courierRepository.findCourierPerformanceBetweenDates( -// any(LocalDateTime.class), any(LocalDateTime.class))) -// .thenReturn(Arrays.asList(janeData, johnData, peterData)); - -// // Act -// List result = courierAnalyticsService -// .getCourierAverageRating(testStartDate, testEndDate); - -// // Assert -// assertNotNull(result); -// assertEquals(3, result.size()); - -// CourierAverageRatingResponse janeResponse = result.stream() -// .filter(r -> r.getCourierName().equals("Jane")) -// .findFirst().orElse(null); -// assertNotNull(janeResponse); -// assertEquals(courierId1, janeResponse.getCourierId()); -// assertEquals(new BigDecimal("4.5"), janeResponse.getAverageRating()); - -// CourierAverageRatingResponse johnResponse = result.stream() -// .filter(r -> r.getCourierName().equals("John")) -// .findFirst().orElse(null); -// assertNotNull(johnResponse); -// assertEquals(courierId2, johnResponse.getCourierId()); -// assertEquals(new BigDecimal("4.0"), johnResponse.getAverageRating()); - -// CourierAverageRatingResponse peterResponse = result.stream() -// .filter(r -> r.getCourierName().equals("Peter")) -// .findFirst().orElse(null); -// assertNotNull(peterResponse); -// assertNull(peterResponse.getAverageRating()); - -// Mockito.verify(courierRepository).findCourierPerformanceBetweenDates( -// expectedStartDateTime, expectedEndDateTime); -// } - -// @Test -// void getCourierAverageRating_shouldReturnEmptyListWhenNoData() { -// // Arrange -// when(courierRepository.findCourierPerformanceBetweenDates( -// any(LocalDateTime.class), any(LocalDateTime.class))) -// .thenReturn(Collections.emptyList()); - -// // Act -// List result = courierAnalyticsService -// .getCourierAverageRating(testStartDate, testEndDate); - -// // Assert -// assertNotNull(result); -// assertTrue(result.isEmpty()); - -// Mockito.verify(courierRepository).findCourierPerformanceBetweenDates( -// expectedStartDateTime, expectedEndDateTime); -// } - -// @Test -// void getCourierPerformanceReport_shouldReturnComprehensiveReport() { -// // Arrange -// // Jane: 8 completed out of 10 deliveries = 80%, Avg Rating 4.5 -// UUID courierId1 = UUID.randomUUID(); -// UUID courierId2 = UUID.randomUUID(); -// CourierPerformanceProjection janeData = createMockProjection( -// courierId1, "Jane", 10L, 8L, new BigDecimal("4.5")); -// // John: 5 completed out of 5 deliveries = 100%, Avg Rating 4.0 -// CourierPerformanceProjection johnData = createMockProjection( -// courierId2, "John", 5L, 5L, new BigDecimal("4.0")); - -// when(courierRepository.findCourierPerformanceBetweenDates( -// any(LocalDateTime.class), any(LocalDateTime.class))) -// .thenReturn(Arrays.asList(janeData, johnData)); - -// // Act -// List result = courierAnalyticsService -// .getCourierPerformanceReport(testStartDate, testEndDate); - -// // Assert -// assertNotNull(result); -// assertEquals(2, result.size()); - -// CourierPerformanceReportResponse janeResponse = result.stream() -// .filter(r -> r.getCourierName().equals("Jane")) -// .findFirst().orElse(null); -// assertNotNull(janeResponse); -// assertEquals(courierId1, janeResponse.getCourierId()); -// assertEquals(10, janeResponse.getDeliveryCount()); -// assertTrue(janeResponse.getSuccessRate().compareTo(new BigDecimal("0.80")) == 0); -// assertTrue(janeResponse.getAverageRating().compareTo(new BigDecimal("4.5")) == 0); - -// CourierPerformanceReportResponse johnResponse = result.stream() -// .filter(r -> r.getCourierName().equals("John")) -// .findFirst().orElse(null); -// assertNotNull(johnResponse); -// assertEquals(courierId2, johnResponse.getCourierId()); -// assertEquals(5, johnResponse.getDeliveryCount()); -// assertTrue(johnResponse.getSuccessRate().compareTo(new BigDecimal("1.00")) == 0); -// assertTrue(johnResponse.getAverageRating().compareTo(new BigDecimal("4.0")) == 0); - -// Mockito.verify(courierRepository).findCourierPerformanceBetweenDates( -// expectedStartDateTime, expectedEndDateTime); -// } - -// @Test -// void getCourierPerformanceReport_shouldReturnEmptyListWhenNoData() { -// // Arrange -// when(courierRepository.findCourierPerformanceBetweenDates( -// any(LocalDateTime.class), any(LocalDateTime.class))) -// .thenReturn(Collections.emptyList()); - -// // Act -// List result = courierAnalyticsService -// .getCourierPerformanceReport(testStartDate, testEndDate); - -// // Assert -// assertNotNull(result); -// assertTrue(result.isEmpty()); - -// Mockito.verify(courierRepository).findCourierPerformanceBetweenDates( -// expectedStartDateTime, expectedEndDateTime); -// } -// } \ No newline at end of file +package com.Podzilla.analytics.services; + +import com.Podzilla.analytics.api.dtos.courier.CourierAverageRatingResponse; +import com.Podzilla.analytics.api.dtos.courier.CourierDeliveryCountResponse; +import com.Podzilla.analytics.api.dtos.courier.CourierPerformanceReportResponse; +import com.Podzilla.analytics.api.dtos.courier.CourierSuccessRateResponse; +import com.Podzilla.analytics.api.projections.courier.CourierPerformanceProjection; +import com.Podzilla.analytics.repositories.CourierRepository; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +public class CourierAnalyticsServiceTest { + + @Mock + private CourierRepository courierRepository; + + @InjectMocks + private CourierAnalyticsService courierAnalyticsService; + + private LocalDate testStartDate; + private LocalDate testEndDate; + private LocalDateTime expectedStartDateTime; + private LocalDateTime expectedEndDateTime; + + @BeforeEach + void setUp() { + testStartDate = LocalDate.of(2024, 1, 1); + testEndDate = LocalDate.of(2024, 1, 31); + expectedStartDateTime = testStartDate.atStartOfDay(); + expectedEndDateTime = testEndDate.atTime(LocalTime.MAX); + } + + private CourierPerformanceProjection createMockProjection( + UUID courierId, String courierName, Long deliveryCount, Long completedCount, BigDecimal averageRating) { + CourierPerformanceProjection mockProjection = Mockito.mock(CourierPerformanceProjection.class); + Mockito.lenient().when(mockProjection.getCourierId()).thenReturn(courierId); + Mockito.lenient().when(mockProjection.getCourierName()).thenReturn(courierName); + Mockito.lenient().when(mockProjection.getDeliveryCount()).thenReturn(deliveryCount); + Mockito.lenient().when(mockProjection.getCompletedCount()).thenReturn(completedCount); + Mockito.lenient().when(mockProjection.getAverageRating()).thenReturn(averageRating); + return mockProjection; + } + + @Test + void getCourierDeliveryCounts_shouldReturnCorrectCountsForMultipleCouriers() { + // Arrange + UUID courierId1 = UUID.randomUUID(); + UUID courierId2 = UUID.randomUUID(); + CourierPerformanceProjection janeData = createMockProjection( + courierId1, "Jane", 10L, 8L, new BigDecimal("4.5")); + CourierPerformanceProjection johnData = createMockProjection( + courierId2, "John", 5L, 5L, new BigDecimal("4.0")); + + when(courierRepository.findCourierPerformanceBetweenDates( + any(LocalDateTime.class), any(LocalDateTime.class))) + .thenReturn(Arrays.asList(janeData, johnData)); + + // Act + List result = courierAnalyticsService + .getCourierDeliveryCounts(testStartDate, testEndDate); + + // Assert + assertNotNull(result); + assertEquals(2, result.size()); + + CourierDeliveryCountResponse janeResponse = result.stream() + .filter(r -> r.getCourierName().equals("Jane")) + .findFirst().orElse(null); + assertNotNull(janeResponse); + assertEquals(courierId1, janeResponse.getCourierId()); + assertEquals(10, janeResponse.getDeliveryCount()); + + CourierDeliveryCountResponse johnResponse = result.stream() + .filter(r -> r.getCourierName().equals("John")) + .findFirst().orElse(null); + assertNotNull(johnResponse); + assertEquals(courierId2, johnResponse.getCourierId()); + assertEquals(5, johnResponse.getDeliveryCount()); + + // Verify repository method was called with correct LocalDateTime arguments + Mockito.verify(courierRepository).findCourierPerformanceBetweenDates( + expectedStartDateTime, expectedEndDateTime); + } + + @Test + void getCourierDeliveryCounts_shouldReturnEmptyListWhenNoData() { + // Arrange + when(courierRepository.findCourierPerformanceBetweenDates( + any(LocalDateTime.class), any(LocalDateTime.class))) + .thenReturn(Collections.emptyList()); + + // Act + List result = courierAnalyticsService + .getCourierDeliveryCounts(testStartDate, testEndDate); + + // Assert + assertNotNull(result); + assertTrue(result.isEmpty()); + + Mockito.verify(courierRepository).findCourierPerformanceBetweenDates( + expectedStartDateTime, expectedEndDateTime); + } + + @Test + void getCourierSuccessRate_shouldReturnCorrectRates() { + // Arrange + // Jane: 8 completed out of 10 deliveries = 80% + UUID courierId1 = UUID.randomUUID(); + UUID courierId2 = UUID.randomUUID(); + UUID courierId3 = UUID.randomUUID(); + CourierPerformanceProjection janeData = createMockProjection( + courierId1, "Jane", 10L, 8L, new BigDecimal("4.5")); + // John: 5 completed out of 5 deliveries = 100% + CourierPerformanceProjection johnData = createMockProjection( + courierId2, "John", 5L, 5L, new BigDecimal("4.0")); + // Peter: 0 completed out of 2 deliveries = 0% + CourierPerformanceProjection peterData = createMockProjection( + courierId3, "Peter", 2L, 0L, new BigDecimal("3.0")); + + when(courierRepository.findCourierPerformanceBetweenDates( + any(LocalDateTime.class), any(LocalDateTime.class))) + .thenReturn(Arrays.asList(janeData, johnData, peterData)); + + // Act + List result = courierAnalyticsService + .getCourierSuccessRate(testStartDate, testEndDate); + + // Assert + assertNotNull(result); + assertEquals(3, result.size()); + + CourierSuccessRateResponse janeResponse = result.stream() + .filter(r -> r.getCourierName().equals("Jane")) + .findFirst().orElse(null); + assertNotNull(janeResponse); + assertEquals(courierId1, janeResponse.getCourierId()); + assertTrue(janeResponse.getSuccessRate().compareTo(new BigDecimal("0.80")) == 0); + + CourierSuccessRateResponse johnResponse = result.stream() + .filter(r -> r.getCourierName().equals("John")) + .findFirst().orElse(null); + assertNotNull(johnResponse); + assertEquals(courierId2, johnResponse.getCourierId()); + assertTrue(johnResponse.getSuccessRate().compareTo(new BigDecimal("1.00")) == 0); + + CourierSuccessRateResponse peterResponse = result.stream() + .filter(r -> r.getCourierName().equals("Peter")) + .findFirst().orElse(null); + assertNotNull(peterResponse); + assertEquals(courierId3, peterResponse.getCourierId()); + assertTrue(peterResponse.getSuccessRate().compareTo(new BigDecimal("0.00")) == 0); + + Mockito.verify(courierRepository).findCourierPerformanceBetweenDates( + expectedStartDateTime, expectedEndDateTime); + } + + @Test + void getCourierSuccessRate_shouldHandleZeroDeliveryCountGracefully() { + // Arrange + // Mark: 0 completed out of 0 deliveries. MetricCalculator should handle this + // (e.g., return 0 or null) + UUID MarkId = UUID.randomUUID(); + CourierPerformanceProjection markData = createMockProjection( + MarkId, "Mark", 0L, 0L, new BigDecimal("0.0")); + + when(courierRepository.findCourierPerformanceBetweenDates( + any(LocalDateTime.class), any(LocalDateTime.class))) + .thenReturn(Collections.singletonList(markData)); + + // Act + List result = courierAnalyticsService + .getCourierSuccessRate(testStartDate, testEndDate); + + // Assert + assertNotNull(result); + assertEquals(1, result.size()); + CourierSuccessRateResponse markResponse = result.get(0); + assertEquals(MarkId, markResponse.getCourierId()); + assertTrue(markResponse.getSuccessRate().compareTo(new BigDecimal("0.00")) == 0); + + Mockito.verify(courierRepository).findCourierPerformanceBetweenDates( + expectedStartDateTime, expectedEndDateTime); + } + + @Test + void getCourierSuccessRate_shouldReturnEmptyListWhenNoData() { + // Arrange + when(courierRepository.findCourierPerformanceBetweenDates( + any(LocalDateTime.class), any(LocalDateTime.class))) + .thenReturn(Collections.emptyList()); + + // Act + List result = courierAnalyticsService + .getCourierSuccessRate(testStartDate, testEndDate); + + // Assert + assertNotNull(result); + assertTrue(result.isEmpty()); + + Mockito.verify(courierRepository).findCourierPerformanceBetweenDates( + expectedStartDateTime, expectedEndDateTime); + } + + @Test + void getCourierAverageRating_shouldReturnCorrectAverageRatings() { + UUID courierId1 = UUID.randomUUID(); + UUID courierId2 = UUID.randomUUID(); + UUID courierId3 = UUID.randomUUID(); + // Arrange + CourierPerformanceProjection janeData = createMockProjection( + courierId1, "Jane", 10L, 8L, new BigDecimal("4.5")); + CourierPerformanceProjection johnData = createMockProjection( + courierId2, "John", 5L, 5L, new BigDecimal("4.0")); + // Peter: No rating available or 0.0 rating (depends on projection and database) + CourierPerformanceProjection peterData = createMockProjection( + courierId3, "Peter", 2L, 0L, null); // Assuming null for no rating + + when(courierRepository.findCourierPerformanceBetweenDates( + any(LocalDateTime.class), any(LocalDateTime.class))) + .thenReturn(Arrays.asList(janeData, johnData, peterData)); + + // Act + List result = courierAnalyticsService + .getCourierAverageRating(testStartDate, testEndDate); + + // Assert + assertNotNull(result); + assertEquals(3, result.size()); + + CourierAverageRatingResponse janeResponse = result.stream() + .filter(r -> r.getCourierName().equals("Jane")) + .findFirst().orElse(null); + assertNotNull(janeResponse); + assertEquals(courierId1, janeResponse.getCourierId()); + assertEquals(new BigDecimal("4.5"), janeResponse.getAverageRating()); + + CourierAverageRatingResponse johnResponse = result.stream() + .filter(r -> r.getCourierName().equals("John")) + .findFirst().orElse(null); + assertNotNull(johnResponse); + assertEquals(courierId2, johnResponse.getCourierId()); + assertEquals(new BigDecimal("4.0"), johnResponse.getAverageRating()); + + CourierAverageRatingResponse peterResponse = result.stream() + .filter(r -> r.getCourierName().equals("Peter")) + .findFirst().orElse(null); + assertNotNull(peterResponse); + assertNull(peterResponse.getAverageRating()); + + Mockito.verify(courierRepository).findCourierPerformanceBetweenDates( + expectedStartDateTime, expectedEndDateTime); + } + + @Test + void getCourierAverageRating_shouldReturnEmptyListWhenNoData() { + // Arrange + when(courierRepository.findCourierPerformanceBetweenDates( + any(LocalDateTime.class), any(LocalDateTime.class))) + .thenReturn(Collections.emptyList()); + + // Act + List result = courierAnalyticsService + .getCourierAverageRating(testStartDate, testEndDate); + + // Assert + assertNotNull(result); + assertTrue(result.isEmpty()); + + Mockito.verify(courierRepository).findCourierPerformanceBetweenDates( + expectedStartDateTime, expectedEndDateTime); + } + + @Test + void getCourierPerformanceReport_shouldReturnComprehensiveReport() { + // Arrange + // Jane: 8 completed out of 10 deliveries = 80%, Avg Rating 4.5 + UUID courierId1 = UUID.randomUUID(); + UUID courierId2 = UUID.randomUUID(); + CourierPerformanceProjection janeData = createMockProjection( + courierId1, "Jane", 10L, 8L, new BigDecimal("4.5")); + // John: 5 completed out of 5 deliveries = 100%, Avg Rating 4.0 + CourierPerformanceProjection johnData = createMockProjection( + courierId2, "John", 5L, 5L, new BigDecimal("4.0")); + + when(courierRepository.findCourierPerformanceBetweenDates( + any(LocalDateTime.class), any(LocalDateTime.class))) + .thenReturn(Arrays.asList(janeData, johnData)); + + // Act + List result = courierAnalyticsService + .getCourierPerformanceReport(testStartDate, testEndDate); + + // Assert + assertNotNull(result); + assertEquals(2, result.size()); + + CourierPerformanceReportResponse janeResponse = result.stream() + .filter(r -> r.getCourierName().equals("Jane")) + .findFirst().orElse(null); + assertNotNull(janeResponse); + assertEquals(courierId1, janeResponse.getCourierId()); + assertEquals(10, janeResponse.getDeliveryCount()); + assertTrue(janeResponse.getSuccessRate().compareTo(new BigDecimal("0.80")) == 0); + assertTrue(janeResponse.getAverageRating().compareTo(new BigDecimal("4.5")) == 0); + + CourierPerformanceReportResponse johnResponse = result.stream() + .filter(r -> r.getCourierName().equals("John")) + .findFirst().orElse(null); + assertNotNull(johnResponse); + assertEquals(courierId2, johnResponse.getCourierId()); + assertEquals(5, johnResponse.getDeliveryCount()); + assertTrue(johnResponse.getSuccessRate().compareTo(new BigDecimal("1.00")) == 0); + assertTrue(johnResponse.getAverageRating().compareTo(new BigDecimal("4.0")) == 0); + + Mockito.verify(courierRepository).findCourierPerformanceBetweenDates( + expectedStartDateTime, expectedEndDateTime); + } + + @Test + void getCourierPerformanceReport_shouldReturnEmptyListWhenNoData() { + // Arrange + when(courierRepository.findCourierPerformanceBetweenDates( + any(LocalDateTime.class), any(LocalDateTime.class))) + .thenReturn(Collections.emptyList()); + + // Act + List result = courierAnalyticsService + .getCourierPerformanceReport(testStartDate, testEndDate); + + // Assert + assertNotNull(result); + assertTrue(result.isEmpty()); + + Mockito.verify(courierRepository).findCourierPerformanceBetweenDates( + expectedStartDateTime, expectedEndDateTime); + } +} \ No newline at end of file diff --git a/src/test/java/com/Podzilla/analytics/services/ProductAnalyticsServiceTest.java b/src/test/java/com/Podzilla/analytics/services/ProductAnalyticsServiceTest.java index b31e7da..6d6ecd8 100644 --- a/src/test/java/com/Podzilla/analytics/services/ProductAnalyticsServiceTest.java +++ b/src/test/java/com/Podzilla/analytics/services/ProductAnalyticsServiceTest.java @@ -1,236 +1,238 @@ -// package com.Podzilla.analytics.services; - -// import java.math.BigDecimal; -// import java.time.LocalDate; -// import java.time.LocalDateTime; -// import java.util.Arrays; -// import java.util.Collections; -// import java.util.List; // Keep import if TopSellerRequest still uses LocalDate -// import java.util.UUID; - -// import static org.junit.jupiter.api.Assertions.assertEquals; // Import LocalDateTime -// import static org.junit.jupiter.api.Assertions.assertTrue; -// import org.junit.jupiter.api.BeforeEach; -// import org.junit.jupiter.api.Test; -// import org.junit.jupiter.api.extension.ExtendWith; -// import static org.mockito.ArgumentMatchers.any; -// import static org.mockito.ArgumentMatchers.eq; -// import org.mockito.Mock; -// import static org.mockito.Mockito.when; -// import org.mockito.junit.jupiter.MockitoExtension; - -// import com.Podzilla.analytics.api.dtos.product.TopSellerRequest; -// import com.Podzilla.analytics.api.dtos.product.TopSellerResponse; -// import com.Podzilla.analytics.api.projections.product.TopSellingProductProjection; -// import com.Podzilla.analytics.repositories.ProductRepository; - -// @ExtendWith(MockitoExtension.class) -// class ProductAnalyticsServiceTest { - -// @Mock -// private ProductRepository productRepository; - -// private ProductAnalyticsService productAnalyticsService; - -// @BeforeEach -// void setUp() { -// productAnalyticsService = new ProductAnalyticsService(productRepository); -// } - -// @Test -// void getTopSellers_SortByRevenue_ShouldReturnCorrectList() { -// // Arrange -// // Assuming TopSellerRequest still uses LocalDate for input -// LocalDate requestStartDate = LocalDate.of(2025, 1, 1); -// LocalDate requestEndDate = LocalDate.of(2025, 12, 31); - -// TopSellerRequest request = TopSellerRequest.builder() -// .startDate(requestStartDate) -// .endDate(requestEndDate) -// .limit(2) // Ensure limit is set to 2 -// .sortBy(TopSellerRequest.SortBy.REVENUE) -// .build(); - -// // Convert LocalDate from request to LocalDateTime for repository call -// // Start of the start day -// LocalDateTime startDate = requestStartDate.atStartOfDay(); -// // Start of the day AFTER the end day to include the whole end day in the query -// LocalDateTime endDate = requestEndDate.plusDays(1).atStartOfDay(); - -// UUID productId1 = UUID.randomUUID(); -// UUID productId2 = UUID.randomUUID(); -// // Mocking the repository to return 2 projections -// List projections = Arrays.asList( -// createProjection(productId1, "iPhone", "Electronics", new BigDecimal("1000.00"), 5L), -// createProjection(productId2, "MacBook", "Electronics", new BigDecimal("2000.00"), 2L) -// ); - -// // Ensure the mock returns the correct results based on the given arguments -// // Use LocalDateTime for the eq() matchers -// when(productRepository.findTopSellers( -// eq(startDate), -// eq(endDate), -// eq(2), -// eq("REVENUE"))) -// .thenReturn(projections); - -// // Act -// List result = productAnalyticsService.getTopSellers(request.getStartDate(), request.getEndDate(), request.getLimit(), request.getSortBy()); - -// // Log the result to help with debugging -// result.forEach(item -> System.out.println("Product ID: " + item.getProductId() + " Revenue: " + item.getValue())); - -// // Assert (Ensure the order is correct as per revenue) -// assertEquals(2, result.size(), "Expected 2 products in the list."); -// assertEquals(productId2, result.get(0).getProductId()); // MacBook should come first due to higher revenue -// assertEquals("MacBook", result.get(0).getProductName()); -// assertEquals("Electronics", result.get(0).getCategory()); -// assertEquals(new BigDecimal("2000.00"), result.get(0).getValue()); - -// assertEquals(productId1, result.get(1).getProductId()); -// assertEquals("iPhone", result.get(1).getProductName()); -// assertEquals("Electronics", result.get(1).getCategory()); -// assertEquals(new BigDecimal("1000.00"), result.get(1).getValue()); -// } - - -// @Test -// void getTopSellers_SortByUnits_ShouldReturnCorrectList() { -// // Arrange -// LocalDate requestStartDate = LocalDate.of(2025, 1, 1); -// LocalDate requestEndDate = LocalDate.of(2025, 12, 31); - -// TopSellerRequest request = TopSellerRequest.builder() -// .startDate(requestStartDate) -// .endDate(requestEndDate) -// .limit(2) -// .sortBy(TopSellerRequest.SortBy.UNITS) -// .build(); - -// // Convert LocalDate from request to LocalDateTime for repository call -// LocalDateTime startDate = requestStartDate.atStartOfDay(); -// LocalDateTime endDate = requestEndDate.plusDays(1).atStartOfDay(); - -// UUID productId1 = UUID.randomUUID(); -// UUID productId2 = UUID.randomUUID(); -// List projections = Arrays.asList( -// createProjection(productId1, "iPhone", "Electronics", new BigDecimal("1000.00"), 5L), -// createProjection(productId2, "MacBook", "Electronics", new BigDecimal("2000.00"), 2L) -// ); - -// // Use LocalDateTime for the eq() matchers -// when(productRepository.findTopSellers( -// eq(startDate), -// eq(endDate), -// eq(2), -// eq("UNITS"))) -// .thenReturn(projections); - -// // Act -// List result = productAnalyticsService.getTopSellers(request.getStartDate(), request.getEndDate(), request.getLimit(), request.getSortBy()); - -// // Assert (Ensure the order is correct as per units) -// assertEquals(2, result.size()); -// assertEquals(productId1, result.get(0).getProductId()); // iPhone comes first because of more units sold -// assertEquals("iPhone", result.get(0).getProductName()); -// assertEquals("Electronics", result.get(0).getCategory()); -// // Note: The projection returns revenue and units as BigDecimal and Long respectively. -// assertEquals(new BigDecimal("5"), result.get(0).getValue()); - - -// assertEquals(productId2, result.get(1).getProductId()); -// assertEquals("MacBook", result.get(1).getProductName()); -// assertEquals("Electronics", result.get(1).getCategory()); -// assertEquals(new BigDecimal("2"), result.get(1).getValue()); -// } - -// @Test -// void getTopSellers_WithEmptyResult_ShouldReturnEmptyList() { -// // Arrange -// LocalDate requestStartDate = LocalDate.of(2025, 1, 1); -// LocalDate requestEndDate = LocalDate.of(2025, 12, 31); - -// TopSellerRequest request = TopSellerRequest.builder() -// .startDate(requestStartDate) -// .endDate(requestEndDate) -// .limit(10) -// .sortBy(TopSellerRequest.SortBy.REVENUE) -// .build(); - -// // Use any() matchers for LocalDateTime parameters -// when(productRepository.findTopSellers(any(LocalDateTime.class), any(LocalDateTime.class), any(), any())) -// .thenReturn(Collections.emptyList()); - -// // Act -// List result = productAnalyticsService.getTopSellers(request.getStartDate(), request.getEndDate(), request.getLimit(), request.getSortBy()); - -// // Assert -// assertTrue(result.isEmpty()); -// } - - -// @Test -// void getTopSellers_WithZeroLimit_ShouldReturnEmptyList() { -// // Arrange -// LocalDate requestStartDate = LocalDate.of(2025, 1, 1); -// LocalDate requestEndDate = LocalDate.of(2025, 12, 31); -// TopSellerRequest request = TopSellerRequest.builder() -// .startDate(requestStartDate) -// .endDate(requestEndDate) -// .limit(0) -// .sortBy(TopSellerRequest.SortBy.REVENUE) -// .build(); - -// // Convert LocalDate from request to LocalDateTime for repository call -// LocalDateTime startDate = requestStartDate.atStartOfDay(); -// LocalDateTime endDate = requestEndDate.plusDays(1).atStartOfDay(); - - -// // Use LocalDateTime for the eq() matchers -// when(productRepository.findTopSellers( -// eq(startDate), -// eq(endDate), -// eq(0), -// eq("REVENUE"))) -// .thenReturn(Collections.emptyList()); - -// // Act -// List result = productAnalyticsService.getTopSellers(request.getStartDate(), request.getEndDate(), request.getLimit(), request.getSortBy()); - -// // Assert -// assertTrue(result.isEmpty()); -// } - -// private TopSellingProductProjection createProjection( -// final UUID id, -// final String name, -// final String category, -// final BigDecimal revenue, -// final Long units) { -// return new TopSellingProductProjection() { - -// @Override -// public UUID getId() { -// return id; -// } - - -// @Override -// public String getName() { -// return name; -// } -// @Override -// public String getCategory() { -// return category; -// } -// @Override -// public BigDecimal getTotalRevenue() { -// return revenue; -// } -// @Override -// public Long getTotalUnits() { -// return units; -// } -// }; -// } -// } \ No newline at end of file +package com.Podzilla.analytics.services; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; // Keep import if TopSellerRequest still uses LocalDate +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.assertEquals; // Import LocalDateTime +import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import org.mockito.Mock; +import static org.mockito.Mockito.when; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.Podzilla.analytics.api.dtos.product.TopSellerRequest; +import com.Podzilla.analytics.api.dtos.product.TopSellerResponse; +import com.Podzilla.analytics.api.projections.product.TopSellingProductProjection; +import com.Podzilla.analytics.repositories.ProductRepository; + +@ExtendWith(MockitoExtension.class) +class ProductAnalyticsServiceTest { + + @Mock + private ProductRepository productRepository; + + private ProductAnalyticsService productAnalyticsService; + + @BeforeEach + void setUp() { + productAnalyticsService = new ProductAnalyticsService(productRepository); + } + + @Test + void getTopSellers_SortByRevenue_ShouldReturnCorrectList() { + // Arrange + // Assuming TopSellerRequest still uses LocalDate for input + LocalDate requestStartDate = LocalDate.of(2025, 1, 1); + LocalDate requestEndDate = LocalDate.of(2025, 12, 31); + + TopSellerRequest request = TopSellerRequest.builder() + .startDate(requestStartDate) + .endDate(requestEndDate) + .limit(2) // Ensure limit is set to 2 + .sortBy(TopSellerRequest.SortBy.REVENUE) + .build(); + + // Convert LocalDate from request to LocalDateTime for repository call + // Start of the start day + LocalDateTime startDate = requestStartDate.atStartOfDay(); + // Start of the day AFTER the end day to include the whole end day in the query + LocalDateTime endDate = requestEndDate.plusDays(1).atStartOfDay(); + + UUID productId1 = UUID.randomUUID(); + UUID productId2 = UUID.randomUUID(); + // Mocking the repository to return 2 projections + List projections = Arrays.asList( + createProjection(productId1, "iPhone", "Electronics", new BigDecimal("1000.00"), 5L), + createProjection(productId2, "MacBook", "Electronics", new BigDecimal("2000.00"), 2L)); + + // Ensure the mock returns the correct results based on the given arguments + // Use LocalDateTime for the eq() matchers + when(productRepository.findTopSellers( + eq(startDate), + eq(endDate), + eq(2), + eq("REVENUE"))) + .thenReturn(projections); + + // Act + List result = productAnalyticsService.getTopSellers(request.getStartDate(), + request.getEndDate(), request.getLimit(), request.getSortBy()); + + // Log the result to help with debugging + result.forEach( + item -> System.out.println("Product ID: " + item.getProductId() + " Revenue: " + item.getValue())); + + // Assert (Ensure the order is correct as per revenue) + assertEquals(2, result.size(), "Expected 2 products in the list."); + assertEquals(productId2, result.get(0).getProductId()); // MacBook should come first due to higher revenue + assertEquals("MacBook", result.get(0).getProductName()); + assertEquals("Electronics", result.get(0).getCategory()); + assertEquals(new BigDecimal("2000.00"), result.get(0).getValue()); + + assertEquals(productId1, result.get(1).getProductId()); + assertEquals("iPhone", result.get(1).getProductName()); + assertEquals("Electronics", result.get(1).getCategory()); + assertEquals(new BigDecimal("1000.00"), result.get(1).getValue()); + } + + @Test + void getTopSellers_SortByUnits_ShouldReturnCorrectList() { + // Arrange + LocalDate requestStartDate = LocalDate.of(2025, 1, 1); + LocalDate requestEndDate = LocalDate.of(2025, 12, 31); + + TopSellerRequest request = TopSellerRequest.builder() + .startDate(requestStartDate) + .endDate(requestEndDate) + .limit(2) + .sortBy(TopSellerRequest.SortBy.UNITS) + .build(); + + // Convert LocalDate from request to LocalDateTime for repository call + LocalDateTime startDate = requestStartDate.atStartOfDay(); + LocalDateTime endDate = requestEndDate.plusDays(1).atStartOfDay(); + + UUID productId1 = UUID.randomUUID(); + UUID productId2 = UUID.randomUUID(); + List projections = Arrays.asList( + createProjection(productId1, "iPhone", "Electronics", new BigDecimal("1000.00"), 5L), + createProjection(productId2, "MacBook", "Electronics", new BigDecimal("2000.00"), 2L)); + + // Use LocalDateTime for the eq() matchers + when(productRepository.findTopSellers( + eq(startDate), + eq(endDate), + eq(2), + eq("UNITS"))) + .thenReturn(projections); + + // Act + List result = productAnalyticsService.getTopSellers(request.getStartDate(), + request.getEndDate(), request.getLimit(), request.getSortBy()); + + // Assert (Ensure the order is correct as per units) + assertEquals(2, result.size()); + assertEquals(productId1, result.get(0).getProductId()); // iPhone comes first because of more units sold + assertEquals("iPhone", result.get(0).getProductName()); + assertEquals("Electronics", result.get(0).getCategory()); + // Note: The projection returns revenue and units as BigDecimal and Long + // respectively. + assertEquals(new BigDecimal("5"), result.get(0).getValue()); + + assertEquals(productId2, result.get(1).getProductId()); + assertEquals("MacBook", result.get(1).getProductName()); + assertEquals("Electronics", result.get(1).getCategory()); + assertEquals(new BigDecimal("2"), result.get(1).getValue()); + } + + @Test + void getTopSellers_WithEmptyResult_ShouldReturnEmptyList() { + // Arrange + LocalDate requestStartDate = LocalDate.of(2025, 1, 1); + LocalDate requestEndDate = LocalDate.of(2025, 12, 31); + + TopSellerRequest request = TopSellerRequest.builder() + .startDate(requestStartDate) + .endDate(requestEndDate) + .limit(10) + .sortBy(TopSellerRequest.SortBy.REVENUE) + .build(); + + // Use any() matchers for LocalDateTime parameters + when(productRepository.findTopSellers(any(LocalDateTime.class), any(LocalDateTime.class), any(), any())) + .thenReturn(Collections.emptyList()); + + // Act + List result = productAnalyticsService.getTopSellers(request.getStartDate(), + request.getEndDate(), request.getLimit(), request.getSortBy()); + + // Assert + assertTrue(result.isEmpty()); + } + + @Test + void getTopSellers_WithZeroLimit_ShouldReturnEmptyList() { + // Arrange + LocalDate requestStartDate = LocalDate.of(2025, 1, 1); + LocalDate requestEndDate = LocalDate.of(2025, 12, 31); + TopSellerRequest request = TopSellerRequest.builder() + .startDate(requestStartDate) + .endDate(requestEndDate) + .limit(0) + .sortBy(TopSellerRequest.SortBy.REVENUE) + .build(); + + // Convert LocalDate from request to LocalDateTime for repository call + LocalDateTime startDate = requestStartDate.atStartOfDay(); + LocalDateTime endDate = requestEndDate.plusDays(1).atStartOfDay(); + + // Use LocalDateTime for the eq() matchers + when(productRepository.findTopSellers( + eq(startDate), + eq(endDate), + eq(0), + eq("REVENUE"))) + .thenReturn(Collections.emptyList()); + + // Act + List result = productAnalyticsService.getTopSellers(request.getStartDate(), + request.getEndDate(), request.getLimit(), request.getSortBy()); + + // Assert + assertTrue(result.isEmpty()); + } + + private TopSellingProductProjection createProjection( + final UUID id, + final String name, + final String category, + final BigDecimal revenue, + final Long units) { + return new TopSellingProductProjection() { + + @Override + public UUID getId() { + return id; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getCategory() { + return category; + } + + @Override + public BigDecimal getTotalRevenue() { + return revenue; + } + + @Override + public Long getTotalUnits() { + return units; + } + }; + } +} \ No newline at end of file diff --git a/src/test/java/com/Podzilla/analytics/services/RevenueReportServiceTest.java b/src/test/java/com/Podzilla/analytics/services/RevenueReportServiceTest.java index 8f4e799..f007740 100644 --- a/src/test/java/com/Podzilla/analytics/services/RevenueReportServiceTest.java +++ b/src/test/java/com/Podzilla/analytics/services/RevenueReportServiceTest.java @@ -1,211 +1,221 @@ -// package com.Podzilla.analytics.services; - -// import static org.mockito.ArgumentMatchers.any; -// import static org.mockito.ArgumentMatchers.eq; -// import static org.mockito.Mockito.when; -// import static org.junit.jupiter.api.Assertions.*; - -// import java.math.BigDecimal; -// import java.time.LocalDate; -// import java.util.Arrays; -// import java.util.Collections; -// import java.util.List; - -// import org.junit.jupiter.api.BeforeEach; -// import org.junit.jupiter.api.Test; -// import org.junit.jupiter.api.extension.ExtendWith; -// import org.mockito.Mock; -// import org.mockito.junit.jupiter.MockitoExtension; - -// import com.Podzilla.analytics.api.dtos.revenue.RevenueByCategoryResponse; -// import com.Podzilla.analytics.api.dtos.revenue.RevenueSummaryRequest; -// import com.Podzilla.analytics.api.dtos.revenue.RevenueSummaryResponse; -// import com.Podzilla.analytics.api.projections.revenue.RevenueByCategoryProjection; -// import com.Podzilla.analytics.api.projections.revenue.RevenueSummaryProjection; -// import com.Podzilla.analytics.repositories.OrderRepository; - -// @ExtendWith(MockitoExtension.class) -// class RevenueReportServiceTest { - -// @Mock -// private OrderRepository orderRepository; - -// private RevenueReportService revenueReportService; - -// @BeforeEach -// void setUp() { -// revenueReportService = new RevenueReportService(orderRepository); -// } - -// @Test -// void getRevenueSummary_WithValidData_ShouldReturnCorrectSummary() { -// // Arrange -// LocalDate startDate = LocalDate.of(2025, 1, 1); -// LocalDate endDate = LocalDate.of(2025, 12, 31); -// RevenueSummaryRequest request = RevenueSummaryRequest.builder() -// .startDate(startDate) -// .endDate(endDate) -// .period(RevenueSummaryRequest.Period.MONTHLY) -// .build(); - -// List projections = Arrays.asList( -// summaryProjection(LocalDate.of(2025, 1, 1), new BigDecimal("1000.00")), -// summaryProjection(LocalDate.of(2025, 2, 1), new BigDecimal("2000.00")) -// ); - -// when(orderRepository.findRevenueSummaryByPeriod(eq(startDate), eq(endDate), eq("MONTHLY"))) -// .thenReturn(projections); - -// // Act -// List result = revenueReportService.getRevenueSummary(request.getStartDate(), -// request.getEndDate(), request.getPeriod().name()); - -// // Assert -// assertEquals(2, result.size()); -// assertEquals(LocalDate.of(2025, 1, 1), result.get(0).getPeriodStartDate()); -// assertEquals(new BigDecimal("1000.00"), result.get(0).getTotalRevenue()); -// assertEquals(LocalDate.of(2025, 2, 1), result.get(1).getPeriodStartDate()); -// assertEquals(new BigDecimal("2000.00"), result.get(1).getTotalRevenue()); -// } - -// @Test -// void getRevenueSummary_WithEmptyData_ShouldReturnEmptyList() { -// // Arrange -// LocalDate startDate = LocalDate.of(2025, 1, 1); -// LocalDate endDate = LocalDate.of(2025, 12, 31); -// RevenueSummaryRequest request = RevenueSummaryRequest.builder() -// .startDate(startDate) -// .endDate(endDate) -// .period(RevenueSummaryRequest.Period.MONTHLY) -// .build(); - -// when(orderRepository.findRevenueSummaryByPeriod(any(), any(), any())) -// .thenReturn(Collections.emptyList()); - -// // Act -// List result = revenueReportService.getRevenueSummary(request.getStartDate(), -// request.getEndDate(), request.getPeriod().name()); - -// // Assert -// assertTrue(result.isEmpty()); -// } - -// @Test -// void getRevenueSummary_WithStartDateAfterEndDate_ShouldReturnEmptyList() { -// // Arrange -// LocalDate startDate = LocalDate.of(2025, 12, 31); -// LocalDate endDate = LocalDate.of(2025, 1, 1); -// RevenueSummaryRequest request = RevenueSummaryRequest.builder() -// .startDate(startDate) -// .endDate(endDate) -// .period(RevenueSummaryRequest.Period.MONTHLY) -// .build(); - -// when(orderRepository.findRevenueSummaryByPeriod(any(), any(), any())) -// .thenReturn(Collections.emptyList()); - -// // Act -// List result = revenueReportService.getRevenueSummary(request.getStartDate(), -// request.getEndDate(), request.getPeriod().name()); - -// // Assert -// assertTrue(result.isEmpty()); -// } - -// @Test -// void getRevenueByCategory_WithValidData_ShouldReturnCorrectCategories() { -// // Arrange -// LocalDate startDate = LocalDate.of(2025, 1, 1); -// LocalDate endDate = LocalDate.of(2025, 12, 31); List projections = Arrays.asList( -// categoryProjection("Books", new BigDecimal("3000.00")), -// categoryProjection("Electronics", new BigDecimal("5000.00")) -// ); - -// when(orderRepository.findRevenueByCategory(eq(startDate), eq(endDate))) -// .thenReturn(projections);// Act -// List result = revenueReportService.getRevenueByCategory(startDate, endDate); - -// // Assert -// assertEquals(2, result.size()); -// assertEquals("Books", result.get(0).getCategory()); -// assertEquals(new BigDecimal("3000.00"), result.get(0).getTotalRevenue()); -// assertEquals("Electronics", result.get(1).getCategory()); -// assertEquals(new BigDecimal("5000.00"), result.get(1).getTotalRevenue()); -// } - -// @Test -// void getRevenueByCategory_WithEmptyData_ShouldReturnEmptyList() { -// // Arrange -// LocalDate startDate = LocalDate.of(2025, 1, 1); -// LocalDate endDate = LocalDate.of(2025, 12, 31); - -// when(orderRepository.findRevenueByCategory(any(), any())) -// .thenReturn(Collections.emptyList()); - -// // Act -// List result = revenueReportService.getRevenueByCategory(startDate, endDate); - -// // Assert -// assertTrue(result.isEmpty()); -// } - -// @Test -// void getRevenueByCategory_WithNullRevenue_ShouldHandleGracefully() { -// // Arrange -// LocalDate startDate = LocalDate.of(2025, 1, 1); -// LocalDate endDate = LocalDate.of(2025, 12, 31); - -// List projections = Arrays.asList( -// new RevenueByCategoryProjection() { -// @Override -// public String getCategory() { -// return "Electronics"; -// } -// @Override -// public BigDecimal getTotalRevenue() { -// return null; -// } -// } -// ); - -// when(orderRepository.findRevenueByCategory(eq(startDate), eq(endDate))) -// .thenReturn(projections); - -// // Act -// List result = revenueReportService.getRevenueByCategory(startDate, endDate); - -// // Assert -// assertEquals(1, result.size()); -// assertEquals("Electronics", result.get(0).getCategory()); -// assertNull(result.get(0).getTotalRevenue()); -// } - -// @Test -// void getRevenueByCategory_WithStartDateAfterEndDate_ShouldReturnEmptyList() { -// // Arrange -// LocalDate startDate = LocalDate.of(2025, 12, 31); -// LocalDate endDate = LocalDate.of(2025, 1, 1); - -// when(orderRepository.findRevenueByCategory(any(), any())) -// .thenReturn(Collections.emptyList()); - -// // Act -// List result = revenueReportService.getRevenueByCategory(startDate, endDate); - -// // Assert -// assertTrue(result.isEmpty()); -// } -// private RevenueSummaryProjection summaryProjection(LocalDate date, BigDecimal revenue) { -// return new RevenueSummaryProjection() { -// public LocalDate getPeriod() { return date; } -// public BigDecimal getTotalRevenue() { return revenue; } -// }; -// } - -// private RevenueByCategoryProjection categoryProjection(String category, BigDecimal revenue) { -// return new RevenueByCategoryProjection() { -// public String getCategory() { return category; } -// public BigDecimal getTotalRevenue() { return revenue; } -// }; -// } -// } +package com.Podzilla.analytics.services; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; +import static org.junit.jupiter.api.Assertions.*; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.Podzilla.analytics.api.dtos.revenue.RevenueByCategoryResponse; +import com.Podzilla.analytics.api.dtos.revenue.RevenueSummaryRequest; +import com.Podzilla.analytics.api.dtos.revenue.RevenueSummaryResponse; +import com.Podzilla.analytics.api.projections.revenue.RevenueByCategoryProjection; +import com.Podzilla.analytics.api.projections.revenue.RevenueSummaryProjection; +import com.Podzilla.analytics.repositories.OrderRepository; + +@ExtendWith(MockitoExtension.class) +class RevenueReportServiceTest { + + @Mock + private OrderRepository orderRepository; + + private RevenueReportService revenueReportService; + + @BeforeEach + void setUp() { + revenueReportService = new RevenueReportService(orderRepository); + } + + @Test + void getRevenueSummary_WithValidData_ShouldReturnCorrectSummary() { + // Arrange + LocalDate startDate = LocalDate.of(2025, 1, 1); + LocalDate endDate = LocalDate.of(2025, 12, 31); + RevenueSummaryRequest request = RevenueSummaryRequest.builder() + .startDate(startDate) + .endDate(endDate) + .period(RevenueSummaryRequest.Period.MONTHLY) + .build(); + + List projections = Arrays.asList( + summaryProjection(LocalDate.of(2025, 1, 1), new BigDecimal("1000.00")), + summaryProjection(LocalDate.of(2025, 2, 1), new BigDecimal("2000.00"))); + + when(orderRepository.findRevenueSummaryByPeriod(eq(startDate), eq(endDate), eq("MONTHLY"))) + .thenReturn(projections); + + // Act + List result = revenueReportService.getRevenueSummary(request.getStartDate(), + request.getEndDate(), request.getPeriod().name()); + + // Assert + assertEquals(2, result.size()); + assertEquals(LocalDate.of(2025, 1, 1), result.get(0).getPeriodStartDate()); + assertEquals(new BigDecimal("1000.00"), result.get(0).getTotalRevenue()); + assertEquals(LocalDate.of(2025, 2, 1), result.get(1).getPeriodStartDate()); + assertEquals(new BigDecimal("2000.00"), result.get(1).getTotalRevenue()); + } + + @Test + void getRevenueSummary_WithEmptyData_ShouldReturnEmptyList() { + // Arrange + LocalDate startDate = LocalDate.of(2025, 1, 1); + LocalDate endDate = LocalDate.of(2025, 12, 31); + RevenueSummaryRequest request = RevenueSummaryRequest.builder() + .startDate(startDate) + .endDate(endDate) + .period(RevenueSummaryRequest.Period.MONTHLY) + .build(); + + when(orderRepository.findRevenueSummaryByPeriod(any(), any(), any())) + .thenReturn(Collections.emptyList()); + + // Act + List result = revenueReportService.getRevenueSummary(request.getStartDate(), + request.getEndDate(), request.getPeriod().name()); + + // Assert + assertTrue(result.isEmpty()); + } + + @Test + void getRevenueSummary_WithStartDateAfterEndDate_ShouldReturnEmptyList() { + // Arrange + LocalDate startDate = LocalDate.of(2025, 12, 31); + LocalDate endDate = LocalDate.of(2025, 1, 1); + RevenueSummaryRequest request = RevenueSummaryRequest.builder() + .startDate(startDate) + .endDate(endDate) + .period(RevenueSummaryRequest.Period.MONTHLY) + .build(); + + when(orderRepository.findRevenueSummaryByPeriod(any(), any(), any())) + .thenReturn(Collections.emptyList()); + + // Act + List result = revenueReportService.getRevenueSummary(request.getStartDate(), + request.getEndDate(), request.getPeriod().name()); + + // Assert + assertTrue(result.isEmpty()); + } + + @Test + void getRevenueByCategory_WithValidData_ShouldReturnCorrectCategories() { + // Arrange + LocalDate startDate = LocalDate.of(2025, 1, 1); + LocalDate endDate = LocalDate.of(2025, 12, 31); + List projections = Arrays.asList( + categoryProjection("Books", new BigDecimal("3000.00")), + categoryProjection("Electronics", new BigDecimal("5000.00"))); + + when(orderRepository.findRevenueByCategory(eq(startDate), eq(endDate))) + .thenReturn(projections);// Act + List result = revenueReportService.getRevenueByCategory(startDate, endDate); + + // Assert + assertEquals(2, result.size()); + assertEquals("Books", result.get(0).getCategory()); + assertEquals(new BigDecimal("3000.00"), result.get(0).getTotalRevenue()); + assertEquals("Electronics", result.get(1).getCategory()); + assertEquals(new BigDecimal("5000.00"), result.get(1).getTotalRevenue()); + } + + @Test + void getRevenueByCategory_WithEmptyData_ShouldReturnEmptyList() { + // Arrange + LocalDate startDate = LocalDate.of(2025, 1, 1); + LocalDate endDate = LocalDate.of(2025, 12, 31); + + when(orderRepository.findRevenueByCategory(any(), any())) + .thenReturn(Collections.emptyList()); + + // Act + List result = revenueReportService.getRevenueByCategory(startDate, endDate); + + // Assert + assertTrue(result.isEmpty()); + } + + @Test + void getRevenueByCategory_WithNullRevenue_ShouldHandleGracefully() { + // Arrange + LocalDate startDate = LocalDate.of(2025, 1, 1); + LocalDate endDate = LocalDate.of(2025, 12, 31); + + List projections = Arrays.asList( + new RevenueByCategoryProjection() { + @Override + public String getCategory() { + return "Electronics"; + } + + @Override + public BigDecimal getTotalRevenue() { + return null; + } + }); + + when(orderRepository.findRevenueByCategory(eq(startDate), eq(endDate))) + .thenReturn(projections); + + // Act + List result = revenueReportService.getRevenueByCategory(startDate, endDate); + + // Assert + assertEquals(1, result.size()); + assertEquals("Electronics", result.get(0).getCategory()); + assertNull(result.get(0).getTotalRevenue()); + } + + @Test + void getRevenueByCategory_WithStartDateAfterEndDate_ShouldReturnEmptyList() { + // Arrange + LocalDate startDate = LocalDate.of(2025, 12, 31); + LocalDate endDate = LocalDate.of(2025, 1, 1); + + when(orderRepository.findRevenueByCategory(any(), any())) + .thenReturn(Collections.emptyList()); + + // Act + List result = revenueReportService.getRevenueByCategory(startDate, endDate); + + // Assert + assertTrue(result.isEmpty()); + } + + private RevenueSummaryProjection summaryProjection(LocalDate date, BigDecimal revenue) { + return new RevenueSummaryProjection() { + public LocalDate getPeriod() { + return date; + } + + public BigDecimal getTotalRevenue() { + return revenue; + } + }; + } + + private RevenueByCategoryProjection categoryProjection(String category, BigDecimal revenue) { + return new RevenueByCategoryProjection() { + public String getCategory() { + return category; + } + + public BigDecimal getTotalRevenue() { + return revenue; + } + }; + } +} From ba2b81c45f956b5972fe5445b0309801c78b227d Mon Sep 17 00:00:00 2001 From: AhmadHoseiny Date: Sun, 18 May 2025 15:21:04 +0200 Subject: [PATCH 08/10] fix: fix lint issue --- .../Podzilla/analytics/messaging/AnalyticsRabbitListener.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/Podzilla/analytics/messaging/AnalyticsRabbitListener.java b/src/main/java/com/Podzilla/analytics/messaging/AnalyticsRabbitListener.java index d8409ad..7642c35 100644 --- a/src/main/java/com/Podzilla/analytics/messaging/AnalyticsRabbitListener.java +++ b/src/main/java/com/Podzilla/analytics/messaging/AnalyticsRabbitListener.java @@ -1,9 +1,9 @@ package com.Podzilla.analytics.messaging; // import org.springframework.amqp.rabbit.annotation.RabbitListener; +// import com.podzilla.mq.EventsConstants; import org.springframework.beans.factory.annotation.Autowired; -// import com.podzilla.mq.EventsConstants; import com.podzilla.mq.events.BaseEvent; import org.springframework.stereotype.Service; From ac28c0835b3fd5a8938a9aa1328ae9e7e8593ab4 Mon Sep 17 00:00:00 2001 From: AhmadHoseiny Date: Sun, 18 May 2025 20:34:54 +0200 Subject: [PATCH 09/10] feat: added order placed event --- pom.xml | 2 +- .../controllers/RabbitTesterController.java | 39 +++++++----- .../analytics/config/DatabaseSeeder.java | 6 +- .../messaging/AnalyticsRabbitListener.java | 1 + .../messaging/commands/CommandFactory.java | 33 ++++++++++ .../commands/order/PlaceOrderCommand.java | 44 ++++++++++++++ .../invokers/order/OrderPlacedInvoker.java | 14 ++++- .../com/Podzilla/analytics/models/Order.java | 13 +++- .../com/Podzilla/analytics/models/Region.java | 3 + .../services/OrderAnalyticsService.java | 60 ++++++++++++++++++- .../analytics/services/OrderItemService.java | 55 +++++++++++++++++ .../analytics/services/RegionService.java | 34 +++++++++++ .../CourierAnalyticsControllerTest.java | 2 +- 13 files changed, 283 insertions(+), 23 deletions(-) create mode 100644 src/main/java/com/Podzilla/analytics/messaging/commands/order/PlaceOrderCommand.java create mode 100644 src/main/java/com/Podzilla/analytics/services/OrderItemService.java create mode 100644 src/main/java/com/Podzilla/analytics/services/RegionService.java diff --git a/pom.xml b/pom.xml index 0b46afc..299c965 100644 --- a/pom.xml +++ b/pom.xml @@ -82,7 +82,7 @@ com.github.Podzilla podzilla-utils-lib - v1.1.11 + v1.1.12 diff --git a/src/main/java/com/Podzilla/analytics/api/controllers/RabbitTesterController.java b/src/main/java/com/Podzilla/analytics/api/controllers/RabbitTesterController.java index 3ef189c..2c9680a 100644 --- a/src/main/java/com/Podzilla/analytics/api/controllers/RabbitTesterController.java +++ b/src/main/java/com/Podzilla/analytics/api/controllers/RabbitTesterController.java @@ -1,7 +1,7 @@ package com.Podzilla.analytics.api.controllers; import java.math.BigDecimal; -import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; @@ -19,7 +19,7 @@ import com.podzilla.mq.events.DeliveryAddress; import com.podzilla.mq.events.InventoryUpdatedEvent; import com.podzilla.mq.events.OrderAssignedToCourierEvent; -import com.podzilla.mq.events.OrderCancelledEvent; +// import com.podzilla.mq.events.OrderCancelledEvent; import com.podzilla.mq.events.OrderDeliveredEvent; import com.podzilla.mq.events.OrderDeliveryFailedEvent; import com.podzilla.mq.events.OrderOutForDeliveryEvent; @@ -29,6 +29,8 @@ @RestController @RequestMapping("/rabbit-tester") public class RabbitTesterController { + + static final int QUANTITY = 5; @Autowired private AnalyticsRabbitListener listener; @@ -57,12 +59,12 @@ public void testOrderAssignedToCourierEvent() { listener.handleOrderEvents(event); } - @GetMapping("/order-cancelled-event") - public void testOrderCancelledEvent() { - BaseEvent event = new OrderCancelledEvent( - "d7c897d1-b23d-46aa-bfb6-258b4b8dcbd4", "2", "some reason"); - listener.handleOrderEvents(event); - } +// @GetMapping("/order-cancelled-event") +// public void testOrderCancelledEvent() { +// BaseEvent event = new OrderCancelledEvent( +// "d7c897d1-b23d-46aa-bfb6-258b4b8dcbd4", "2", "some reason"); +// listener.handleOrderEvents(event); +// } @GetMapping("/order-delivered-event") public void testOrderDeliveredEvent() { @@ -87,18 +89,27 @@ public void testOrderOutForDeliveryEvent() { } @GetMapping("/order-placed-event") - public void testOrderPlacedEvent() { + public void testOrderPlacedEvent( + @RequestParam final String customerId, + @RequestParam final String productId1, + @RequestParam final String productId2 +) { BaseEvent event = new OrderPlacedEvent( "a1aa7c7d-fe6a-491f-a2cc-b3b923340777", - "2", - new ArrayList<>(), + customerId, + Arrays.asList( + new com.podzilla.mq.events.OrderItem(productId1, + QUANTITY, new BigDecimal("8.5")), + new com.podzilla.mq.events.OrderItem(productId2, + QUANTITY, new BigDecimal("12.75")) + ), new DeliveryAddress( - "some street", - "some city", + "rabbit street", + "rabbit city wallahy", "some state", "some country", "some postal code"), - new BigDecimal("10.0"), 0.0, 0.0, "signature", + new BigDecimal("13290.0"), 0.0, 0.0, "signature", ConfirmationType.QR_CODE); listener.handleOrderEvents(event); } diff --git a/src/main/java/com/Podzilla/analytics/config/DatabaseSeeder.java b/src/main/java/com/Podzilla/analytics/config/DatabaseSeeder.java index 5500ad5..0015048 100644 --- a/src/main/java/com/Podzilla/analytics/config/DatabaseSeeder.java +++ b/src/main/java/com/Podzilla/analytics/config/DatabaseSeeder.java @@ -132,19 +132,19 @@ public void run(final String... args) { private List seedRegions() { Region region1 = regionRepository.save( Region.builder() - .id(UUID.randomUUID()) + // .id(UUID.randomUUID()) .city("Metropolis").state("NY") .country("USA").postalCode("10001") .build()); Region region2 = regionRepository.save( Region.builder() - .id(UUID.randomUUID()) + // .id(UUID.randomUUID()) .city("Gotham").state("NJ") .country("USA").postalCode("07001") .build()); Region region3 = regionRepository.save( Region.builder() - .id(UUID.randomUUID()) + // .id(UUID.randomUUID()) .city("Star City").state("CA") .country("USA").postalCode("90210") .build()); diff --git a/src/main/java/com/Podzilla/analytics/messaging/AnalyticsRabbitListener.java b/src/main/java/com/Podzilla/analytics/messaging/AnalyticsRabbitListener.java index 7642c35..350a6e6 100644 --- a/src/main/java/com/Podzilla/analytics/messaging/AnalyticsRabbitListener.java +++ b/src/main/java/com/Podzilla/analytics/messaging/AnalyticsRabbitListener.java @@ -27,6 +27,7 @@ public void handleUserEvents(final BaseEvent userEvent) { // queues = EventsConstants.ANALYTICS_ORDER_EVENT_QUEUE // ) public void handleOrderEvents(final BaseEvent orderEvent) { + System.out.println("Received orderEvent henaaa"); dispatcher.dispatch(orderEvent); } diff --git a/src/main/java/com/Podzilla/analytics/messaging/commands/CommandFactory.java b/src/main/java/com/Podzilla/analytics/messaging/commands/CommandFactory.java index 7d6bc95..692fea7 100644 --- a/src/main/java/com/Podzilla/analytics/messaging/commands/CommandFactory.java +++ b/src/main/java/com/Podzilla/analytics/messaging/commands/CommandFactory.java @@ -7,13 +7,19 @@ import com.Podzilla.analytics.services.CourierAnalyticsService; import com.Podzilla.analytics.services.ProductAnalyticsService; import com.Podzilla.analytics.services.InventoryAnalyticsService; +import com.Podzilla.analytics.services.OrderAnalyticsService; +import com.Podzilla.analytics.services.RegionService; import com.Podzilla.analytics.messaging.commands.user.RegisterCustomerCommand; import com.Podzilla.analytics.messaging.commands.user.RegisterCourierCommand; import com.Podzilla.analytics.messaging.commands.inventory.CreateProductCommand; import com.Podzilla.analytics.messaging.commands.inventory.UpdateInventoryCommand; +import com.Podzilla.analytics.messaging.commands.order.PlaceOrderCommand; + +import com.podzilla.mq.events.DeliveryAddress; import java.math.BigDecimal; import java.time.Instant; +import java.util.List; @Component public class CommandFactory { @@ -29,6 +35,13 @@ public class CommandFactory { @Autowired private InventoryAnalyticsService inventoryAnalyticsService; + @Autowired + private OrderAnalyticsService orderAnalyticsService; + + + @Autowired + private RegionService regionService; + public RegisterCustomerCommand createRegisterCustomerCommand( final String customerId, final String customerName @@ -80,4 +93,24 @@ public UpdateInventoryCommand createUpdateInventoryCommand( .timestamp(timestamp) .build(); } + + public PlaceOrderCommand createPlaceOrderCommand( + final String orderId, + final String customerId, + final List items, + final DeliveryAddress deliveryAddress, + final BigDecimal totalAmount, + final Instant timestamp + ) { + return PlaceOrderCommand.builder() + .orderAnalyticsService(orderAnalyticsService) + .regionService(regionService) + .orderId(orderId) + .customerId(customerId) + .items(items) + .deliveryAddress(deliveryAddress) + .totalAmount(totalAmount) + .timestamp(timestamp) + .build(); + } } diff --git a/src/main/java/com/Podzilla/analytics/messaging/commands/order/PlaceOrderCommand.java b/src/main/java/com/Podzilla/analytics/messaging/commands/order/PlaceOrderCommand.java new file mode 100644 index 0000000..606d368 --- /dev/null +++ b/src/main/java/com/Podzilla/analytics/messaging/commands/order/PlaceOrderCommand.java @@ -0,0 +1,44 @@ +package com.Podzilla.analytics.messaging.commands.order; + +import java.math.BigDecimal; + +import com.Podzilla.analytics.services.OrderAnalyticsService; +import com.Podzilla.analytics.services.RegionService; +import com.podzilla.mq.events.DeliveryAddress; +import com.Podzilla.analytics.messaging.commands.Command; +import com.Podzilla.analytics.models.Region; + +import java.util.List; +import java.time.Instant; +import lombok.Builder; + +@Builder +public class PlaceOrderCommand implements Command { + private OrderAnalyticsService orderAnalyticsService; + private RegionService regionService; + private String orderId; + private String customerId; + private List items; + private DeliveryAddress deliveryAddress; + private BigDecimal totalAmount; + private Instant timestamp; + + @Override + public void execute() { + System.out.println("Executing PlaceOrderCommand henaaa"); + Region region = regionService.saveRegion( + deliveryAddress.getCity(), + deliveryAddress.getState(), + deliveryAddress.getCountry(), + deliveryAddress.getPostalCode() + ); + orderAnalyticsService.saveOrder( + orderId, + customerId, + items, + region, + totalAmount, + timestamp + ); + } +} diff --git a/src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderPlacedInvoker.java b/src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderPlacedInvoker.java index 77a2899..4a9dd19 100644 --- a/src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderPlacedInvoker.java +++ b/src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderPlacedInvoker.java @@ -3,6 +3,7 @@ import org.springframework.beans.factory.annotation.Autowired; import com.Podzilla.analytics.messaging.commands.CommandFactory; +import com.Podzilla.analytics.messaging.commands.order.PlaceOrderCommand; import com.Podzilla.analytics.messaging.invokers.Invoker; import com.podzilla.mq.events.OrderPlacedEvent; @@ -17,8 +18,17 @@ public OrderPlacedInvoker(final CommandFactory commandFactory) { @Override public void invoke(final OrderPlacedEvent event) { - // create a command and call its execute method - System.out.println("Order Placed Event Invoked: " + event); + System.out.println("Invoking PlaceOrderInvoker henaaa"); + PlaceOrderCommand command = commandFactory + .createPlaceOrderCommand( + event.getOrderId(), + event.getCustomerId(), + event.getItems(), + event.getDeliveryAddress(), + event.getTotalAmount(), + event.getTimestamp() + ); + command.execute(); } } diff --git a/src/main/java/com/Podzilla/analytics/models/Order.java b/src/main/java/com/Podzilla/analytics/models/Order.java index daff52a..16c3b90 100644 --- a/src/main/java/com/Podzilla/analytics/models/Order.java +++ b/src/main/java/com/Podzilla/analytics/models/Order.java @@ -31,6 +31,7 @@ public class Order { private BigDecimal totalAmount; private LocalDateTime orderPlacedTimestamp; + private LocalDateTime orderFulfillmentFailedTimestamp; private LocalDateTime orderCancelledTimestamp; private LocalDateTime shippedTimestamp; private LocalDateTime deliveredTimestamp; @@ -51,7 +52,7 @@ public class Order { private Customer customer; @ManyToOne - @JoinColumn(name = "courier_id", nullable = false) + @JoinColumn(name = "courier_id", nullable = true) private Courier courier; @ManyToOne @@ -63,6 +64,7 @@ public class Order { public enum OrderStatus { PLACED, + FULFILLMENT_FAILED, CANCELLED, SHIPPED, DELIVERED, @@ -77,6 +79,7 @@ public static class Builder { private UUID id; private BigDecimal totalAmount; private LocalDateTime orderPlacedTimestamp; + private LocalDateTime orderFulfillmentFailedTimestamp; private LocalDateTime orderCancelledTimestamp; private LocalDateTime shippedTimestamp; private LocalDateTime deliveredTimestamp; @@ -110,6 +113,13 @@ public Builder orderPlacedTimestamp( return this; } + public Builder orderFulfillmentFailedTimestamp( + final LocalDateTime orderFulfillmentFailedTimestamp) { + this.orderFulfillmentFailedTimestamp = + orderFulfillmentFailedTimestamp; + return this; + } + public Builder orderCancelledTimestamp( final LocalDateTime orderCancelledTimestamp) { this.orderCancelledTimestamp = orderCancelledTimestamp; @@ -185,6 +195,7 @@ public Order build() { id, totalAmount, orderPlacedTimestamp, + orderFulfillmentFailedTimestamp, orderCancelledTimestamp, shippedTimestamp, deliveredTimestamp, diff --git a/src/main/java/com/Podzilla/analytics/models/Region.java b/src/main/java/com/Podzilla/analytics/models/Region.java index d83075f..5ba9fb4 100644 --- a/src/main/java/com/Podzilla/analytics/models/Region.java +++ b/src/main/java/com/Podzilla/analytics/models/Region.java @@ -1,6 +1,8 @@ package com.Podzilla.analytics.models; import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.Table; import lombok.AllArgsConstructor; @@ -15,6 +17,7 @@ @AllArgsConstructor public class Region { @Id + @GeneratedValue(strategy = GenerationType.UUID) private UUID id; private String city; private String state; diff --git a/src/main/java/com/Podzilla/analytics/services/OrderAnalyticsService.java b/src/main/java/com/Podzilla/analytics/services/OrderAnalyticsService.java index b064d0f..cc5ff63 100644 --- a/src/main/java/com/Podzilla/analytics/services/OrderAnalyticsService.java +++ b/src/main/java/com/Podzilla/analytics/services/OrderAnalyticsService.java @@ -1,5 +1,7 @@ package com.Podzilla.analytics.services; +import java.math.BigDecimal; +import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.List; @@ -14,17 +16,28 @@ import com.Podzilla.analytics.api.projections.order.OrderFailureReasonsProjection; import com.Podzilla.analytics.api.projections.order.OrderRegionProjection; import com.Podzilla.analytics.api.projections.order.OrderStatusProjection; +import com.Podzilla.analytics.repositories.CustomerRepository; import com.Podzilla.analytics.repositories.OrderRepository; import com.Podzilla.analytics.util.DatetimeFormatter; +import com.Podzilla.analytics.models.Order; +import com.Podzilla.analytics.models.Order.OrderStatus; +import com.Podzilla.analytics.models.Region; +import com.Podzilla.analytics.models.Customer; +import com.Podzilla.analytics.util.StringToUUIDParser; + import lombok.RequiredArgsConstructor; +import java.util.UUID; -@RequiredArgsConstructor @Service +@RequiredArgsConstructor public class OrderAnalyticsService { private final OrderRepository orderRepository; + private final CustomerRepository customerRepository; + private final OrderItemService orderItemService; + public List getOrdersByRegion( final LocalDate startDate, final LocalDate endDate @@ -88,4 +101,49 @@ public OrderFailureResponse getOrdersFailures( .failureRate(failureRate.getFailureRate()) .build(); } + + public Order saveOrder( + final String orderId, + final String customerId, + final List items, + final Region region, + final BigDecimal totalAmount, + final Instant timeStamp + ) { + UUID orderUUID = + StringToUUIDParser.parseStringToUUID(orderId); + UUID customerUUID = + StringToUUIDParser.parseStringToUUID(customerId); + Customer customer = + customerRepository.findById(customerUUID) + .orElseThrow(() -> new RuntimeException("Customer not found")); + int numberOfItems = items.stream() + .mapToInt(com.podzilla.mq.events.OrderItem::getQuantity) + .sum(); + LocalDateTime orderPlacedTimestamp = + DatetimeFormatter.convertIntsantToDateTime(timeStamp); + Order order = Order.builder() + .id(orderUUID) + .totalAmount(totalAmount) + .orderPlacedTimestamp(orderPlacedTimestamp) + .finalStatusTimestamp(orderPlacedTimestamp) + .region(region) + .customer(customer) + .numberOfItems(numberOfItems) + .status(OrderStatus.PLACED) + .build(); + orderRepository.save(order); + + List orderItems = + items.stream() + .map(item -> orderItemService.saveOrderItem( + item.getQuantity(), + item.getPricePerUnit(), + item.getProductId(), + orderUUID.toString() + )) + .toList(); + order.setOrderItems(orderItems); + return orderRepository.save(order); + } } diff --git a/src/main/java/com/Podzilla/analytics/services/OrderItemService.java b/src/main/java/com/Podzilla/analytics/services/OrderItemService.java new file mode 100644 index 0000000..daa91bc --- /dev/null +++ b/src/main/java/com/Podzilla/analytics/services/OrderItemService.java @@ -0,0 +1,55 @@ +package com.Podzilla.analytics.services; + +import org.springframework.stereotype.Service; +import lombok.RequiredArgsConstructor; +import com.Podzilla.analytics.repositories.OrderItemRepository; +import com.Podzilla.analytics.repositories.ProductRepository; +import com.Podzilla.analytics.repositories.OrderRepository; + + +import com.Podzilla.analytics.util.StringToUUIDParser; + +import org.springframework.beans.factory.annotation.Autowired; +import com.Podzilla.analytics.models.OrderItem; +import com.Podzilla.analytics.models.Product; +import com.Podzilla.analytics.models.Order; + + +import java.math.BigDecimal; +import java.util.UUID; + +@Service +@RequiredArgsConstructor +public class OrderItemService { + @Autowired + private final OrderItemRepository orderItemRepository; + + @Autowired + private final ProductRepository productRepository; + + @Autowired + private final OrderRepository orderRepository; + + public OrderItem saveOrderItem( + final int quantity, + final BigDecimal pricePerUnit, + final String productId, + final String orderId + ) { + UUID productUUID = StringToUUIDParser.parseStringToUUID(productId); + UUID orderUUID = StringToUUIDParser.parseStringToUUID(orderId); + Product product = productRepository.findById(productUUID) + .orElseThrow(() -> new RuntimeException("Product not found")); + Order order = orderRepository.findById(orderUUID) + .orElseThrow(() -> new RuntimeException("Order not found")); + + OrderItem orderItem = OrderItem.builder() + .quantity(quantity) + .pricePerUnit(pricePerUnit) + .product(product) + .order(order) + .build(); + return orderItemRepository.save(orderItem); + } + +} diff --git a/src/main/java/com/Podzilla/analytics/services/RegionService.java b/src/main/java/com/Podzilla/analytics/services/RegionService.java new file mode 100644 index 0000000..f653fed --- /dev/null +++ b/src/main/java/com/Podzilla/analytics/services/RegionService.java @@ -0,0 +1,34 @@ +package com.Podzilla.analytics.services; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.Podzilla.analytics.repositories.RegionRepository; + +import lombok.RequiredArgsConstructor; +import com.Podzilla.analytics.models.Region; + + + +@Service +@RequiredArgsConstructor +public class RegionService { + + @Autowired + private final RegionRepository regionRepository; + + public Region saveRegion( + final String city, + final String state, + final String country, + final String postalCode + ) { + Region region = Region.builder() + .city(city) + .state(state) + .country(country) + .postalCode(postalCode) + .build(); + return regionRepository.save(region); + } +} diff --git a/src/test/java/com/Podzilla/analytics/controllers/CourierAnalyticsControllerTest.java b/src/test/java/com/Podzilla/analytics/controllers/CourierAnalyticsControllerTest.java index e758879..7a31e5a 100644 --- a/src/test/java/com/Podzilla/analytics/controllers/CourierAnalyticsControllerTest.java +++ b/src/test/java/com/Podzilla/analytics/controllers/CourierAnalyticsControllerTest.java @@ -74,7 +74,7 @@ void setUp() { .id(UUID.randomUUID()) .name("John Doe").build()); region1 = regionRepository.save(Region.builder() - .id(UUID.randomUUID()) + // .id(UUID.randomUUID()) .city("Sample City") .state("Sample State") .country("Sample Country") From f72679873b0c7b997d4f97937121a175593ecea4 Mon Sep 17 00:00:00 2001 From: AhmadHoseiny Date: Sun, 18 May 2025 22:39:27 +0200 Subject: [PATCH 10/10] feat: added update order events --- .../controllers/RabbitTesterController.java | 58 ++++++--- .../messaging/AnalyticsRabbitListener.java | 1 - .../messaging/InvokerDispatcherConfig.java | 7 ++ .../messaging/commands/CommandFactory.java | 84 +++++++++++++ .../MarkOrderAsFailedToFulfillCommand.java | 25 ++++ .../order/AssignCourierToOrderCommand.java | 17 +++ .../commands/order/CancelOrderCommand.java | 25 ++++ .../order/MarkOrderAsDeliveredCommand.java | 27 ++++ .../MarkOrderAsFailedToDeliverCommand.java | 25 ++++ .../MarkOrderAsOutForDeliveryCommand.java | 20 +++ .../commands/order/PlaceOrderCommand.java | 1 - .../OrderFulfillmentFailedInvoker.java | 29 +++++ .../order/OrderAssignedToCourierInvoker.java | 9 +- .../invokers/order/OrderCancelledInvoker.java | 11 +- .../invokers/order/OrderDeliveredInvoker.java | 10 +- .../order/OrderDeliveryFailedInvoker.java | 10 +- .../order/OrderOutForDeliveryInvoker.java | 9 +- .../invokers/order/OrderPlacedInvoker.java | 1 - .../services/OrderAnalyticsService.java | 118 ++++++++++++++++++ 19 files changed, 457 insertions(+), 30 deletions(-) create mode 100644 src/main/java/com/Podzilla/analytics/messaging/commands/inventory/MarkOrderAsFailedToFulfillCommand.java create mode 100644 src/main/java/com/Podzilla/analytics/messaging/commands/order/AssignCourierToOrderCommand.java create mode 100644 src/main/java/com/Podzilla/analytics/messaging/commands/order/CancelOrderCommand.java create mode 100644 src/main/java/com/Podzilla/analytics/messaging/commands/order/MarkOrderAsDeliveredCommand.java create mode 100644 src/main/java/com/Podzilla/analytics/messaging/commands/order/MarkOrderAsFailedToDeliverCommand.java create mode 100644 src/main/java/com/Podzilla/analytics/messaging/commands/order/MarkOrderAsOutForDeliveryCommand.java create mode 100644 src/main/java/com/Podzilla/analytics/messaging/invokers/inventory/OrderFulfillmentFailedInvoker.java diff --git a/src/main/java/com/Podzilla/analytics/api/controllers/RabbitTesterController.java b/src/main/java/com/Podzilla/analytics/api/controllers/RabbitTesterController.java index 2c9680a..0e3dd5e 100644 --- a/src/main/java/com/Podzilla/analytics/api/controllers/RabbitTesterController.java +++ b/src/main/java/com/Podzilla/analytics/api/controllers/RabbitTesterController.java @@ -14,18 +14,20 @@ import com.podzilla.mq.events.BaseEvent; import com.podzilla.mq.events.ConfirmationType; import com.podzilla.mq.events.ProductSnapshot; +import com.podzilla.mq.events.WarehouseOrderFulfillmentFailedEvent; import com.podzilla.mq.events.CourierRegisteredEvent; import com.podzilla.mq.events.CustomerRegisteredEvent; import com.podzilla.mq.events.DeliveryAddress; import com.podzilla.mq.events.InventoryUpdatedEvent; import com.podzilla.mq.events.OrderAssignedToCourierEvent; -// import com.podzilla.mq.events.OrderCancelledEvent; +import com.podzilla.mq.events.OrderCancelledEvent; import com.podzilla.mq.events.OrderDeliveredEvent; import com.podzilla.mq.events.OrderDeliveryFailedEvent; import com.podzilla.mq.events.OrderOutForDeliveryEvent; import com.podzilla.mq.events.OrderPlacedEvent; import com.podzilla.mq.events.ProductCreatedEvent; +import java.util.ArrayList; @RestController @RequestMapping("/rabbit-tester") public class RabbitTesterController { @@ -51,40 +53,64 @@ public void testCustomerRegisteredEvent() { } @GetMapping("/order-assigned-to-courier-event") - public void testOrderAssignedToCourierEvent() { + public void testOrderAssignedToCourierEvent( + @RequestParam final String orderId, + @RequestParam final String courierId + ) { BaseEvent event = new OrderAssignedToCourierEvent( - "e715d122-2628-4c68-82bc-a3c4fc1eefd1", "2", + orderId, + courierId, new BigDecimal("10.0"), 0.0, 0.0, "signature", ConfirmationType.QR_CODE); listener.handleOrderEvents(event); } -// @GetMapping("/order-cancelled-event") -// public void testOrderCancelledEvent() { -// BaseEvent event = new OrderCancelledEvent( -// "d7c897d1-b23d-46aa-bfb6-258b4b8dcbd4", "2", "some reason"); -// listener.handleOrderEvents(event); -// } + @GetMapping("/order-cancelled-event") + public void testOrderCancelledEvent( + @RequestParam final String orderId + ) { + BaseEvent event = new OrderCancelledEvent( + orderId, + "2", // customerId (not used in the event) + "rabbit reason", + new ArrayList<>() + ); + listener.handleOrderEvents(event); + } @GetMapping("/order-delivered-event") - public void testOrderDeliveredEvent() { + public void testOrderDeliveredEvent( + @RequestParam final String orderId + ) { BaseEvent event = new OrderDeliveredEvent( - "21063caa-4265-4286-8b16-6361b5bda83a", "2", - new BigDecimal("10.0")); + orderId, "2", + new BigDecimal("4.73")); listener.handleOrderEvents(event); } @GetMapping("/order-delivery-failed-event") - public void testOrderDeliveryFailedEvent() { + public void testOrderDeliveryFailedEvent( + @RequestParam final String orderId + ) { BaseEvent event = new OrderDeliveryFailedEvent( - "a0736362-6e37-46d7-95c6-4ad9092bf642", "2", "some reason"); + orderId, "the rabit delivery failed reason", "2"); listener.handleOrderEvents(event); } @GetMapping("/order-out-for-delivery-event") - public void testOrderOutForDeliveryEvent() { + public void testOrderOutForDeliveryEvent( + @RequestParam final String orderId + ) { BaseEvent event = new OrderOutForDeliveryEvent( - "55399710-a835-4f66-ba9d-1d299e40702b", "2"); + orderId, "2"); + listener.handleOrderEvents(event); + } + @GetMapping("/order-fulfillment-failed-event") + public void testOrderFailedToFulfill( + @RequestParam final String orderId + ) { + BaseEvent event = new WarehouseOrderFulfillmentFailedEvent( + orderId, "order fulfillment failed rabbit reason"); listener.handleOrderEvents(event); } diff --git a/src/main/java/com/Podzilla/analytics/messaging/AnalyticsRabbitListener.java b/src/main/java/com/Podzilla/analytics/messaging/AnalyticsRabbitListener.java index 350a6e6..7642c35 100644 --- a/src/main/java/com/Podzilla/analytics/messaging/AnalyticsRabbitListener.java +++ b/src/main/java/com/Podzilla/analytics/messaging/AnalyticsRabbitListener.java @@ -27,7 +27,6 @@ public void handleUserEvents(final BaseEvent userEvent) { // queues = EventsConstants.ANALYTICS_ORDER_EVENT_QUEUE // ) public void handleOrderEvents(final BaseEvent orderEvent) { - System.out.println("Received orderEvent henaaa"); dispatcher.dispatch(orderEvent); } diff --git a/src/main/java/com/Podzilla/analytics/messaging/InvokerDispatcherConfig.java b/src/main/java/com/Podzilla/analytics/messaging/InvokerDispatcherConfig.java index 6c85f82..bae26ac 100644 --- a/src/main/java/com/Podzilla/analytics/messaging/InvokerDispatcherConfig.java +++ b/src/main/java/com/Podzilla/analytics/messaging/InvokerDispatcherConfig.java @@ -15,6 +15,7 @@ import com.Podzilla.analytics.messaging.invokers.InvokerFactory; import com.Podzilla.analytics.messaging.invokers.inventory.InventoryUpdatedInvoker; import com.Podzilla.analytics.messaging.invokers.inventory.ProductCreatedInvoker; +import com.Podzilla.analytics.messaging.invokers.inventory.OrderFulfillmentFailedInvoker; import com.podzilla.mq.events.CourierRegisteredEvent; import com.podzilla.mq.events.CustomerRegisteredEvent; @@ -26,6 +27,7 @@ import com.podzilla.mq.events.OrderOutForDeliveryEvent; import com.podzilla.mq.events.InventoryUpdatedEvent; import com.podzilla.mq.events.ProductCreatedEvent; +import com.podzilla.mq.events.WarehouseOrderFulfillmentFailedEvent; @Configuration @@ -103,6 +105,11 @@ private void registerInventoryInvokers( ProductCreatedEvent.class, invokerFactory.createInvoker(ProductCreatedInvoker.class) ); + + dispatcher.registerInvoker( + WarehouseOrderFulfillmentFailedEvent.class, + invokerFactory.createInvoker(OrderFulfillmentFailedInvoker.class) + ); } } diff --git a/src/main/java/com/Podzilla/analytics/messaging/commands/CommandFactory.java b/src/main/java/com/Podzilla/analytics/messaging/commands/CommandFactory.java index 692fea7..d385e02 100644 --- a/src/main/java/com/Podzilla/analytics/messaging/commands/CommandFactory.java +++ b/src/main/java/com/Podzilla/analytics/messaging/commands/CommandFactory.java @@ -13,7 +13,14 @@ import com.Podzilla.analytics.messaging.commands.user.RegisterCourierCommand; import com.Podzilla.analytics.messaging.commands.inventory.CreateProductCommand; import com.Podzilla.analytics.messaging.commands.inventory.UpdateInventoryCommand; +import com.Podzilla.analytics.messaging.commands.inventory.MarkOrderAsFailedToFulfillCommand; import com.Podzilla.analytics.messaging.commands.order.PlaceOrderCommand; +import com.Podzilla.analytics.messaging.commands.order.AssignCourierToOrderCommand; +import com.Podzilla.analytics.messaging.commands.order.CancelOrderCommand; +import com.Podzilla.analytics.messaging.commands.order.MarkOrderAsOutForDeliveryCommand; +import com.Podzilla.analytics.messaging.commands.order.MarkOrderAsDeliveredCommand; +import com.Podzilla.analytics.messaging.commands.order.MarkOrderAsFailedToDeliverCommand; + import com.podzilla.mq.events.DeliveryAddress; @@ -113,4 +120,81 @@ public PlaceOrderCommand createPlaceOrderCommand( .timestamp(timestamp) .build(); } + + public CancelOrderCommand createCancelOrderCommand( + final String orderId, + final String reason, + final Instant timestamp + ) { + return CancelOrderCommand.builder() + .orderAnalyticsService(orderAnalyticsService) + .orderId(orderId) + .reason(reason) + .timestamp(timestamp) + .build(); + } + + public AssignCourierToOrderCommand createAssignCourierToOrderCommand( + final String orderId, + final String courierId + ) { + return AssignCourierToOrderCommand.builder() + .orderAnalyticsService(orderAnalyticsService) + .orderId(orderId) + .courierId(courierId) + .build(); + } + + public MarkOrderAsOutForDeliveryCommand + createMarkOrderAsOutForDeliveryCommand( + final String orderId, + final Instant timestamp + ) { + return MarkOrderAsOutForDeliveryCommand.builder() + .orderAnalyticsService(orderAnalyticsService) + .orderId(orderId) + .timestamp(timestamp) + .build(); + } + + public MarkOrderAsDeliveredCommand createMarkOrderAsDeliveredCommand( + final String orderId, + final BigDecimal courierRating, + final Instant timestamp + ) { + return MarkOrderAsDeliveredCommand.builder() + .orderAnalyticsService(orderAnalyticsService) + .orderId(orderId) + .courierRating(courierRating) + .timestamp(timestamp) + .build(); + } + + public MarkOrderAsFailedToDeliverCommand + createMarkOrderAsFailedToDeliverCommand( + final String orderId, + final String reason, + final Instant timestamp + ) { + return MarkOrderAsFailedToDeliverCommand.builder() + .orderAnalyticsService(orderAnalyticsService) + .orderId(orderId) + .reason(reason) + .timestamp(timestamp) + .build(); + } + + public MarkOrderAsFailedToFulfillCommand + createMarkOrderAsFailedToFulfillCommand( + final String orderId, + final String reason, + final Instant timestamp + ) { + return MarkOrderAsFailedToFulfillCommand.builder() + .orderAnalyticsService(orderAnalyticsService) + .orderId(orderId) + .reason(reason) + .timestamp(timestamp) + .build(); + } } diff --git a/src/main/java/com/Podzilla/analytics/messaging/commands/inventory/MarkOrderAsFailedToFulfillCommand.java b/src/main/java/com/Podzilla/analytics/messaging/commands/inventory/MarkOrderAsFailedToFulfillCommand.java new file mode 100644 index 0000000..8340fc3 --- /dev/null +++ b/src/main/java/com/Podzilla/analytics/messaging/commands/inventory/MarkOrderAsFailedToFulfillCommand.java @@ -0,0 +1,25 @@ +package com.Podzilla.analytics.messaging.commands.inventory; + +import com.Podzilla.analytics.messaging.commands.Command; +import com.Podzilla.analytics.services.OrderAnalyticsService; + +import java.time.Instant; +import lombok.Builder; + +@Builder +public class MarkOrderAsFailedToFulfillCommand implements Command { + private OrderAnalyticsService orderAnalyticsService; + private String orderId; + private String reason; + private Instant timestamp; + + @Override + public void execute() { + orderAnalyticsService.markOrderAsFailedToFulfill( + orderId, + reason, + timestamp + ); + } + +} diff --git a/src/main/java/com/Podzilla/analytics/messaging/commands/order/AssignCourierToOrderCommand.java b/src/main/java/com/Podzilla/analytics/messaging/commands/order/AssignCourierToOrderCommand.java new file mode 100644 index 0000000..4dbc0c7 --- /dev/null +++ b/src/main/java/com/Podzilla/analytics/messaging/commands/order/AssignCourierToOrderCommand.java @@ -0,0 +1,17 @@ +package com.Podzilla.analytics.messaging.commands.order; + +import com.Podzilla.analytics.messaging.commands.Command; +import com.Podzilla.analytics.services.OrderAnalyticsService; +import lombok.Builder; + +@Builder +public class AssignCourierToOrderCommand implements Command { + private OrderAnalyticsService orderAnalyticsService; + private String orderId; + private String courierId; + + @Override + public void execute() { + orderAnalyticsService.assignCourier(orderId, courierId); + } +} diff --git a/src/main/java/com/Podzilla/analytics/messaging/commands/order/CancelOrderCommand.java b/src/main/java/com/Podzilla/analytics/messaging/commands/order/CancelOrderCommand.java new file mode 100644 index 0000000..d11ca82 --- /dev/null +++ b/src/main/java/com/Podzilla/analytics/messaging/commands/order/CancelOrderCommand.java @@ -0,0 +1,25 @@ +package com.Podzilla.analytics.messaging.commands.order; + +import java.time.Instant; + +import com.Podzilla.analytics.services.OrderAnalyticsService; +import com.Podzilla.analytics.messaging.commands.Command; + +import lombok.Builder; + +@Builder +public class CancelOrderCommand implements Command { + private OrderAnalyticsService orderAnalyticsService; + private String orderId; + private String reason; + private Instant timestamp; + + @Override + public void execute() { + orderAnalyticsService.cancelOrder( + orderId, + reason, + timestamp + ); + } +} diff --git a/src/main/java/com/Podzilla/analytics/messaging/commands/order/MarkOrderAsDeliveredCommand.java b/src/main/java/com/Podzilla/analytics/messaging/commands/order/MarkOrderAsDeliveredCommand.java new file mode 100644 index 0000000..bd5e3e5 --- /dev/null +++ b/src/main/java/com/Podzilla/analytics/messaging/commands/order/MarkOrderAsDeliveredCommand.java @@ -0,0 +1,27 @@ +package com.Podzilla.analytics.messaging.commands.order; + +import java.math.BigDecimal; + +import com.Podzilla.analytics.services.OrderAnalyticsService; + +import lombok.Builder; + +import com.Podzilla.analytics.messaging.commands.Command; +import java.time.Instant; + +@Builder +public class MarkOrderAsDeliveredCommand implements Command { + private OrderAnalyticsService orderAnalyticsService; + private String orderId; + private BigDecimal courierRating; + private Instant timestamp; + + @Override + public void execute() { + orderAnalyticsService.markOrderAsDelivered( + orderId, + courierRating, + timestamp + ); + } +} diff --git a/src/main/java/com/Podzilla/analytics/messaging/commands/order/MarkOrderAsFailedToDeliverCommand.java b/src/main/java/com/Podzilla/analytics/messaging/commands/order/MarkOrderAsFailedToDeliverCommand.java new file mode 100644 index 0000000..f788710 --- /dev/null +++ b/src/main/java/com/Podzilla/analytics/messaging/commands/order/MarkOrderAsFailedToDeliverCommand.java @@ -0,0 +1,25 @@ +package com.Podzilla.analytics.messaging.commands.order; + +import com.Podzilla.analytics.services.OrderAnalyticsService; +import com.Podzilla.analytics.messaging.commands.Command; + +import java.time.Instant; +import lombok.Builder; + +@Builder +public class MarkOrderAsFailedToDeliverCommand implements Command { + private OrderAnalyticsService orderAnalyticsService; + private String orderId; + private String reason; + private Instant timestamp; + + @Override + public void execute() { + orderAnalyticsService.markOrderAsFailedToDeliver( + orderId, + reason, + timestamp + ); + } + +} diff --git a/src/main/java/com/Podzilla/analytics/messaging/commands/order/MarkOrderAsOutForDeliveryCommand.java b/src/main/java/com/Podzilla/analytics/messaging/commands/order/MarkOrderAsOutForDeliveryCommand.java new file mode 100644 index 0000000..867f4c1 --- /dev/null +++ b/src/main/java/com/Podzilla/analytics/messaging/commands/order/MarkOrderAsOutForDeliveryCommand.java @@ -0,0 +1,20 @@ +package com.Podzilla.analytics.messaging.commands.order; + +import com.Podzilla.analytics.services.OrderAnalyticsService; +import com.Podzilla.analytics.messaging.commands.Command; +import java.time.Instant; + +import lombok.Builder; + +@Builder +public class MarkOrderAsOutForDeliveryCommand implements Command { + private OrderAnalyticsService orderAnalyticsService; + private String orderId; + private Instant timestamp; + + @Override + public void execute() { + orderAnalyticsService.markOrderAsOutForDelivery(orderId, timestamp); + } + +} diff --git a/src/main/java/com/Podzilla/analytics/messaging/commands/order/PlaceOrderCommand.java b/src/main/java/com/Podzilla/analytics/messaging/commands/order/PlaceOrderCommand.java index 606d368..9415b8a 100644 --- a/src/main/java/com/Podzilla/analytics/messaging/commands/order/PlaceOrderCommand.java +++ b/src/main/java/com/Podzilla/analytics/messaging/commands/order/PlaceOrderCommand.java @@ -25,7 +25,6 @@ public class PlaceOrderCommand implements Command { @Override public void execute() { - System.out.println("Executing PlaceOrderCommand henaaa"); Region region = regionService.saveRegion( deliveryAddress.getCity(), deliveryAddress.getState(), diff --git a/src/main/java/com/Podzilla/analytics/messaging/invokers/inventory/OrderFulfillmentFailedInvoker.java b/src/main/java/com/Podzilla/analytics/messaging/invokers/inventory/OrderFulfillmentFailedInvoker.java new file mode 100644 index 0000000..ecb813d --- /dev/null +++ b/src/main/java/com/Podzilla/analytics/messaging/invokers/inventory/OrderFulfillmentFailedInvoker.java @@ -0,0 +1,29 @@ +package com.Podzilla.analytics.messaging.invokers.inventory; +import org.springframework.beans.factory.annotation.Autowired; +import com.Podzilla.analytics.messaging.commands.CommandFactory; +import com.Podzilla.analytics.messaging.invokers.Invoker; +import com.podzilla.mq.events.WarehouseOrderFulfillmentFailedEvent; +import com.Podzilla.analytics.messaging.commands.inventory.MarkOrderAsFailedToFulfillCommand; + +public class OrderFulfillmentFailedInvoker + implements Invoker { + + @Autowired + private final CommandFactory commandFactory; + + public OrderFulfillmentFailedInvoker(final CommandFactory commandFactory) { + this.commandFactory = commandFactory; + } + + @Override + public void invoke(final WarehouseOrderFulfillmentFailedEvent event) { + MarkOrderAsFailedToFulfillCommand command = + commandFactory.createMarkOrderAsFailedToFulfillCommand( + event.getOrderId(), + event.getReason(), + event.getTimestamp() + ); + command.execute(); + } + +} diff --git a/src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderAssignedToCourierInvoker.java b/src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderAssignedToCourierInvoker.java index cc01a8f..81b53d0 100644 --- a/src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderAssignedToCourierInvoker.java +++ b/src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderAssignedToCourierInvoker.java @@ -5,6 +5,7 @@ import com.Podzilla.analytics.messaging.commands.CommandFactory; import com.Podzilla.analytics.messaging.invokers.Invoker; import com.podzilla.mq.events.OrderAssignedToCourierEvent; +import com.Podzilla.analytics.messaging.commands.order.AssignCourierToOrderCommand; public class OrderAssignedToCourierInvoker implements Invoker { @@ -17,7 +18,11 @@ public OrderAssignedToCourierInvoker(final CommandFactory commandFactory) { @Override public void invoke(final OrderAssignedToCourierEvent event) { - // create a command and call its execute method - System.out.println("Order Assigned To Courier Event Invoked: " + event); + AssignCourierToOrderCommand command = commandFactory + .createAssignCourierToOrderCommand( + event.getOrderId(), + event.getCourierId() + ); + command.execute(); } } diff --git a/src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderCancelledInvoker.java b/src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderCancelledInvoker.java index 8b3499b..4b81e0a 100644 --- a/src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderCancelledInvoker.java +++ b/src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderCancelledInvoker.java @@ -5,7 +5,7 @@ import com.Podzilla.analytics.messaging.commands.CommandFactory; import com.Podzilla.analytics.messaging.invokers.Invoker; import com.podzilla.mq.events.OrderCancelledEvent; - +import com.Podzilla.analytics.messaging.commands.order.CancelOrderCommand; public class OrderCancelledInvoker implements Invoker { @@ -17,7 +17,12 @@ public OrderCancelledInvoker(final CommandFactory commandFactory) { @Override public void invoke(final OrderCancelledEvent event) { - // create a command and call its execute method - System.out.println("Order Cancelled Event Invoked: " + event); + CancelOrderCommand command = commandFactory + .createCancelOrderCommand( + event.getOrderId(), + event.getReason(), + event.getTimestamp() + ); + command.execute(); } } diff --git a/src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderDeliveredInvoker.java b/src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderDeliveredInvoker.java index 903c97f..c28e276 100644 --- a/src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderDeliveredInvoker.java +++ b/src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderDeliveredInvoker.java @@ -3,6 +3,7 @@ import org.springframework.beans.factory.annotation.Autowired; import com.Podzilla.analytics.messaging.commands.CommandFactory; +import com.Podzilla.analytics.messaging.commands.order.MarkOrderAsDeliveredCommand; import com.Podzilla.analytics.messaging.invokers.Invoker; import com.podzilla.mq.events.OrderDeliveredEvent; @@ -17,8 +18,13 @@ public OrderDeliveredInvoker(final CommandFactory commandFactory) { @Override public void invoke(final OrderDeliveredEvent event) { - // create a command and call its execute method - System.out.println("Order Delivered Event Invoked: " + event); + MarkOrderAsDeliveredCommand command = + commandFactory.createMarkOrderAsDeliveredCommand( + event.getOrderId(), + event.getCourierRating(), + event.getTimestamp() + ); + command.execute(); } } diff --git a/src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderDeliveryFailedInvoker.java b/src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderDeliveryFailedInvoker.java index eebcd8e..c483511 100644 --- a/src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderDeliveryFailedInvoker.java +++ b/src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderDeliveryFailedInvoker.java @@ -5,6 +5,7 @@ import com.Podzilla.analytics.messaging.commands.CommandFactory; import com.Podzilla.analytics.messaging.invokers.Invoker; import com.podzilla.mq.events.OrderDeliveryFailedEvent; +import com.Podzilla.analytics.messaging.commands.order.MarkOrderAsFailedToDeliverCommand; public class OrderDeliveryFailedInvoker implements Invoker { @@ -17,8 +18,13 @@ public OrderDeliveryFailedInvoker(final CommandFactory commandFactory) { @Override public void invoke(final OrderDeliveryFailedEvent event) { - // create a command and call its execute method - System.out.println("Order Delivery Failed Event Invoked: " + event); + MarkOrderAsFailedToDeliverCommand command = + commandFactory.createMarkOrderAsFailedToDeliverCommand( + event.getOrderId(), + event.getCourierId(), + event.getTimestamp() + ); + command.execute(); } } diff --git a/src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderOutForDeliveryInvoker.java b/src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderOutForDeliveryInvoker.java index b9caacf..7ceed8e 100644 --- a/src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderOutForDeliveryInvoker.java +++ b/src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderOutForDeliveryInvoker.java @@ -5,6 +5,7 @@ import com.Podzilla.analytics.messaging.commands.CommandFactory; import com.Podzilla.analytics.messaging.invokers.Invoker; import com.podzilla.mq.events.OrderOutForDeliveryEvent; +import com.Podzilla.analytics.messaging.commands.order.MarkOrderAsOutForDeliveryCommand; public class OrderOutForDeliveryInvoker implements Invoker { @@ -17,7 +18,11 @@ public OrderOutForDeliveryInvoker(final CommandFactory commandFactory) { @Override public void invoke(final OrderOutForDeliveryEvent event) { - // create a command and call its execute method - System.out.println("Order Out For Delivery Event Invoked: " + event); + MarkOrderAsOutForDeliveryCommand command = commandFactory + .createMarkOrderAsOutForDeliveryCommand( + event.getOrderId(), + event.getTimestamp() + ); + command.execute(); } } diff --git a/src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderPlacedInvoker.java b/src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderPlacedInvoker.java index 4a9dd19..923a6ca 100644 --- a/src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderPlacedInvoker.java +++ b/src/main/java/com/Podzilla/analytics/messaging/invokers/order/OrderPlacedInvoker.java @@ -18,7 +18,6 @@ public OrderPlacedInvoker(final CommandFactory commandFactory) { @Override public void invoke(final OrderPlacedEvent event) { - System.out.println("Invoking PlaceOrderInvoker henaaa"); PlaceOrderCommand command = commandFactory .createPlaceOrderCommand( event.getOrderId(), diff --git a/src/main/java/com/Podzilla/analytics/services/OrderAnalyticsService.java b/src/main/java/com/Podzilla/analytics/services/OrderAnalyticsService.java index cc5ff63..eb74223 100644 --- a/src/main/java/com/Podzilla/analytics/services/OrderAnalyticsService.java +++ b/src/main/java/com/Podzilla/analytics/services/OrderAnalyticsService.java @@ -16,6 +16,7 @@ import com.Podzilla.analytics.api.projections.order.OrderFailureReasonsProjection; import com.Podzilla.analytics.api.projections.order.OrderRegionProjection; import com.Podzilla.analytics.api.projections.order.OrderStatusProjection; +import com.Podzilla.analytics.repositories.CourierRepository; import com.Podzilla.analytics.repositories.CustomerRepository; import com.Podzilla.analytics.repositories.OrderRepository; import com.Podzilla.analytics.util.DatetimeFormatter; @@ -23,9 +24,11 @@ import com.Podzilla.analytics.models.Order.OrderStatus; import com.Podzilla.analytics.models.Region; import com.Podzilla.analytics.models.Customer; +import com.Podzilla.analytics.models.Courier; import com.Podzilla.analytics.util.StringToUUIDParser; + import lombok.RequiredArgsConstructor; import java.util.UUID; @@ -36,6 +39,7 @@ public class OrderAnalyticsService { private final OrderRepository orderRepository; private final CustomerRepository customerRepository; + private final CourierRepository courierRepository; private final OrderItemService orderItemService; public List getOrdersByRegion( @@ -146,4 +150,118 @@ public Order saveOrder( order.setOrderItems(orderItems); return orderRepository.save(order); } + + public Order cancelOrder( + final String orderId, + final String reason, + final Instant timeStamp + ) { + UUID orderUUID = + StringToUUIDParser.parseStringToUUID(orderId); + LocalDateTime orderCancelledTimestamp = + DatetimeFormatter.convertIntsantToDateTime(timeStamp); + Order order = + orderRepository.findById(orderUUID) + .orElseThrow(() -> new RuntimeException("Order not found")); + order.setStatus(OrderStatus.CANCELLED); + order.setFailureReason(reason); + order.setOrderCancelledTimestamp(orderCancelledTimestamp); + order.setFinalStatusTimestamp(orderCancelledTimestamp); + return orderRepository.save(order); + } + + public void assignCourier( + final String orderId, + final String courierId + ) { + UUID orderUUID = + StringToUUIDParser.parseStringToUUID(orderId); + UUID courierUUID = + StringToUUIDParser.parseStringToUUID(courierId); + Order order = + orderRepository.findById(orderUUID) + .orElseThrow(() -> new RuntimeException("Order not found")); + Courier courier = + courierRepository.findById(courierUUID) + .orElseThrow(() -> new RuntimeException("Courier not found")); + order.setCourier(courier); + orderRepository.save(order); + } + public void markOrderAsOutForDelivery( + final String orderId, + final Instant timeStamp + ) { + UUID orderUUID = + StringToUUIDParser.parseStringToUUID(orderId); + LocalDateTime orderOutForDeliveryTimestamp = + DatetimeFormatter.convertIntsantToDateTime(timeStamp); + Order order = + orderRepository.findById(orderUUID) + .orElseThrow(() -> new RuntimeException("Order not found")); + order.setStatus(OrderStatus.SHIPPED); + order.setShippedTimestamp(orderOutForDeliveryTimestamp); + order.setFinalStatusTimestamp(orderOutForDeliveryTimestamp); + orderRepository.save(order); + } + + public void markOrderAsDelivered( + final String orderId, + final BigDecimal courierRating, + final Instant timeStamp + ) { + UUID orderUUID = + StringToUUIDParser.parseStringToUUID(orderId); + LocalDateTime orderDeliveredTimestamp = + DatetimeFormatter.convertIntsantToDateTime(timeStamp); + Order order = + orderRepository.findById(orderUUID) + .orElseThrow(() -> new RuntimeException("Order not found")); + order.setStatus(OrderStatus.DELIVERED); + order.setDeliveredTimestamp(orderDeliveredTimestamp); + order.setFinalStatusTimestamp(orderDeliveredTimestamp); + order.setCourierRating(courierRating); + orderRepository.save(order); + } + + public void markOrderAsFailedToDeliver( + final String orderId, + final String reason, + final Instant timeStamp + ) { + UUID orderUUID = + StringToUUIDParser.parseStringToUUID(orderId); + LocalDateTime orderFailedToDeliverTimestamp = + DatetimeFormatter.convertIntsantToDateTime(timeStamp); + Order order = + orderRepository.findById(orderUUID) + .orElseThrow(() -> new RuntimeException("Order not found")); + order.setStatus(OrderStatus.DELIVERY_FAILED); + order.setFailureReason(reason); + order.setOrderDeliveryFailedTimestamp( + orderFailedToDeliverTimestamp + ); + order.setFinalStatusTimestamp(orderFailedToDeliverTimestamp); + orderRepository.save(order); + } + + public void markOrderAsFailedToFulfill( + final String orderId, + final String reason, + final Instant timeStamp + ) { + UUID orderUUID = + StringToUUIDParser.parseStringToUUID(orderId); + LocalDateTime orderFulfillmentFailedTimestamp = + DatetimeFormatter.convertIntsantToDateTime(timeStamp); + Order order = + orderRepository.findById(orderUUID) + .orElseThrow(() -> new RuntimeException("Order not found")); + order.setStatus(OrderStatus.FULFILLMENT_FAILED); + order.setFailureReason(reason); + order.setOrderFulfillmentFailedTimestamp( + orderFulfillmentFailedTimestamp + ); + order.setFinalStatusTimestamp(orderFulfillmentFailedTimestamp); + orderRepository.save(order); + } }