diff --git a/docker-compose.yml b/docker-compose.yml
index c126715..1ff4abe 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -23,6 +23,16 @@ services:
ports:
- "5432:5432"
+ rabbitmq:
+ image: rabbitmq:3-management
+ container_name: rabbitmq
+ ports:
+ - "15672:15672"
+ - "5672:5672"
+ environment:
+ RABBITMQ_DEFAULT_USER: user
+ RABBITMQ_DEFAULT_PASS: password
+
loki:
image: grafana/loki:3.5.0
container_name: loki
diff --git a/pom.xml b/pom.xml
index a7ebd93..a4bf087 100644
--- a/pom.xml
+++ b/pom.xml
@@ -29,11 +29,22 @@
23
+
+
+ jitpack.io
+ https://jitpack.io
+
+
org.springframework.boot
spring-boot-starter-data-jdbc
+
+ org.springframework.cloud
+ spring-cloud-starter-stream-rabbit
+ 4.0.0
+
org.springdoc
springdoc-openapi-starter-webmvc-ui
@@ -82,8 +93,12 @@
spring-boot-starter-test
test
+
+ com.github.Podzilla
+ podzilla-utils-lib
+ v1.1.11
+
-
diff --git a/src/main/java/com/podzilla/order/OrderApplication.java b/src/main/java/com/podzilla/order/OrderApplication.java
index 7492983..7d4ba92 100644
--- a/src/main/java/com/podzilla/order/OrderApplication.java
+++ b/src/main/java/com/podzilla/order/OrderApplication.java
@@ -2,8 +2,10 @@
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication
+@ComponentScan(basePackages = {"com.podzilla"})
public class OrderApplication {
public static void main(final String[] args) {
diff --git a/src/main/java/com/podzilla/order/config/WebClientConfig.java b/src/main/java/com/podzilla/order/config/WebClientConfig.java
new file mode 100644
index 0000000..c0e81c6
--- /dev/null
+++ b/src/main/java/com/podzilla/order/config/WebClientConfig.java
@@ -0,0 +1,14 @@
+package com.podzilla.order.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.reactive.function.client.WebClient;
+
+@Configuration
+public class WebClientConfig {
+
+ @Bean
+ public WebClient webClient() {
+ return WebClient.builder().build();
+ }
+}
diff --git a/src/main/java/com/podzilla/order/controller/OrderController.java b/src/main/java/com/podzilla/order/controller/OrderController.java
index 2e21d66..1c18bb6 100644
--- a/src/main/java/com/podzilla/order/controller/OrderController.java
+++ b/src/main/java/com/podzilla/order/controller/OrderController.java
@@ -1,6 +1,7 @@
package com.podzilla.order.controller;
import com.podzilla.order.model.Order;
+import com.podzilla.order.model.OrderLocation;
import com.podzilla.order.model.OrderStatus;
import com.podzilla.order.service.OrderService;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -13,6 +14,7 @@
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
@@ -38,6 +40,8 @@ public OrderController(final OrderService orderService) {
this.orderService = orderService;
}
+
+
@PostMapping
@Operation(summary = "Create a new order",
description = "Creates a new order and returns the created order")
@@ -49,6 +53,8 @@ public ResponseEntity createOrder(@RequestBody final Order order) {
return ResponseEntity.ok(createdOrder);
}
+
+
@GetMapping
@Operation(summary = "Get all orders",
description = "Returns a list of all orders")
@@ -59,6 +65,8 @@ public ResponseEntity> getAllOrders() {
return ResponseEntity.ok(orders);
}
+
+
@GetMapping("/{id}")
@Operation(summary = "Get order by ID",
description = "Returns an order by its ID")
@@ -70,7 +78,9 @@ public ResponseEntity getOrderById(@PathVariable final UUID id) {
return ResponseEntity.ok(order);
}
- @PutMapping("/{id}")
+
+
+ @PatchMapping("/{id}")
@Operation(summary = "Update an order",
description = "Updates an existing order and returns the updated "
+ "order")
@@ -83,6 +93,8 @@ public ResponseEntity updateOrder(@PathVariable final UUID id,
return ResponseEntity.ok(updatedOrder);
}
+
+
@DeleteMapping("/{id}")
@Operation(summary = "Delete an order",
description = "Deletes an order by its ID")
@@ -94,6 +106,8 @@ public ResponseEntity deleteOrder(@PathVariable final UUID id) {
return ResponseEntity.noContent().build();
}
+
+
@GetMapping("/user/{userId}")
@Operation(
summary = "Get order by user ID",
@@ -109,6 +123,8 @@ public ResponseEntity> getOrderByUserId(
return ResponseEntity.ok(order);
}
+
+
@PutMapping("/cancel/{id}")
@Operation(
summary = "Cancel order",
@@ -117,12 +133,15 @@ public ResponseEntity> getOrderByUserId(
@ApiResponse(
responseCode = "200", description = "Order cancelled"
)
- public ResponseEntity cancelOrder(@PathVariable final UUID id) {
- Order order = orderService.cancelOrder(id);
+ public ResponseEntity cancelOrder(@PathVariable final UUID id,
+ @RequestBody final String reason) {
+ Order order = orderService.cancelOrder(id, reason);
LOGGER.info("Order with ID: {} cancelled", id);
return ResponseEntity.ok(order);
}
+
+
@PutMapping("/status/{id}")
@Operation(
summary = "Update order status",
@@ -138,4 +157,21 @@ public ResponseEntity updateOrderStatus(@PathVariable final UUID id,
LOGGER.info("Order status updated for ID: {}", id);
return ResponseEntity.ok(order);
}
+
+
+
+ @GetMapping("/trackOrder/{id}")
+ @Operation(
+ summary = "Track order",
+ description = "Tracks the location of an order based on "
+ + "the provided order ID"
+ )
+ @ApiResponse(
+ responseCode = "200", description = "Order location tracked"
+ )
+ public ResponseEntity trackOrder(@PathVariable final UUID id) {
+ OrderLocation location = orderService.trackOrder(id);
+ LOGGER.info("Order location tracked for ID: {}", id);
+ return ResponseEntity.ok(location);
+ }
}
diff --git a/src/main/java/com/podzilla/order/dtos/LocationDTO.java b/src/main/java/com/podzilla/order/dtos/LocationDTO.java
new file mode 100644
index 0000000..bfa3dfb
--- /dev/null
+++ b/src/main/java/com/podzilla/order/dtos/LocationDTO.java
@@ -0,0 +1,14 @@
+package com.podzilla.order.dtos;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class LocationDTO {
+ private double first; // Latitude
+ private double second; // Longitude
+}
diff --git a/src/main/java/com/podzilla/order/messaging/OrderConsumer.java b/src/main/java/com/podzilla/order/messaging/OrderConsumer.java
new file mode 100644
index 0000000..9549673
--- /dev/null
+++ b/src/main/java/com/podzilla/order/messaging/OrderConsumer.java
@@ -0,0 +1,163 @@
+package com.podzilla.order.messaging;
+
+import com.podzilla.mq.EventsConstants;
+import com.podzilla.mq.events.BaseEvent;
+import com.podzilla.mq.events.CartCheckedoutEvent;
+import com.podzilla.mq.events.OrderAssignedToCourierEvent;
+import com.podzilla.mq.events.OrderDeliveredEvent;
+import com.podzilla.mq.events.OrderOutForDeliveryEvent;
+import com.podzilla.mq.events.OrderPackagedEvent;
+import com.podzilla.mq.events.WarehouseOrderFulfillmentFailedEvent;
+import com.podzilla.mq.events.WarehouseStockReservedEvent;
+import com.podzilla.order.model.Address;
+import com.podzilla.order.model.Order;
+import com.podzilla.order.model.OrderProduct;
+import com.podzilla.order.model.OrderStatus;
+
+import com.podzilla.order.service.OrderService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.amqp.rabbit.annotation.RabbitListener;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+import java.util.UUID;
+
+@Component
+@Slf4j
+public class OrderConsumer {
+
+ private final OrderService orderService;
+
+ public OrderConsumer(final OrderService orderService) {
+ this.orderService = orderService;
+ }
+
+ @RabbitListener(queues = EventsConstants.ORDER_INVENTORY_EVENT_QUEUE)
+ public void handleStockReserved(final BaseEvent payload) {
+ if (payload instanceof WarehouseStockReservedEvent) {
+ WarehouseStockReservedEvent warehouseStockReservedEvent =
+ (WarehouseStockReservedEvent) payload;
+ handleWarehouseStockReservedEvent(warehouseStockReservedEvent);
+ }
+ if (payload instanceof WarehouseOrderFulfillmentFailedEvent) {
+ WarehouseOrderFulfillmentFailedEvent warehouseOrderFulfillmentFailedEvent =
+ (WarehouseOrderFulfillmentFailedEvent) payload;
+ handleWarehouseOrderFulfillmentFailedEvent(
+ warehouseOrderFulfillmentFailedEvent);
+ }
+ }
+
+ @RabbitListener(queues = EventsConstants.ORDER_ORDER_EVENT_QUEUE)
+ public void trackOrder(final BaseEvent payload) {
+ if (payload instanceof OrderPackagedEvent) {
+ OrderPackagedEvent orderPackagedEvent = (OrderPackagedEvent) payload;
+ handleOrderPackagedEvent(orderPackagedEvent);
+ }
+ if (payload instanceof OrderAssignedToCourierEvent) {
+ OrderAssignedToCourierEvent orderAssignedToCourierEvent =
+ (OrderAssignedToCourierEvent) payload;
+ handleOrderAssignedToCourierEvent(orderAssignedToCourierEvent);
+ }
+ if (payload instanceof OrderOutForDeliveryEvent) {
+ OrderOutForDeliveryEvent orderOutForDeliveryEvent =
+ (OrderOutForDeliveryEvent) payload;
+ handleOrderOutForDeliveryEvent(orderOutForDeliveryEvent);
+ }
+ if (payload instanceof OrderDeliveredEvent) {
+ OrderDeliveredEvent orderDeliveredEvent =
+ (OrderDeliveredEvent) payload;
+ handleOrderDeliveredEvent(orderDeliveredEvent);
+ }
+ if (payload instanceof CartCheckedoutEvent) {
+ CartCheckedoutEvent cartCheckedoutEvent =
+ (CartCheckedoutEvent) payload;
+ handleCartCheckoutEvent(cartCheckedoutEvent);
+ }
+ }
+
+ private void handleWarehouseStockReservedEvent(
+ final WarehouseStockReservedEvent warehouseStockReservedEvent) {
+
+ log.info("✅ Stock reserved for order: {}",
+ warehouseStockReservedEvent.getOrderId());
+ orderService.placeOrder(UUID.fromString(warehouseStockReservedEvent.getOrderId()));
+ }
+
+ private void handleWarehouseOrderFulfillmentFailedEvent(
+ final WarehouseOrderFulfillmentFailedEvent
+ warehouseOrderFulfillmentFailedEvent) {
+ log.info("❌ Order fulfillment failed for order: {}, reason: {}",
+ warehouseOrderFulfillmentFailedEvent.getOrderId(),
+ warehouseOrderFulfillmentFailedEvent.getReason());
+ orderService.cancelOrder(
+ UUID.fromString(warehouseOrderFulfillmentFailedEvent.getOrderId()),
+ warehouseOrderFulfillmentFailedEvent.getReason());
+ }
+
+ private void handleCartCheckoutEvent(
+ final CartCheckedoutEvent cartCheckedoutEvent) {
+ log.info("✅ Cart checked out for user with id: {}",
+ cartCheckedoutEvent.getCustomerId());
+ Address address = new Address();
+ address.setStreet(cartCheckedoutEvent.getDeliveryAddress().getStreet());
+ address.setCity(cartCheckedoutEvent.getDeliveryAddress().getCity());
+ address.setState(cartCheckedoutEvent.getDeliveryAddress().getState());
+ address.setCountry(cartCheckedoutEvent.getDeliveryAddress().getCountry());
+ address.setPostalCode(cartCheckedoutEvent.getDeliveryAddress().getPostalCode());
+ List orderProducts = cartCheckedoutEvent.getItems().stream()
+ .map(orderProduct -> {
+ OrderProduct product = new OrderProduct();
+ product.setProductId(UUID.fromString(orderProduct.getProductId()));
+ product.setQuantity(orderProduct.getQuantity());
+ product.setPricePerUnit(orderProduct.getPricePerUnit());
+ return product;
+ }).toList();
+ Order order = new Order();
+ order.setUserId(UUID.fromString(cartCheckedoutEvent.getCustomerId()));
+ order.setTotalAmount(cartCheckedoutEvent.getTotalAmount());
+ order.setStatus(OrderStatus.PENDING);
+ order.setShippingAddress(address);
+ order.setOrderProducts(orderProducts);
+ order.setConfirmationType(cartCheckedoutEvent.getConfirmationType());
+ order.setSignature(cartCheckedoutEvent.getSignature());
+ order.setOrderLatitude(cartCheckedoutEvent.getOrderLatitude());
+ order.setOrderLongitude(cartCheckedoutEvent.getOrderLongitude());
+ orderService.createOrder(order);
+ }
+
+ private void handleOrderAssignedToCourierEvent(final OrderAssignedToCourierEvent orderAssignedToCourierEvent) {
+ log.info("✅ Order assigned to courier for order: {}",
+ orderAssignedToCourierEvent.getOrderId());
+ orderService.updateOrder(
+ UUID.fromString(orderAssignedToCourierEvent.getOrderId()),
+ Order.builder()
+ .courierId(UUID.fromString(orderAssignedToCourierEvent.getCourierId()))
+ .status(OrderStatus.ORDER_ASSIGNED_TO_COURIER)
+ .build());
+ }
+
+ private void handleOrderDeliveredEvent(
+ final OrderDeliveredEvent orderDeliveredEvent) {
+ log.info("✅ Order delivered for order: {}",
+ orderDeliveredEvent.getOrderId());
+ orderService.updateOrderStatus(UUID.fromString(orderDeliveredEvent.getOrderId()), OrderStatus.DELIVERED);
+ }
+
+ private void handleOrderOutForDeliveryEvent(final OrderOutForDeliveryEvent orderOutForDeliveryEvent) {
+ log.info("✅ Order out for delivery for order: {}", orderOutForDeliveryEvent.getOrderId());
+ orderService.updateOrder(
+ UUID.fromString(orderOutForDeliveryEvent.getOrderId()),
+ Order.builder()
+ .courierId(UUID.fromString(orderOutForDeliveryEvent.getCourierId()))
+ .status(OrderStatus.OUT_FOR_DELIVERY)
+ .build());
+ }
+
+ private void handleOrderPackagedEvent(final OrderPackagedEvent orderPackagedEvent) {
+ log.info("✅ Order packaged for order: {}",
+ orderPackagedEvent.getOrderId());
+ orderService.updateOrderStatus(
+ UUID.fromString(orderPackagedEvent.getOrderId()),
+ OrderStatus.PACKAGED);
+ }
+}
diff --git a/src/main/java/com/podzilla/order/messaging/OrderProducer.java b/src/main/java/com/podzilla/order/messaging/OrderProducer.java
new file mode 100644
index 0000000..a34781d
--- /dev/null
+++ b/src/main/java/com/podzilla/order/messaging/OrderProducer.java
@@ -0,0 +1,43 @@
+package com.podzilla.order.messaging;
+
+import com.podzilla.mq.EventPublisher;
+import com.podzilla.mq.EventsConstants;
+import com.podzilla.mq.events.OrderCancelledEvent;
+import com.podzilla.mq.events.OrderPlacedEvent;
+import com.podzilla.mq.events.OrderStockReservationRequestedEvent;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Component;
+
+@Component
+@RequiredArgsConstructor
+public class OrderProducer {
+
+
+ private final EventPublisher eventPublisher;
+
+
+ public void sendStockReservationRequest(
+ final OrderStockReservationRequestedEvent orderStockReservationRequestedEvent) {
+
+ eventPublisher.publishEvent(
+ EventsConstants.ORDER_STOCK_RESERVATION_REQUESTED,
+ orderStockReservationRequestedEvent
+ );
+ }
+
+ public void sendOrderPlaced(
+ final OrderPlacedEvent orderPlaced) {
+ eventPublisher.publishEvent(
+ EventsConstants.ORDER_PLACED,
+ orderPlaced
+ );
+ }
+
+ public void sendCancelOrder(
+ final OrderCancelledEvent orderCancelledEvent) {
+ eventPublisher.publishEvent(
+ EventsConstants.ORDER_CANCELLED,
+ orderCancelledEvent
+ );
+ }
+}
diff --git a/src/main/java/com/podzilla/order/messaging/RabbitMQConfig.java b/src/main/java/com/podzilla/order/messaging/RabbitMQConfig.java
new file mode 100644
index 0000000..2d8892b
--- /dev/null
+++ b/src/main/java/com/podzilla/order/messaging/RabbitMQConfig.java
@@ -0,0 +1,68 @@
+package com.podzilla.order.messaging;
+
+import org.springframework.amqp.core.Binding;
+import org.springframework.amqp.core.BindingBuilder;
+import org.springframework.amqp.core.Queue;
+import org.springframework.amqp.core.TopicExchange;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class RabbitMQConfig {
+ // Exchange
+ public static final String EXCHANGE = "shared_exchange";
+
+ // Outbound (sending to warehouse)
+ public static final String ORDER_ROUTING_KEY =
+ "order.stock_reservation_requested";
+
+ public static final String ORDER_PLACED_ROUTING_KEY =
+ "order.placed";
+
+ // Inbound (responses from warehouse)
+ public static final String STOCK_RESERVED_ROUTING_KEY =
+ "warehouse.stock_reserved";
+ public static final String ORDER_FAILED_ROUTING_KEY =
+ "warehouse.order_fulfillment_failed";
+
+ // Queues this service will consume from (responses from warehouse)
+ public static final String STOCK_RESERVED_QUEUE =
+ "order.stock_reserved.queue";
+ public static final String ORDER_FAILED_QUEUE =
+ "order.fulfillment_failed.queue";
+
+ @Bean
+ public Queue stockReservedQueue() {
+ return new Queue(STOCK_RESERVED_QUEUE);
+ }
+
+ @Bean
+ public Queue orderFailedQueue() {
+ return new Queue(ORDER_FAILED_QUEUE);
+ }
+
+ @Bean
+ public TopicExchange exchange() {
+ return new TopicExchange(EXCHANGE);
+ }
+
+ @Bean
+ public Binding warehouseStockReservedBinding(
+ final Queue stockReservedQueue,
+ final TopicExchange exchange) {
+ return BindingBuilder
+ .bind(stockReservedQueue)
+ .to(exchange)
+ .with("warehouse.stock_reserved");
+ }
+
+ @Bean
+ public Binding warehouseFulfillmentFailedBinding(
+ final Queue orderFailedQueue,
+ final TopicExchange exchange) {
+ return BindingBuilder
+ .bind(orderFailedQueue)
+ .to(exchange)
+ .with("warehouse.order_fulfillment_failed");
+ }
+}
diff --git a/src/main/java/com/podzilla/order/model/Order.java b/src/main/java/com/podzilla/order/model/Order.java
index fbc14ee..11f857b 100644
--- a/src/main/java/com/podzilla/order/model/Order.java
+++ b/src/main/java/com/podzilla/order/model/Order.java
@@ -7,16 +7,18 @@
import jakarta.persistence.Id;
import jakarta.persistence.OneToMany;
import jakarta.persistence.OneToOne;
-import jakarta.persistence.Column;
import jakarta.persistence.Enumerated;
import jakarta.persistence.EnumType;
import jakarta.persistence.CascadeType;
import jakarta.persistence.GenerationType;
+import com.podzilla.mq.events.ConfirmationType;
import lombok.AllArgsConstructor;
+import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
+import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@@ -28,41 +30,38 @@
@AllArgsConstructor
@Getter
@Setter
+@Builder
public class Order {
@Id
- @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @GeneratedValue(strategy = GenerationType.UUID)
private UUID id;
private UUID userId;
- private double totalAmount;
+ private UUID courierId;
+
+ private BigDecimal totalAmount;
@OneToOne(cascade = CascadeType.ALL)
private Address shippingAddress;
@Enumerated(EnumType.STRING)
- @Column(nullable = false)
private OrderStatus status;
- @Column(nullable = false)
private LocalDateTime createdAt;
- @Column(nullable = false)
private LocalDateTime updatedAt;
- @OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval =
- true)
- private List orderItems = new ArrayList<>();
+ private ConfirmationType confirmationType;
+
+ private String signature;
+
+ private double orderLatitude;
- public Order(final UUID userId, final double totalAmount,
- final OrderStatus status, final List orderItems) {
- this.userId = userId;
- this.totalAmount = totalAmount;
- this.status = status;
- this.createdAt = LocalDateTime.now();
- this.updatedAt = LocalDateTime.now();
- this.orderItems = orderItems;
- }
+ private double orderLongitude;
+ @OneToMany(mappedBy = "order", cascade = CascadeType.ALL,
+ orphanRemoval = true)
+ private List orderProducts = new ArrayList<>();
}
diff --git a/src/main/java/com/podzilla/order/model/OrderLocation.java b/src/main/java/com/podzilla/order/model/OrderLocation.java
new file mode 100644
index 0000000..b4fe5cb
--- /dev/null
+++ b/src/main/java/com/podzilla/order/model/OrderLocation.java
@@ -0,0 +1,18 @@
+package com.podzilla.order.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.Data;
+
+@Getter
+@Setter
+@NoArgsConstructor
+@AllArgsConstructor
+@Data
+public class OrderLocation {
+
+ private double latitude;
+ private double longitude;
+}
diff --git a/src/main/java/com/podzilla/order/model/OrderItem.java b/src/main/java/com/podzilla/order/model/OrderProduct.java
similarity index 75%
rename from src/main/java/com/podzilla/order/model/OrderItem.java
rename to src/main/java/com/podzilla/order/model/OrderProduct.java
index 0d7a03c..dad2e31 100644
--- a/src/main/java/com/podzilla/order/model/OrderItem.java
+++ b/src/main/java/com/podzilla/order/model/OrderProduct.java
@@ -4,36 +4,39 @@
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
-import jakarta.persistence.Table;
-import jakarta.persistence.Column;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
+import jakarta.persistence.Table;
+import jakarta.persistence.Column;
import jakarta.persistence.FetchType;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
+import java.math.BigDecimal;
+import java.util.UUID;
+
@Entity
-@Table(name = "order_items")
+@Table(name = "order_products")
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
-public class OrderItem {
+public class OrderProduct {
@Id
- @GeneratedValue(strategy = GenerationType.IDENTITY)
- private long id;
+ @GeneratedValue(strategy = GenerationType.UUID)
+ private UUID id;
@Column(nullable = false)
- private long productId;
+ private UUID productId;
@Column(nullable = false)
private int quantity;
@Column(nullable = false)
- private double price;
+ private BigDecimal pricePerUnit;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "order_id", nullable = false)
diff --git a/src/main/java/com/podzilla/order/model/OrderStatus.java b/src/main/java/com/podzilla/order/model/OrderStatus.java
index 07de71e..5b56052 100644
--- a/src/main/java/com/podzilla/order/model/OrderStatus.java
+++ b/src/main/java/com/podzilla/order/model/OrderStatus.java
@@ -1,10 +1,12 @@
package com.podzilla.order.model;
public enum OrderStatus {
+ PENDING,
PLACED,
- CANCELLED,
- SHIPPED,
+ PACKAGED,
+ ORDER_ASSIGNED_TO_COURIER,
+ OUT_FOR_DELIVERY,
DELIVERED,
- FAILED
+ CANCELLED,
}
diff --git a/src/main/java/com/podzilla/order/service/OrderService.java b/src/main/java/com/podzilla/order/service/OrderService.java
index fab9aba..8bfd0a7 100644
--- a/src/main/java/com/podzilla/order/service/OrderService.java
+++ b/src/main/java/com/podzilla/order/service/OrderService.java
@@ -1,14 +1,27 @@
package com.podzilla.order.service;
+import com.podzilla.mq.events.OrderCancelledEvent;
+import com.podzilla.mq.events.OrderItem;
+import com.podzilla.mq.events.OrderPlacedEvent;
+import com.podzilla.mq.events.OrderStockReservationRequestedEvent;
+import com.podzilla.mq.events.DeliveryAddress;
+import com.podzilla.order.dtos.LocationDTO;
import com.podzilla.order.exception.NotFoundException;
+import com.podzilla.order.messaging.OrderProducer;
import com.podzilla.order.model.Order;
+import com.podzilla.order.model.OrderLocation;
+import com.podzilla.order.model.OrderProduct;
import com.podzilla.order.model.OrderStatus;
import com.podzilla.order.repository.OrderRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
+import org.springframework.web.reactive.function.client.WebClient;
+
import java.time.LocalDateTime;
+import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
@@ -18,17 +31,59 @@
@Service
public class OrderService {
private final OrderRepository orderRepository;
+ private final OrderProducer orderProducer;
+ private final WebClient webClient;
+
+ @Value("${api.gateway.url}")
+ private String apiGatewayUrl;
@Autowired
- public OrderService(final OrderRepository orderRepository) {
+ public OrderService(final OrderRepository orderRepository,
+ final OrderProducer orderProducer,
+ final WebClient webClient) {
this.orderRepository = orderRepository;
+ this.orderProducer = orderProducer;
+ this.webClient = webClient;
}
public Order createOrder(final Order order) {
log.info("Creating new order: {}", order);
order.setCreatedAt(LocalDateTime.now());
order.setUpdatedAt(LocalDateTime.now());
- return orderRepository.save(order);
+ orderRepository.save(order);
+ OrderStockReservationRequestedEvent stockReservationRequest =
+ OrderStockReservationRequestedEvent.builder()
+ .orderId(order.getId().toString())
+ .items(getOrderItems(order))
+ .build();
+ orderProducer.sendStockReservationRequest(stockReservationRequest);
+ return order;
+ }
+
+ public Order placeOrder(final UUID orderId) {
+ log.info("Placing order with ID: {}", orderId);
+ Order order = updateOrderStatus(orderId, OrderStatus.PLACED);
+ DeliveryAddress deliveryAddress = new DeliveryAddress(
+ order.getShippingAddress().getStreet(),
+ order.getShippingAddress().getCity(),
+ order.getShippingAddress().getState(),
+ order.getShippingAddress().getCountry(),
+ order.getShippingAddress().getPostalCode()
+ );
+ OrderPlacedEvent orderPlaced =
+ OrderPlacedEvent.builder()
+ .orderId(order.getId().toString())
+ .customerId(order.getUserId().toString())
+ .items(getOrderItems(order))
+ .totalAmount(order.getTotalAmount())
+ .deliveryAddress(deliveryAddress)
+ .confirmationType(order.getConfirmationType())
+ .signature(order.getSignature())
+ .orderLatitude(order.getOrderLatitude())
+ .orderLongitude(order.getOrderLongitude())
+ .build();
+ orderProducer.sendOrderPlaced(orderPlaced);
+ return order;
}
public List getAllOrders() {
@@ -76,7 +131,7 @@ public Optional getOrderByUserId(final UUID userId) {
return orderRepository.findByUserId(userId);
}
- public Order cancelOrder(final UUID id) {
+ public Order cancelOrder(final UUID id, final String reason) {
log.info("Cancelling order with ID: {}", id);
Optional existingOrder = orderRepository.findById(id);
@@ -87,7 +142,15 @@ public Order cancelOrder(final UUID id) {
Order order = existingOrder.get();
order.setStatus(OrderStatus.CANCELLED);
order.setUpdatedAt(LocalDateTime.now());
- return orderRepository.save(order);
+ orderRepository.save(order);
+ OrderCancelledEvent orderCancelledEvent =
+ OrderCancelledEvent.builder()
+ .orderId(order.getId().toString())
+ .customerId(order.getUserId().toString())
+ .reason(reason)
+ .build();
+ orderProducer.sendCancelOrder(orderCancelledEvent);
+ return order;
}
public Order updateOrderStatus(final UUID id,
@@ -105,10 +168,45 @@ public Order updateOrderStatus(final UUID id,
return orderRepository.save(order);
}
+ public OrderLocation trackOrder(final UUID id) {
+ log.info("Tracking order with ID: {}", id);
+ Optional existingOrder = orderRepository.findById(id);
+ checkNotFoundException(existingOrder.orElse(null),
+ "Order not found with id: " + id);
+ String url = apiGatewayUrl + "/delivery-tasks/" + id + "/location";
+ LocationDTO location = webClient
+ .get()
+ .uri(url)
+ .retrieve()
+ .bodyToMono(LocationDTO.class)
+ .block();
+
+ if (location == null) {
+ throw new RuntimeException("Failed to get location for order " + id);
+ }
+ log.info("Order location: {}", location);
+ return new OrderLocation(location.getFirst(), location.getSecond());
+ }
+
private void checkNotFoundException(final Object value,
final String message) {
if (value == null) {
throw new NotFoundException(message);
}
}
+
+ private List getOrderItems(final Order order) {
+ List orderItems = new ArrayList<>();
+ List orderProducts = order.getOrderProducts();
+ for (OrderProduct product : orderProducts) {
+ OrderItem orderItem = new OrderItem();
+ orderItem.setProductId(product.getId().toString());
+ orderItem.setQuantity(product.getQuantity());
+ orderItem.setPricePerUnit(product.getPricePerUnit());
+ orderItems.add(orderItem);
+ }
+ return orderItems;
+ }
+
+
}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index bcd3ef5..6b013d9 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -1 +1,2 @@
spring.application.name=order
+api.gateway.url=http://localhost:8080
\ No newline at end of file
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
new file mode 100644
index 0000000..0bc1010
--- /dev/null
+++ b/src/main/resources/application.yml
@@ -0,0 +1,8 @@
+spring:
+ application:
+ name: User
+ rabbitmq:
+ host: rabbitmq # container name
+ port: 5672
+ username: user
+ password: password
diff --git a/src/test/java/com/podzilla/order/OrderApplicationTests.java b/src/test/java/com/podzilla/order/OrderApplicationTests.java
index a1d6a75..8556e0a 100644
--- a/src/test/java/com/podzilla/order/OrderApplicationTests.java
+++ b/src/test/java/com/podzilla/order/OrderApplicationTests.java
@@ -1,13 +1,13 @@
-package com.podzilla.order;
-
-import org.junit.jupiter.api.Test;
-import org.springframework.boot.test.context.SpringBootTest;
-
-@SpringBootTest
-class OrderApplicationTests {
-
- @Test
- void contextLoads() {
- }
-
-}
+//package com.podzilla.order;
+//
+//import org.junit.jupiter.api.Test;
+//import org.springframework.boot.test.context.SpringBootTest;
+//
+//@SpringBootTest
+//class OrderApplicationTests {
+//
+// @Test
+// void contextLoads() {
+// }
+//
+//}