From 6b76241856e01153bb4b3aa21e6819d2c4052064 Mon Sep 17 00:00:00 2001 From: Ariel Date: Tue, 7 Oct 2025 12:37:31 +0200 Subject: [PATCH] update OrderItem to include variantId and related fields; add reservationId to Order and OrderToCreate --- .gitignore | 1 + CLAUDE.md | 230 ------------------ pom.xml | 6 +- .../e2e/tests/CreateOrderE2ETest.java | 4 + .../api/grpc/OrderGrpcMapper.java | 85 +++---- .../api/grpc/OrderGrpcService.java | 85 +------ .../api/grpc/ReturnGrpcMapper.java | 3 +- .../api/kafka/CartCreatedEvent.java | 11 +- .../api/rest/order/OrderControllerMapper.java | 2 +- .../api/rest/order/OrdersController.java | 1 + .../rest/order/dto/CreateOrderRequest.java | 15 +- .../adapter/repository/db/OrderEntity.java | 12 + .../repository/db/OrderEntityMapper.java | 18 +- .../repository/db/OrderItemEntity.java | 32 +++ .../order/domain/DefaultOrderFacade.java | 2 + .../orderservice/order/domain/Order.java | 9 +- .../orderservice/order/domain/OrderItem.java | 6 +- .../order/domain/OrderStatus.java | 11 +- .../order/domain/OrderToCreate.java | 2 + .../order/domain/ReservationId.java | 12 + .../migration/V1__Create_initial_schema.sql | 13 +- .../db/migration/V2__Insert_example_data.sql | 58 ++--- .../grpc/OrderGrpcServiceTest.java | 196 +-------------- .../inmemory/InMemoryOrderRepositoryTest.java | 4 + .../order/api/OrdersControllerTest.java | 2 + .../order/domain/OrderFacadeTest.java | 44 +++- 26 files changed, 247 insertions(+), 617 deletions(-) delete mode 100644 CLAUDE.md create mode 100644 src/main/java/com/ecmsp/orderservice/order/domain/ReservationId.java diff --git a/.gitignore b/.gitignore index 667aaef..3bafc06 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ target/ .mvn/wrapper/maven-wrapper.jar !**/src/main/**/target/ !**/src/test/**/target/ +scripts/ ### STS ### .apt_generated diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index 621ef2f..0000000 --- a/CLAUDE.md +++ /dev/null @@ -1,230 +0,0 @@ -# CLAUDE.md - -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. - -## Development Commands - -### Build and Test -- `mvn clean compile` - Compile the project -- `mvn clean test` - Run unit tests (excludes e2e tests) -- `mvn clean verify` - Run all tests including e2e tests -- `mvn clean package` - Build the JAR file -- `mvn spring-boot:run` - Run the application locally - -### Local Development -- Run with `local` Spring profile for development (uses testcontainers for Kafka) -- Access Kafka UI at `http://localhost:8088` when running locally -- Docker daemon must be running for local development - -### Testing -- Unit tests: `mvn test` -- E2E tests: `mvn failsafe:integration-test` -- Single test: `mvn test -Dtest=TestClassName` - -## Architecture Overview - -### Domain-Driven Design Structure -The codebase follows a hexagonal architecture with clear domain boundaries: - -- **Domain Layer** (`order.domain`): Core business logic with `OrderFacade` as the main entry point -- **Application Layer** (`api`): REST controllers, gRPC services, and Kafka consumers -- **Infrastructure Layer** (`order.adapter`): Database, Kafka, and external service implementations - -### Key Components -- **OrderFacade**: Main domain service interface for order operations -- **Order Aggregates**: Domain entities (Order, OrderItem, OrderStatus) -- **Repository Pattern**: Configurable implementations (DB, in-memory) -- **Event Publisher**: Kafka-based domain event publishing -- **Payment Integration**: Kafka-based communication with payment service - -### Configuration Profiles -- `local`: Uses testcontainers for Kafka and PostgreSQL -- `dev`: Development environment configuration -- `compose`: Docker compose environment -- `test`: Test environment with H2 database - -### Kafka Topics -- `payment-processed-succeeded/failed`: Payment result events -- `payment-request`: Payment initiation -- `order-status-updated`: Order state changes -- `cart-event`: Cart-related events - -### Configurable Components -The service uses Spring profiles to switch between implementations: -- Repository: `db` or `inmemory` -- Payment Client: `kafka` or `inmemory` -- Event Publisher: `kafka` or `inmemory` -- ID Generator: `random` or `fixed` - -### gRPC Integration -The service includes gRPC endpoints using protobuf definitions from the `com.ecmsp:protos` dependency. - -### Database -- PostgreSQL for production -- H2 for testing -- JPA entities with Hibernate -- Flyway for migrations (enabled by default) - -## API Endpoints for E2E Testing - -### REST API Endpoints - -#### Base URL -- **REST API**: `http://localhost:8300/api/orders` -- **Health Check**: `http://localhost:8300/health` - -#### OrdersController (`/api/orders`) - -**GET /api/orders** -- List all orders (TODO: requires admin authorization) -- Response: `200 OK` with array of `OrderDetailsResponse` - -**GET /api/orders/user/{userId}** -- List orders by user ID (uses JWT context from gateway) -- Requires: `X-User-Id` header (injected by gateway) -- Response: `200 OK` with array of `OrderDetailsResponse` - -**GET /api/orders/{orderId}** -- Get order by ID -- Path parameter: `orderId` (UUID) -- Response: `200 OK` with `OrderDetailsResponse` or `404 Not Found` - -**POST /api/orders** -- Create a new order -- Headers: - - `X-Correlation-Id` (optional): UUID for request correlation -- Request body: `CreateOrderRequest` -```json -{ - "clientId": "uuid", - "items": [ - { - "itemId": "uuid", - "quantity": 1, - "price": 99.99, - "returnable": true - } - ] -} -``` -- Response: `201 Created` with `OrderDetailsResponse` - -**PUT /api/orders/{orderId}** -- Update order status -- Path parameter: `orderId` (UUID) -- Request body: `UpdateOrderRequest` -```json -{ - "orderStatus": "PAID" -} -``` -- Valid statuses: `PENDING`, `PROCESSING`, `PAID`, `FAILED`, `CANCELLED`, `COMPLETED` -- Response: `200 OK` with `OrderDetailsResponse` - -**DELETE /api/orders/{orderId}** -- Delete an order -- Path parameter: `orderId` (UUID) -- Response: `204 No Content` - -**GET /api/orders/{orderId}/returnability** -- Get order returnability details -- Path parameter: `orderId` (UUID) -- Response: `200 OK` with `OrderReturnabilityResponse` or `404 Not Found` - -**GET /api/orders/{orderId}/returnable** -- Check if order can be returned (boolean) -- Path parameter: `orderId` (UUID) -- Response: `200 OK` with boolean value - -#### InternalOrdersController (`/api/internal/orders`) - -**POST /api/internal/orders/order-id-mappings** -- Create order ID mapping (only available when `order.id-generator.type=fixed`) -- Request body: `OrderIdMappingDto` -```json -{ - "correlationId": "uuid", - "orderId": "uuid" -} -``` -- Response: `200 OK` - -#### HealthController (`/health`) - -**GET /health** -- Health check endpoint -- Response: `200 OK` with `"OK"` string - -### gRPC API Endpoints - -#### Service Configuration -- **gRPC Server**: `localhost:7300` -- **Proto Package**: `com.ecmsp.order.v1` -- **Service**: `OrderService` - -#### gRPC Methods - -**GetOrder** -- Request: `GetOrderRequest { string order_id }` -- Response: `GetOrderResponse` with order details -- Errors: `NOT_FOUND` if order doesn't exist, `INTERNAL` for other errors - -**CreateOrder** -- Request: `CreateOrderRequest` with client ID and items -- Response: `CreateOrderResponse` with created order details -- Note: Context/metadata support is planned for future versions -- Errors: `INTERNAL` on failure - -**UpdateOrder** -- Request: `UpdateOrderRequest` with order ID and new status -- Response: `UpdateOrderResponse` with updated order details -- Errors: `NOT_FOUND` if order doesn't exist, `INTERNAL` for other errors - -**DeleteOrder** -- Request: `DeleteOrderRequest { string order_id }` -- Response: `DeleteOrderResponse { bool success }` -- Errors: `INTERNAL` on failure - -**ListOrders** -- Request: `ListOrdersRequest` (empty) -- Response: `ListOrdersResponse` with array of orders -- Errors: `INTERNAL` on failure - -### Response Models - -**OrderDetailsResponse** -```json -{ - "orderId": "uuid", - "clientId": "uuid", - "orderStatus": "PENDING|PROCESSING|PAID|FAILED|CANCELLED|COMPLETED", - "date": "2025-09-30T12:00:00", - "items": [ - { - "itemId": "uuid", - "quantity": 1 - } - ] -} -``` - -### E2E Testing Notes - -1. **Authentication**: The service expects JWT context via headers (injected by API Gateway) - - Use `/api/orders/user/{userId}` endpoint to test gateway integration - -2. **Correlation IDs**: Include `X-Correlation-Id` header for request tracing - -3. **Order Lifecycle**: - - Orders start in `PENDING` status - - Payment processing moves to `PROCESSING` then `PAID` or `FAILED` - - Can be `CANCELLED` by user or system - - Final status is `COMPLETED` after delivery - -4. **Kafka Integration**: Creating orders triggers payment requests via Kafka - - Topic: `payment-request` - - Listen on: `payment-processed-succeeded` or `payment-processed-failed` for results - -5. **Database State**: Orders are persisted to PostgreSQL (or H2 in tests) - -6. **Proto Definitions**: gRPC contracts are defined in `com.ecmsp:protos` dependency \ No newline at end of file diff --git a/pom.xml b/pom.xml index 050d820..71d98a8 100644 --- a/pom.xml +++ b/pom.xml @@ -85,9 +85,7 @@ spring-boot-starter-data-jpa - + @@ -97,7 +95,7 @@ com.ecmsp protos - 1.0.0-20251005.125206-32 + 1.0.0-20251007.110512-35 diff --git a/src/e2e-test/java/com/ecmsp/orderservice/e2e/tests/CreateOrderE2ETest.java b/src/e2e-test/java/com/ecmsp/orderservice/e2e/tests/CreateOrderE2ETest.java index a4330d0..e01c14c 100644 --- a/src/e2e-test/java/com/ecmsp/orderservice/e2e/tests/CreateOrderE2ETest.java +++ b/src/e2e-test/java/com/ecmsp/orderservice/e2e/tests/CreateOrderE2ETest.java @@ -26,17 +26,21 @@ public class CreateOrderE2ETest { private static final List ITEMS = List.of( new CartItem( ITEM_1_ID, + null, "Item 1", new java.math.BigDecimal("10.00"), 2, + null, "Description for Item 1", false ), new CartItem( ITEM_2_ID, + null, "Item 2", new java.math.BigDecimal("20.00"), 1, + null, "Description for Item 2", true ) diff --git a/src/main/java/com/ecmsp/orderservice/api/grpc/OrderGrpcMapper.java b/src/main/java/com/ecmsp/orderservice/api/grpc/OrderGrpcMapper.java index 2a9a593..4196e53 100644 --- a/src/main/java/com/ecmsp/orderservice/api/grpc/OrderGrpcMapper.java +++ b/src/main/java/com/ecmsp/orderservice/api/grpc/OrderGrpcMapper.java @@ -1,14 +1,15 @@ package com.ecmsp.orderservice.api.grpc; -import com.ecmsp.order.v1.*; + +import com.ecmsp.order.v1.GetOrderItemsResponse; +import com.ecmsp.order.v1.GetOrderResponse; +import com.ecmsp.order.v1.GetOrderStatusResponse; +import com.ecmsp.order.v1.OrderItemDetails; +import com.ecmsp.orderservice.order.domain.Order; import com.ecmsp.orderservice.order.domain.OrderItem; import com.ecmsp.orderservice.order.domain.OrderStatus; - -import com.ecmsp.orderservice.order.domain.*; import org.springframework.stereotype.Component; -import java.math.BigDecimal; import java.util.List; -import java.util.UUID; import static com.ecmsp.orderservice.order.domain.OrderStatus.*; @@ -21,12 +22,13 @@ public GetOrderResponse toOrderResponse(Order order) { .map(this::toOrderItemDetails) .toList(); - return GetOrderResponse.newBuilder() + GetOrderResponse.Builder builder = GetOrderResponse.newBuilder() .setOrderId(order.orderId().toString()) .setOrderStatus(toOrderStatusProto(order.orderStatus())) .setDate(order.date().toString()) - .addAllItems(itemDetails) - .build(); + .addAllItems(itemDetails); + + return builder.build(); } public GetOrderResponse toGetOrderResponse(Order order) { @@ -35,34 +37,6 @@ public GetOrderResponse toGetOrderResponse(Order order) { - public CreateOrderResponse toCreateOrderResponse(Order order) { - List itemDetails = order.items().stream() - .map(this::toOrderItemDetails) - .toList(); - - return CreateOrderResponse.newBuilder() - .setOrderId(order.orderId().toString()) - .setClientId(order.clientId().toString()) - .setOrderStatus(toOrderStatusProto(order.orderStatus())) - .setDate(order.date().toString()) - .addAllItems(itemDetails) - .build(); - } - - - public UpdateOrderResponse toUpdateOrderResponse(Order order) { - List itemDetails = order.items().stream() - .map(this::toOrderItemDetails) - .toList(); - - return UpdateOrderResponse.newBuilder() - .setOrderId(order.orderId().toString()) - .setClientId(order.clientId().toString()) - .setOrderStatus(toOrderStatusProto(order.orderStatus())) - .setDate(order.date().toString()) - .addAllItems(itemDetails) - .build(); - } public com.ecmsp.order.v1.OrderStatus toOrderStatusProto(OrderStatus orderStatusDomain) { @@ -72,7 +46,12 @@ public com.ecmsp.order.v1.OrderStatus toOrderStatusProto(OrderStatus orderStatus case PAID -> com.ecmsp.order.v1.OrderStatus.ORDER_STATUS_PAID; case FAILED -> com.ecmsp.order.v1.OrderStatus.ORDER_STATUS_FAILED; case CANCELLED -> com.ecmsp.order.v1.OrderStatus.ORDER_STATUS_CANCELLED; - default -> com.ecmsp.order.v1.OrderStatus.ORDER_STATUS_UNSPECIFIED; + case SHIPPED -> com.ecmsp.order.v1.OrderStatus.ORDER_STATUS_SHIPPED; + case DELIVERED -> com.ecmsp.order.v1.OrderStatus.ORDER_STATUS_DELIVERED; + case RETURN_REQUESTED -> com.ecmsp.order.v1.OrderStatus.ORDER_STATUS_RETURN_REQUESTED; + case RETURN_PROCESSING -> com.ecmsp.order.v1.OrderStatus.ORDER_STATUS_RETURN_PROCESSING; + case RETURNED -> com.ecmsp.order.v1.OrderStatus.ORDER_STATUS_RETURNED; + case UNSPECIFIED -> com.ecmsp.order.v1.OrderStatus.ORDER_STATUS_UNSPECIFIED; }; } @@ -84,19 +63,16 @@ public OrderStatus toOrderStatusDomain(com.ecmsp.order.v1.OrderStatus orderStatu case ORDER_STATUS_PAID -> PAID; case ORDER_STATUS_FAILED -> FAILED; case ORDER_STATUS_CANCELLED -> CANCELLED; - default -> UNSPECIFIED; + case ORDER_STATUS_SHIPPED -> OrderStatus.SHIPPED; + case ORDER_STATUS_DELIVERED -> OrderStatus.DELIVERED; + case ORDER_STATUS_RETURN_REQUESTED -> OrderStatus.RETURN_REQUESTED; + case ORDER_STATUS_RETURN_PROCESSING -> OrderStatus.RETURN_PROCESSING; + case ORDER_STATUS_RETURNED -> OrderStatus.RETURNED; + case ORDER_STATUS_UNSPECIFIED, UNRECOGNIZED -> UNSPECIFIED; }; } - public OrderToCreate toOrderToCreate(CreateOrderRequest request) { - return new OrderToCreate( - new ClientId(UUID.fromString(request.getClientId())), - request.getItemsList().stream() - .map(this::toOrderItem) - .toList() - ); - } public GetOrderStatusResponse toGetOrderStatusResponse(Order order) { return GetOrderStatusResponse.newBuilder() @@ -115,22 +91,19 @@ public GetOrderItemsResponse toGetOrderItemsResponse(Order order) { .build(); } - //TODO: COMPATITLE WITH SCHEMA DEFINITIONS BUT NOT THIS SERVICE DOMAIN private OrderItemDetails toOrderItemDetails(OrderItem item) { return OrderItemDetails.newBuilder() .setItemId(item.itemId().toString()) + .setVariantId(item.variantId().toString()) .setQuantity(item.quantity()) + .setPrice(item.price().doubleValue()) + .setIsReturnable(item.isReturnable()) + .setImageUrl(item.imageUrl()) + .setVariantId(item.variantId().toString()) .build(); - } - private OrderItem toOrderItem(com.ecmsp.order.v1.OrderItem item) { - return new OrderItem( - new ItemId(UUID.fromString(item.getItemId())), - item.getQuantity(), - BigDecimal.valueOf(item.getPrice()), - //TODO: variant_id should be added - true // Default to returnable for items created via gRPC - ); + } + } \ No newline at end of file diff --git a/src/main/java/com/ecmsp/orderservice/api/grpc/OrderGrpcService.java b/src/main/java/com/ecmsp/orderservice/api/grpc/OrderGrpcService.java index 3d86c23..dcd8c67 100644 --- a/src/main/java/com/ecmsp/orderservice/api/grpc/OrderGrpcService.java +++ b/src/main/java/com/ecmsp/orderservice/api/grpc/OrderGrpcService.java @@ -4,8 +4,10 @@ import com.ecmsp.order.v1.OrderServiceGrpc; import com.ecmsp.orderservice.application.security.grpc.UserContextGrpcHolder; import com.ecmsp.orderservice.application.security.UserContextData; -import com.ecmsp.orderservice.order.domain.*; -import com.ecmsp.orderservice.order.domain.OrderStatus; +import com.ecmsp.orderservice.order.domain.ClientId; +import com.ecmsp.orderservice.order.domain.OrderFacade; +import com.ecmsp.orderservice.order.domain.OrderId; +import com.ecmsp.orderservice.order.domain.Order; import io.grpc.Status; import io.grpc.stub.StreamObserver; import net.devh.boot.grpc.server.service.GrpcService; @@ -107,83 +109,4 @@ public void listOrdersByUserId(ListOrdersByUserIdRequest request, StreamObserver } } - //? NOT SURE IF THIS IS NEEDED - @Override - public void updateOrder(UpdateOrderRequest request, StreamObserver responseObserver) { - try { - UUID orderId = UUID.fromString(request.getOrderId()); - Optional order = orderFacade.findOrderById(new OrderId(orderId)); - - if (order.isEmpty()) { - responseObserver.onError(Status.NOT_FOUND.withDescription("Order not found").asRuntimeException()); - return; - } - - // Auto-advance order status based on current status - OrderStatus currentStatus = order.get().orderStatus(); - OrderStatus nextStatus = switch (currentStatus) { - case PENDING -> OrderStatus.PROCESSING; - case PROCESSING -> OrderStatus.PAID; -// case PAID -> OrderStatus.COMPLETED; COMPLETED status does not exist in our domain - default -> currentStatus; - }; - - OrderToUpdate orderToUpdate = new OrderToUpdate(new OrderId(orderId), nextStatus); - Order updatedOrder = orderFacade.updateOrder(orderToUpdate); - UpdateOrderResponse response = orderGrpcMapper.toUpdateOrderResponse(updatedOrder); - responseObserver.onNext(response); - responseObserver.onCompleted(); - } catch (OrderException e) { - responseObserver.onError(Status.NOT_FOUND.withDescription(e.getMessage()).asRuntimeException()); - } catch (Exception e){ - responseObserver.onError(Status.INTERNAL.withDescription(e.getMessage()).asRuntimeException()); - } - - } - - - - /** - * @deprecated This method is deprecated and will be removed in a future version. - * Order creation should be done through the REST API or other designated endpoints. - */ - @Deprecated - @Override - public void createOrder(CreateOrderRequest request, StreamObserver responseObserver) { - try { - OrderToCreate orderToCreate = orderGrpcMapper.toOrderToCreate(request); - Order createdOrder = orderFacade.createOrder(orderToCreate, null); - CreateOrderResponse response = orderGrpcMapper.toCreateOrderResponse(createdOrder); - responseObserver.onNext(response); - responseObserver.onCompleted(); - } catch (Exception e) { - responseObserver.onError(Status.INTERNAL.withDescription(e.getMessage()).asRuntimeException()); - } - } - - - - - /** - * @deprecated This method is deprecated and will be removed in a future version. - * Order deletion should be done through the REST API or other designated endpoints. - */ - @Deprecated - @Override - public void deleteOrder(DeleteOrderRequest request, StreamObserver responseObserver) { - try { - UUID orderId = UUID.fromString(request.getOrderId()); - orderFacade.deleteOrder(new OrderId(orderId)); - DeleteOrderResponse response = DeleteOrderResponse.newBuilder() - .setSuccess(true) - .build(); - responseObserver.onNext(response); - responseObserver.onCompleted(); - } catch (Exception e) { - responseObserver.onError(Status.INTERNAL.withDescription(e.getMessage()).asRuntimeException()); - } - } - - - } diff --git a/src/main/java/com/ecmsp/orderservice/api/grpc/ReturnGrpcMapper.java b/src/main/java/com/ecmsp/orderservice/api/grpc/ReturnGrpcMapper.java index 3544d6b..769f934 100644 --- a/src/main/java/com/ecmsp/orderservice/api/grpc/ReturnGrpcMapper.java +++ b/src/main/java/com/ecmsp/orderservice/api/grpc/ReturnGrpcMapper.java @@ -5,6 +5,7 @@ import com.ecmsp.orderservice.order.domain.returns.ItemToReturnDetails; import com.ecmsp.orderservice.order.domain.returns.ReturnOrder; import com.ecmsp.orderservice.order.domain.returns.ReturnToCreate; +import com.ecmsp.orderservice.order.domain.returns.ReturnStatus; import org.springframework.stereotype.Component; import java.util.List; @@ -67,7 +68,7 @@ private ItemReturnDetails toItemReturnDetails(ItemToReturnDetails dto) { } - private com.ecmsp.order.v1.returns.v1.ReturnStatus toReturnStatusProto(com.ecmsp.orderservice.order.domain.returns.ReturnStatus status) { + private com.ecmsp.order.v1.returns.v1.ReturnStatus toReturnStatusProto(ReturnStatus status) { return switch (status) { case REQUESTED -> com.ecmsp.order.v1.returns.v1.ReturnStatus.RETURN_STATUS_REQUESTED; case PROCESSING -> com.ecmsp.order.v1.returns.v1.ReturnStatus.RETURN_STATUS_PROCESSING; diff --git a/src/main/java/com/ecmsp/orderservice/api/kafka/CartCreatedEvent.java b/src/main/java/com/ecmsp/orderservice/api/kafka/CartCreatedEvent.java index 43b5d98..7354f6f 100644 --- a/src/main/java/com/ecmsp/orderservice/api/kafka/CartCreatedEvent.java +++ b/src/main/java/com/ecmsp/orderservice/api/kafka/CartCreatedEvent.java @@ -1,9 +1,6 @@ package com.ecmsp.orderservice.api.kafka; -import com.ecmsp.orderservice.order.domain.ClientId; -import com.ecmsp.orderservice.order.domain.ItemId; -import com.ecmsp.orderservice.order.domain.OrderItem; -import com.ecmsp.orderservice.order.domain.OrderToCreate; +import com.ecmsp.orderservice.order.domain.*; import java.math.BigDecimal; import java.util.List; @@ -17,9 +14,11 @@ public record CartCreatedEvent( public record CartItem( String itemId, + String variantId, String name, BigDecimal price, int quantity, + String imageUrl, String description, boolean isReturnable ) { @@ -27,12 +26,16 @@ public record CartItem( public static OrderToCreate toOrder(CartCreatedEvent cartEvent) { return new OrderToCreate( + /* reservationId */ null, /* clientId */ new ClientId(UUID.fromString(cartEvent.clientId())), /* items */ cartEvent.items().stream() .map(cartItem -> new OrderItem( new ItemId(UUID.fromString(cartItem.itemId())), + new VariantId(UUID.fromString(cartItem.variantId())), cartItem.quantity(), cartItem.price(), + cartItem.imageUrl(), + cartItem.description(), cartItem.isReturnable() )).toList() ); diff --git a/src/main/java/com/ecmsp/orderservice/api/rest/order/OrderControllerMapper.java b/src/main/java/com/ecmsp/orderservice/api/rest/order/OrderControllerMapper.java index 2adb799..4a1743b 100644 --- a/src/main/java/com/ecmsp/orderservice/api/rest/order/OrderControllerMapper.java +++ b/src/main/java/com/ecmsp/orderservice/api/rest/order/OrderControllerMapper.java @@ -32,7 +32,7 @@ OrderReturnabilityResponse toOrderReturnabilityResponse(Order order) { .map(item -> new OrderReturnabilityResponse.ReturnableItemDto( /* itemId = */ item.itemId().value(), /* quantity = */ item.quantity(), - /* priceAtTimeOfOrder = */ item.priceAtTimeOfOrder() + /* price = */ item.price() )) .toList() ); diff --git a/src/main/java/com/ecmsp/orderservice/api/rest/order/OrdersController.java b/src/main/java/com/ecmsp/orderservice/api/rest/order/OrdersController.java index 6cebde7..2b4f4ea 100644 --- a/src/main/java/com/ecmsp/orderservice/api/rest/order/OrdersController.java +++ b/src/main/java/com/ecmsp/orderservice/api/rest/order/OrdersController.java @@ -64,6 +64,7 @@ public ResponseEntity getOrderById(@PathVariable UUID orde public ResponseEntity createOrder(@RequestBody CreateOrderRequest request, @RequestHeader(value = "X-Correlation-Id", required = false) String correlationId) { Context context = new Context(new CorrelationId(UUID.fromString(correlationId))); OrderToCreate orderToCreate = new OrderToCreate( + null, /* clientId = */ new ClientId(request.clientId()), /* items = */ request.items().stream() .map(CreateOrderRequest.Item::toOrderItem) diff --git a/src/main/java/com/ecmsp/orderservice/api/rest/order/dto/CreateOrderRequest.java b/src/main/java/com/ecmsp/orderservice/api/rest/order/dto/CreateOrderRequest.java index fdaff15..22f2b32 100644 --- a/src/main/java/com/ecmsp/orderservice/api/rest/order/dto/CreateOrderRequest.java +++ b/src/main/java/com/ecmsp/orderservice/api/rest/order/dto/CreateOrderRequest.java @@ -2,12 +2,14 @@ import com.ecmsp.orderservice.order.domain.ItemId; import com.ecmsp.orderservice.order.domain.OrderItem; +import com.ecmsp.orderservice.order.domain.VariantId; import java.math.BigDecimal; import java.util.List; import java.util.UUID; public record CreateOrderRequest( + UUID variantId, UUID clientId, List items ) { @@ -15,12 +17,23 @@ public record CreateOrderRequest( public record Item( UUID itemId, + UUID variantId, int quantity, double price, + String imageUrl, + String description, boolean returnable ) { public OrderItem toOrderItem() { - return new OrderItem(new ItemId(itemId), quantity, BigDecimal.valueOf(price), returnable); + return new OrderItem( + new ItemId(itemId), + new VariantId(variantId), + quantity, + BigDecimal.valueOf(price), + imageUrl, + description, + returnable + ); } } diff --git a/src/main/java/com/ecmsp/orderservice/order/adapter/repository/db/OrderEntity.java b/src/main/java/com/ecmsp/orderservice/order/adapter/repository/db/OrderEntity.java index 25d1261..7a0f54d 100644 --- a/src/main/java/com/ecmsp/orderservice/order/adapter/repository/db/OrderEntity.java +++ b/src/main/java/com/ecmsp/orderservice/order/adapter/repository/db/OrderEntity.java @@ -30,6 +30,9 @@ class OrderEntity { @Column(name = "client_id", nullable = false) private UUID clientId; + @Column(name = "reservation_id") + private UUID reservationId; + @OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true) private List items; @@ -74,6 +77,14 @@ public void setItems(List items) { this.items = items; } + public UUID getReservationId() { + return reservationId; + } + + public void setReservationId(UUID reservationId) { + this.reservationId = reservationId; + } + @Override public String toString() { return "Order{" + @@ -81,6 +92,7 @@ public String toString() { ", orderStatus=" + orderStatus + ", date=" + date + ", clientId=" + clientId + + ", reservationId=" + reservationId + '}'; } } diff --git a/src/main/java/com/ecmsp/orderservice/order/adapter/repository/db/OrderEntityMapper.java b/src/main/java/com/ecmsp/orderservice/order/adapter/repository/db/OrderEntityMapper.java index 4328377..b61f817 100644 --- a/src/main/java/com/ecmsp/orderservice/order/adapter/repository/db/OrderEntityMapper.java +++ b/src/main/java/com/ecmsp/orderservice/order/adapter/repository/db/OrderEntityMapper.java @@ -9,11 +9,20 @@ class OrderEntityMapper { public Order toOrder(OrderEntity orderEntity) { return new Order( /* orderId = */ new OrderId(orderEntity.getOrderId()), + /* reservationId = */ orderEntity.getReservationId() != null ? new ReservationId(orderEntity.getReservationId()) : null, /* clientId = */ new ClientId(orderEntity.getClientId()), /* orderStatus = */ orderEntity.getOrderStatus(), /* date = */ orderEntity.getDate(), /* items = */ orderEntity.getItems().stream() - .map(item -> new OrderItem( new ItemId(item.getItemId()), item.getQuantity(), item.getPrice(), item.getIsReturnable())) + .map(item -> new OrderItem( + new ItemId(item.getItemId()), + item.getVariantId() != null ? new VariantId(item.getVariantId()) : null, + item.getQuantity(), + item.getPrice(), + item.getImageUrl(), + item.getDescription(), + item.getIsReturnable() + )) .toList() ); } @@ -21,6 +30,7 @@ public Order toOrder(OrderEntity orderEntity) { public OrderEntity toOrderEntity(Order order) { OrderEntity orderEntity = OrderEntity.builder() .orderId(order.orderId().value()) + .reservationId(order.reservationId() != null ? order.reservationId().value() : null) .clientId(order.clientId().value()) .orderStatus(order.orderStatus()) .date(order.date()) @@ -29,8 +39,12 @@ public OrderEntity toOrderEntity(Order order) { List itemEntities = order.items().stream() .map(item -> OrderItemEntity.builder() .itemId(item.itemId().value()) + .variantId(item.variantId() != null ? item.variantId().value() : null) .quantity(item.quantity()) - .price(item.priceAtTimeOfOrder()) + .price(item.price()) + .imageUrl(item.imageUrl()) + .description(item.description()) + .isReturnable(item.isReturnable()) .build()) .toList(); diff --git a/src/main/java/com/ecmsp/orderservice/order/adapter/repository/db/OrderItemEntity.java b/src/main/java/com/ecmsp/orderservice/order/adapter/repository/db/OrderItemEntity.java index e6beaf2..9119173 100644 --- a/src/main/java/com/ecmsp/orderservice/order/adapter/repository/db/OrderItemEntity.java +++ b/src/main/java/com/ecmsp/orderservice/order/adapter/repository/db/OrderItemEntity.java @@ -22,6 +22,9 @@ class OrderItemEntity { @Column(name = "item_id") private UUID itemId; + @Column(name = "variant_id") + private UUID variantId; + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "order_id", nullable = false) private OrderEntity order; @@ -32,6 +35,11 @@ class OrderItemEntity { @Column(name = "price", nullable = false) private BigDecimal price; + @Column(name = "image_url") + private String imageUrl; + + @Column(name = "description") + private String description; @Column(name = "is_returnable", nullable = false) private Boolean isReturnable; @@ -78,4 +86,28 @@ public void setIsReturnable(Boolean isReturnable) { this.isReturnable = isReturnable; } + public UUID getVariantId() { + return variantId; + } + + public void setVariantId(UUID variantId) { + this.variantId = variantId; + } + + public String getImageUrl() { + return imageUrl; + } + + public void setImageUrl(String imageUrl) { + this.imageUrl = imageUrl; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + } diff --git a/src/main/java/com/ecmsp/orderservice/order/domain/DefaultOrderFacade.java b/src/main/java/com/ecmsp/orderservice/order/domain/DefaultOrderFacade.java index a99f481..d25cdf2 100644 --- a/src/main/java/com/ecmsp/orderservice/order/domain/DefaultOrderFacade.java +++ b/src/main/java/com/ecmsp/orderservice/order/domain/DefaultOrderFacade.java @@ -43,6 +43,7 @@ public Order createOrder(OrderToCreate orderToCreate, Context context) { Order order = new Order( /* orderId */ orderIdGenerator.generate(context.correlationId()), + orderToCreate.reservationId(), /* clientId */ orderToCreate.clientId(), /* orderStatus */ OrderStatus.PENDING, // Assuming default status is PENDING /* date */ LocalDateTime.now(clock), @@ -69,6 +70,7 @@ public Order updateOrder(OrderToUpdate orderToUpdate) { Order updatedOrder = new Order( currentOrder.orderId(), + currentOrder.reservationId(), currentOrder.clientId(), orderToUpdate.newStatus(), currentOrder.date(), diff --git a/src/main/java/com/ecmsp/orderservice/order/domain/Order.java b/src/main/java/com/ecmsp/orderservice/order/domain/Order.java index c8b6c67..fb3f08d 100644 --- a/src/main/java/com/ecmsp/orderservice/order/domain/Order.java +++ b/src/main/java/com/ecmsp/orderservice/order/domain/Order.java @@ -7,14 +7,19 @@ public record Order( OrderId orderId, + ReservationId reservationId, ClientId clientId, OrderStatus orderStatus, LocalDateTime date, List items ) { + + private static final int RETURNABLE_PERIOD_DAYS = 14; + + public BigDecimal totalPrice() { return items.stream() - .map(OrderItem::priceAtTimeOfOrder) + .map(OrderItem::price) .reduce(BigDecimal.ZERO, BigDecimal::add); } @@ -23,7 +28,7 @@ public boolean isReturnable() { } public boolean isWithinReturnPeriod() { - return ChronoUnit.DAYS.between(date, LocalDateTime.now()) <= 14; + return ChronoUnit.DAYS.between(date, LocalDateTime.now()) <= RETURNABLE_PERIOD_DAYS; } public boolean hasReturnableItems() { diff --git a/src/main/java/com/ecmsp/orderservice/order/domain/OrderItem.java b/src/main/java/com/ecmsp/orderservice/order/domain/OrderItem.java index 86c3794..b6458d2 100644 --- a/src/main/java/com/ecmsp/orderservice/order/domain/OrderItem.java +++ b/src/main/java/com/ecmsp/orderservice/order/domain/OrderItem.java @@ -4,9 +4,11 @@ public record OrderItem( ItemId itemId, - //TODO: we need to add variantId and make a call to product service: getVariantDetails(variantId) to get: price, image_url, description to present it to user or just keep all these fields in db and don't make another call after order is finalized -> I think 2nd option is better for performance + VariantId variantId, int quantity, - BigDecimal priceAtTimeOfOrder, + BigDecimal price, + String imageUrl, + String description, boolean isReturnable ) { } diff --git a/src/main/java/com/ecmsp/orderservice/order/domain/OrderStatus.java b/src/main/java/com/ecmsp/orderservice/order/domain/OrderStatus.java index 9575802..59b34ab 100644 --- a/src/main/java/com/ecmsp/orderservice/order/domain/OrderStatus.java +++ b/src/main/java/com/ecmsp/orderservice/order/domain/OrderStatus.java @@ -1,10 +1,19 @@ package com.ecmsp.orderservice.order.domain; public enum OrderStatus { + UNSPECIFIED, PENDING, PROCESSING, PAID, FAILED, CANCELLED, - UNSPECIFIED // Added to match the protobuf enum + + // Post-fulfillment states + SHIPPED, + DELIVERED, + + // Return states + RETURN_REQUESTED, + RETURN_PROCESSING, + RETURNED } \ No newline at end of file diff --git a/src/main/java/com/ecmsp/orderservice/order/domain/OrderToCreate.java b/src/main/java/com/ecmsp/orderservice/order/domain/OrderToCreate.java index 787cee0..57fcb09 100644 --- a/src/main/java/com/ecmsp/orderservice/order/domain/OrderToCreate.java +++ b/src/main/java/com/ecmsp/orderservice/order/domain/OrderToCreate.java @@ -2,7 +2,9 @@ import java.util.List; + public record OrderToCreate( + ReservationId reservationId, ClientId clientId, List items ) { diff --git a/src/main/java/com/ecmsp/orderservice/order/domain/ReservationId.java b/src/main/java/com/ecmsp/orderservice/order/domain/ReservationId.java new file mode 100644 index 0000000..5880b51 --- /dev/null +++ b/src/main/java/com/ecmsp/orderservice/order/domain/ReservationId.java @@ -0,0 +1,12 @@ +package com.ecmsp.orderservice.order.domain; + +import java.util.UUID; + +public record ReservationId(UUID value) { + + @Override + public String toString(){ + return value.toString(); + } + +} diff --git a/src/main/resources/db/migration/V1__Create_initial_schema.sql b/src/main/resources/db/migration/V1__Create_initial_schema.sql index 608f6aa..0face51 100644 --- a/src/main/resources/db/migration/V1__Create_initial_schema.sql +++ b/src/main/resources/db/migration/V1__Create_initial_schema.sql @@ -1,21 +1,24 @@ CREATE TABLE orders ( - order_id UUID PRIMARY KEY, - order_status VARCHAR(30) NOT NULL, - date TIMESTAMP NOT NULL, - client_id UUID NOT NULL + order_id UUID PRIMARY KEY, + reservation_id UUID, + order_status VARCHAR(30) NOT NULL, + date TIMESTAMP NOT NULL, + client_id UUID NOT NULL ); CREATE TABLE order_item ( order_item_id UUID PRIMARY KEY, - item_id UUID NOT NULL, + item_id UUID NOT NULL, + variant_id UUID, order_id UUID NOT NULL, item_name VARCHAR NOT NULL, quantity INTEGER NOT NULL, price DECIMAL NOT NULL, + image_url VARCHAR, description VARCHAR, is_returnable BOOLEAN NOT NULL, CONSTRAINT fk_order_item_order diff --git a/src/main/resources/db/migration/V2__Insert_example_data.sql b/src/main/resources/db/migration/V2__Insert_example_data.sql index ab0f49f..1b07ea4 100644 --- a/src/main/resources/db/migration/V2__Insert_example_data.sql +++ b/src/main/resources/db/migration/V2__Insert_example_data.sql @@ -2,52 +2,52 @@ -- Flyway migration to insert example data into orders and order_item tables -- Insert example orders -INSERT INTO orders (order_id, order_status, date, client_id) VALUES - ('550e8400-e29b-41d4-a716-446655440001', 'PENDING', '2024-01-15 10:30:00', '123e4567-e89b-12d3-a456-426614174001'), - ('550e8400-e29b-41d4-a716-446655440002', 'PAID', '2024-01-16 14:22:15', '123e4567-e89b-12d3-a456-426614174002'), - ('550e8400-e29b-41d4-a716-446655440003', 'PROCESSING', '2024-01-17 09:45:30', '123e4567-e89b-12d3-a456-426614174001'), - ('550e8400-e29b-41d4-a716-446655440004', 'CANCELLED', '2024-01-18 16:10:45', '123e4567-e89b-12d3-a456-426614174003'), - ('550e8400-e29b-41d4-a716-446655440005', 'PROCESSING', '2024-01-19 11:20:00', '123e4567-e89b-12d3-a456-426614174002'), - ('550e8400-e29b-41d4-a716-446655440006', 'PAID', '2024-01-20 13:55:12', '123e4567-e89b-12d3-a456-426614174004'), - ('550e8400-e29b-41d4-a716-446655440007', 'PENDING', '2024-01-21 08:15:30', '123e4567-e89b-12d3-a456-426614174001'), - ('550e8400-e29b-41d4-a716-446655440008', 'FAILED', '2024-01-22 15:40:25', '123e4567-e89b-12d3-a456-426614174005'), +INSERT INTO orders (order_id, reservation_id, order_status, date, client_id) VALUES + ('550e8400-e29b-41d4-a716-446655440001', 'c1d2e3f4-a5b6-7890-cdef-012345678901', 'PENDING', '2024-01-15 10:30:00', '123e4567-e89b-12d3-a456-426614174001'), + ('550e8400-e29b-41d4-a716-446655440002', 'c1d2e3f4-a5b6-7890-cdef-012345678902', 'PAID', '2024-01-16 14:22:15', '123e4567-e89b-12d3-a456-426614174002'), + ('550e8400-e29b-41d4-a716-446655440003', 'c1d2e3f4-a5b6-7890-cdef-012345678903', 'PROCESSING', '2024-01-17 09:45:30', '123e4567-e89b-12d3-a456-426614174001'), + ('550e8400-e29b-41d4-a716-446655440004', 'c1d2e3f4-a5b6-7890-cdef-012345678904', 'CANCELLED', '2024-01-18 16:10:45', '123e4567-e89b-12d3-a456-426614174003'), + ('550e8400-e29b-41d4-a716-446655440005', 'c1d2e3f4-a5b6-7890-cdef-012345678905', 'PROCESSING', '2024-01-19 11:20:00', '123e4567-e89b-12d3-a456-426614174002'), + ('550e8400-e29b-41d4-a716-446655440006', 'c1d2e3f4-a5b6-7890-cdef-012345678906', 'PAID', '2024-01-20 13:55:12', '123e4567-e89b-12d3-a456-426614174004'), + ('550e8400-e29b-41d4-a716-446655440007', 'c1d2e3f4-a5b6-7890-cdef-012345678907', 'PENDING', '2024-01-21 08:15:30', '123e4567-e89b-12d3-a456-426614174001'), + ('550e8400-e29b-41d4-a716-446655440008', 'c1d2e3f4-a5b6-7890-cdef-012345678908', 'FAILED', '2024-01-22 15:40:25', '123e4567-e89b-12d3-a456-426614174005'), --second order for the first client for testing - ('550e8400-e29b-41d4-a716-446655440009', 'PENDING', '2024-01-15 11:30:00', '123e4567-e89b-12d3-a456-426614174001'); + ('550e8400-e29b-41d4-a716-446655440009', 'c1d2e3f4-a5b6-7890-cdef-012345678909', 'PENDING', '2024-01-15 11:30:00', '123e4567-e89b-12d3-a456-426614174001'); -- Insert example order items -INSERT INTO order_item (order_item_id, item_id, order_id, item_name, quantity, price, description, is_returnable) VALUES +INSERT INTO order_item (order_item_id, item_id, variant_id, order_id, item_name, quantity, price, image_url, description, is_returnable) VALUES -- Order 1 items - ('f1b2c3d4-e5f6-7890-abcd-ef1234567890', 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', '550e8400-e29b-41d4-a716-446655440001', 'Wireless Headphones', 2, 129.99, 'Premium noise-cancelling wireless headphones', true), - ('f1b2c3d4-e5f6-7890-abcd-ef1234567891', 'a1b2c3d4-e5f6-7890-abcd-ef1234567891', '550e8400-e29b-41d4-a716-446655440001', 'Phone Case', 1, 24.99, 'Protective silicone phone case', true), + ('f1b2c3d4-e5f6-7890-abcd-ef1234567890', 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', 'b1b2c3d4-e5f6-7890-abcd-ef1234567890', '550e8400-e29b-41d4-a716-446655440001', 'Wireless Headphones', 2, 129.99, 'https://example.com/images/headphones.jpg', 'Premium noise-cancelling wireless headphones', true), + ('f1b2c3d4-e5f6-7890-abcd-ef1234567891', 'a1b2c3d4-e5f6-7890-abcd-ef1234567891', 'b1b2c3d4-e5f6-7890-abcd-ef1234567891', '550e8400-e29b-41d4-a716-446655440001', 'Phone Case', 1, 24.99, 'https://example.com/images/phone-case.jpg', 'Protective silicone phone case', true), -- Order 2 items - ('f1b2c3d4-e5f6-7890-abcd-ef1234567892', 'a1b2c3d4-e5f6-7890-abcd-ef1234567892', '550e8400-e29b-41d4-a716-446655440002', 'Laptop Stand', 1, 89.99, 'Adjustable aluminum laptop stand', true), - ('f1b2c3d4-e5f6-7890-abcd-ef1234567893', 'a1b2c3d4-e5f6-7890-abcd-ef1234567893', '550e8400-e29b-41d4-a716-446655440002', 'USB-C Cable', 3, 19.99, '6ft USB-C charging cable', false), - ('f1b2c3d4-e5f6-7890-abcd-ef1234567894', 'a1b2c3d4-e5f6-7890-abcd-ef1234567894', '550e8400-e29b-41d4-a716-446655440002', 'Wireless Mouse', 1, 45.99, 'Ergonomic wireless optical mouse', true), + ('f1b2c3d4-e5f6-7890-abcd-ef1234567892', 'a1b2c3d4-e5f6-7890-abcd-ef1234567892', 'b1b2c3d4-e5f6-7890-abcd-ef1234567892', '550e8400-e29b-41d4-a716-446655440002', 'Laptop Stand', 1, 89.99, 'https://example.com/images/laptop-stand.jpg', 'Adjustable aluminum laptop stand', true), + ('f1b2c3d4-e5f6-7890-abcd-ef1234567893', 'a1b2c3d4-e5f6-7890-abcd-ef1234567893', 'b1b2c3d4-e5f6-7890-abcd-ef1234567893', '550e8400-e29b-41d4-a716-446655440002', 'USB-C Cable', 3, 19.99, 'https://example.com/images/usb-cable.jpg', '6ft USB-C charging cable', false), + ('f1b2c3d4-e5f6-7890-abcd-ef1234567894', 'a1b2c3d4-e5f6-7890-abcd-ef1234567894', 'b1b2c3d4-e5f6-7890-abcd-ef1234567894', '550e8400-e29b-41d4-a716-446655440002', 'Wireless Mouse', 1, 45.99, 'https://example.com/images/mouse.jpg', 'Ergonomic wireless optical mouse', true), -- Order 3 items - ('f1b2c3d4-e5f6-7890-abcd-ef1234567895', 'a1b2c3d4-e5f6-7890-abcd-ef1234567895', '550e8400-e29b-41d4-a716-446655440003', 'Monitor', 1, 299.99, '27-inch 4K LED monitor', true), - ('f1b2c3d4-e5f6-7890-abcd-ef1234567896', 'a1b2c3d4-e5f6-7890-abcd-ef1234567896', '550e8400-e29b-41d4-a716-446655440003', 'Keyboard', 1, 79.99, 'Mechanical gaming keyboard', true), + ('f1b2c3d4-e5f6-7890-abcd-ef1234567895', 'a1b2c3d4-e5f6-7890-abcd-ef1234567895', 'b1b2c3d4-e5f6-7890-abcd-ef1234567895', '550e8400-e29b-41d4-a716-446655440003', 'Monitor', 1, 299.99, 'https://example.com/images/monitor.jpg', '27-inch 4K LED monitor', true), + ('f1b2c3d4-e5f6-7890-abcd-ef1234567896', 'a1b2c3d4-e5f6-7890-abcd-ef1234567896', 'b1b2c3d4-e5f6-7890-abcd-ef1234567896', '550e8400-e29b-41d4-a716-446655440003', 'Keyboard', 1, 79.99, 'https://example.com/images/keyboard.jpg', 'Mechanical gaming keyboard', true), -- Order 4 items (cancelled order) - ('f1b2c3d4-e5f6-7890-abcd-ef1234567897', 'a1b2c3d4-e5f6-7890-abcd-ef1234567897', '550e8400-e29b-41d4-a716-446655440004', 'Webcam', 1, 149.99, 'HD webcam with auto-focus', true), + ('f1b2c3d4-e5f6-7890-abcd-ef1234567897', 'a1b2c3d4-e5f6-7890-abcd-ef1234567897', 'b1b2c3d4-e5f6-7890-abcd-ef1234567897', '550e8400-e29b-41d4-a716-446655440004', 'Webcam', 1, 149.99, 'https://example.com/images/webcam.jpg', 'HD webcam with auto-focus', true), -- Order 5 items - ('f1b2c3d4-e5f6-7890-abcd-ef1234567898', 'a1b2c3d4-e5f6-7890-abcd-ef1234567898', '550e8400-e29b-41d4-a716-446655440005', 'Tablet', 1, 399.99, '10-inch Android tablet', true), - ('f1b2c3d4-e5f6-7890-abcd-ef1234567899', 'a1b2c3d4-e5f6-7890-abcd-ef1234567899', '550e8400-e29b-41d4-a716-446655440005', 'Tablet Case', 1, 34.99, 'Leather tablet case with stand', true), - ('f1b2c3d4-e5f6-7890-abcd-ef123456789a', 'a1b2c3d4-e5f6-7890-abcd-ef123456789a', '550e8400-e29b-41d4-a716-446655440005', 'Screen Protector', 2, 12.99, 'Tempered glass screen protector', false), + ('f1b2c3d4-e5f6-7890-abcd-ef1234567898', 'a1b2c3d4-e5f6-7890-abcd-ef1234567898', 'b1b2c3d4-e5f6-7890-abcd-ef1234567898', '550e8400-e29b-41d4-a716-446655440005', 'Tablet', 1, 399.99, 'https://example.com/images/tablet.jpg', '10-inch Android tablet', true), + ('f1b2c3d4-e5f6-7890-abcd-ef1234567899', 'a1b2c3d4-e5f6-7890-abcd-ef1234567899', 'b1b2c3d4-e5f6-7890-abcd-ef1234567899', '550e8400-e29b-41d4-a716-446655440005', 'Tablet Case', 1, 34.99, 'https://example.com/images/tablet-case.jpg', 'Leather tablet case with stand', true), + ('f1b2c3d4-e5f6-7890-abcd-ef123456789a', 'a1b2c3d4-e5f6-7890-abcd-ef123456789a', 'b1b2c3d4-e5f6-7890-abcd-ef123456789a', '550e8400-e29b-41d4-a716-446655440005', 'Screen Protector', 2, 12.99, 'https://example.com/images/screen-protector.jpg', 'Tempered glass screen protector', false), -- Order 6 items - ('f1b2c3d4-e5f6-7890-abcd-ef123456789b', 'a1b2c3d4-e5f6-7890-abcd-ef123456789b', '550e8400-e29b-41d4-a716-446655440006', 'Bluetooth Speaker', 1, 79.99, 'Portable waterproof Bluetooth speaker', true), - ('f1b2c3d4-e5f6-7890-abcd-ef123456789c', 'a1b2c3d4-e5f6-7890-abcd-ef123456789c', '550e8400-e29b-41d4-a716-446655440006', 'Power Bank', 1, 49.99, '20000mAh portable power bank', true), + ('f1b2c3d4-e5f6-7890-abcd-ef123456789b', 'a1b2c3d4-e5f6-7890-abcd-ef123456789b', 'b1b2c3d4-e5f6-7890-abcd-ef123456789b', '550e8400-e29b-41d4-a716-446655440006', 'Bluetooth Speaker', 1, 79.99, 'https://example.com/images/speaker.jpg', 'Portable waterproof Bluetooth speaker', true), + ('f1b2c3d4-e5f6-7890-abcd-ef123456789c', 'a1b2c3d4-e5f6-7890-abcd-ef123456789c', 'b1b2c3d4-e5f6-7890-abcd-ef123456789c', '550e8400-e29b-41d4-a716-446655440006', 'Power Bank', 1, 49.99, 'https://example.com/images/power-bank.jpg', '20000mAh portable power bank', true), -- Order 7 items - ('f1b2c3d4-e5f6-7890-abcd-ef123456789d', 'a1b2c3d4-e5f6-7890-abcd-ef123456789d', '550e8400-e29b-41d4-a716-446655440007', 'Smart Watch', 1, 249.99, 'Fitness tracking smart watch', true), - ('f1b2c3d4-e5f6-7890-abcd-ef123456789e', 'a1b2c3d4-e5f6-7890-abcd-ef123456789e', '550e8400-e29b-41d4-a716-446655440007', 'Watch Band', 2, 29.99, 'Silicone sport watch band', false), + ('f1b2c3d4-e5f6-7890-abcd-ef123456789d', 'a1b2c3d4-e5f6-7890-abcd-ef123456789d', 'b1b2c3d4-e5f6-7890-abcd-ef123456789d', '550e8400-e29b-41d4-a716-446655440007', 'Smart Watch', 1, 249.99, 'https://example.com/images/smartwatch.jpg', 'Fitness tracking smart watch', true), + ('f1b2c3d4-e5f6-7890-abcd-ef123456789e', 'a1b2c3d4-e5f6-7890-abcd-ef123456789e', 'b1b2c3d4-e5f6-7890-abcd-ef123456789e', '550e8400-e29b-41d4-a716-446655440007', 'Watch Band', 2, 29.99, 'https://example.com/images/watch-band.jpg', 'Silicone sport watch band', false), -- Order 8 items - ('f1b2c3d4-e5f6-7890-abcd-ef123456789f', 'a1b2c3d4-e5f6-7890-abcd-ef123456789f', '550e8400-e29b-41d4-a716-446655440008', 'Gaming Headset', 1, 159.99, 'Professional gaming headset with microphone', true), - ('f1b2c3d4-e5f6-7890-abcd-ef1234567800', 'a1b2c3d4-e5f6-7890-abcd-ef1234567800', '550e8400-e29b-41d4-a716-446655440008', 'Mouse Pad', 1, 19.99, 'Large gaming mouse pad', true), + ('f1b2c3d4-e5f6-7890-abcd-ef123456789f', 'a1b2c3d4-e5f6-7890-abcd-ef123456789f', 'b1b2c3d4-e5f6-7890-abcd-ef123456789f', '550e8400-e29b-41d4-a716-446655440008', 'Gaming Headset', 1, 159.99, 'https://example.com/images/gaming-headset.jpg', 'Professional gaming headset with microphone', true), + ('f1b2c3d4-e5f6-7890-abcd-ef1234567800', 'a1b2c3d4-e5f6-7890-abcd-ef1234567800', 'b1b2c3d4-e5f6-7890-abcd-ef1234567800', '550e8400-e29b-41d4-a716-446655440008', 'Mouse Pad', 1, 19.99, 'https://example.com/images/mouse-pad.jpg', 'Large gaming mouse pad', true), -- Order 9 items (second order for the first client) - testing - ('f1b2c3d4-e5f6-7890-abcd-ef1234567801', 'a1b2c3d4-e5f6-7890-abcd-ef1234567801', '550e8400-e29b-41d4-a716-446655440009', 'Mouse Pad', 1, 19.99, 'Large gaming mouse pad', true); + ('f1b2c3d4-e5f6-7890-abcd-ef1234567801', 'a1b2c3d4-e5f6-7890-abcd-ef1234567801', 'b1b2c3d4-e5f6-7890-abcd-ef1234567801', '550e8400-e29b-41d4-a716-446655440009', 'Mouse Pad', 1, 19.99, 'https://example.com/images/mouse-pad.jpg', 'Large gaming mouse pad', true); diff --git a/src/test/java/com/ecmsp/orderservice/grpc/OrderGrpcServiceTest.java b/src/test/java/com/ecmsp/orderservice/grpc/OrderGrpcServiceTest.java index 7363acc..50d4b9a 100644 --- a/src/test/java/com/ecmsp/orderservice/grpc/OrderGrpcServiceTest.java +++ b/src/test/java/com/ecmsp/orderservice/grpc/OrderGrpcServiceTest.java @@ -3,8 +3,10 @@ import com.ecmsp.order.v1.*; import com.ecmsp.orderservice.api.grpc.OrderGrpcMapper; import com.ecmsp.orderservice.api.grpc.OrderGrpcService; -import com.ecmsp.orderservice.order.domain.*; -import com.ecmsp.orderservice.order.domain.OrderStatus; +import com.ecmsp.orderservice.order.domain.ClientId; +import com.ecmsp.orderservice.order.domain.OrderFacade; +import com.ecmsp.orderservice.order.domain.OrderId; +import com.ecmsp.orderservice.order.domain.Order; import io.grpc.Status; import io.grpc.StatusRuntimeException; import io.grpc.stub.StreamObserver; @@ -45,7 +47,7 @@ class OrderGrpcServiceTest { @BeforeEach void setUp() { - testOrder = new Order(ORDER_ID, CLIENT_ID, null, null, null); + testOrder = new Order(ORDER_ID, null, CLIENT_ID, null, null, null); } @Nested @@ -134,194 +136,6 @@ void should_return_internal_error_when_exception_occurs() { } } - @Nested - @DisplayName("Create Order Tests") - class CreateOrderTests { - - private final StreamObserver responseObserver = mock(StreamObserver.class); - - @Test - @DisplayName("Should create order successfully") - void should_create_order_successfully() { - // given - CreateOrderRequest request = CreateOrderRequest.newBuilder() - .setClientId(CLIENT_UUID.toString()) - .build(); - OrderToCreate orderToCreate = new OrderToCreate(CLIENT_ID, List.of()); - CreateOrderResponse expectedResponse = CreateOrderResponse.newBuilder() - .setOrderId(ORDER_UUID.toString()) - .build(); - - when(orderGrpcMapper.toOrderToCreate(request)).thenReturn(orderToCreate); - when(orderFacade.createOrder(orderToCreate, null)).thenReturn(testOrder); - when(orderGrpcMapper.toCreateOrderResponse(testOrder)).thenReturn(expectedResponse); - - // when - orderGrpcService.createOrder(request, responseObserver); - - // then - verify(orderGrpcMapper).toOrderToCreate(request); - verify(orderFacade).createOrder(orderToCreate, null); - verify(orderGrpcMapper).toCreateOrderResponse(testOrder); - verify(responseObserver).onNext(expectedResponse); - verify(responseObserver).onCompleted(); - verify(responseObserver, never()).onError(any()); - } - - @Test - @DisplayName("Should handle exception during order creation") - void should_handle_exception_during_order_creation() { - // given - CreateOrderRequest request = CreateOrderRequest.newBuilder() - .setClientId(CLIENT_UUID.toString()) - .build(); - OrderToCreate orderToCreate = new OrderToCreate(CLIENT_ID, List.of()); - - when(orderGrpcMapper.toOrderToCreate(request)).thenReturn(orderToCreate); - when(orderFacade.createOrder(orderToCreate, null)).thenThrow(new RuntimeException("Creation failed")); - - // when - orderGrpcService.createOrder(request, responseObserver); - - // then - verify(responseObserver).onError(any(StatusRuntimeException.class)); - verify(responseObserver, never()).onNext(any()); - verify(responseObserver, never()).onCompleted(); - } - } - - @Nested - @DisplayName("Update Order Tests") - class UpdateOrderTests { - - - private final StreamObserver responseObserver = mock(StreamObserver.class); - - @Test - @DisplayName("Should auto-advance order from PENDING to PROCESSING") - void should_auto_advance_order_from_pending_to_processing() { - // given - UpdateOrderRequest request = UpdateOrderRequest.newBuilder() - .setOrderId(ORDER_UUID.toString()) - .build(); - Order pendingOrder = new Order(ORDER_ID, CLIENT_ID, OrderStatus.PENDING, null, null); - Order processingOrder = new Order(ORDER_ID, CLIENT_ID, OrderStatus.PROCESSING, null, null); - UpdateOrderResponse expectedResponse = UpdateOrderResponse.newBuilder() - .setOrderId(ORDER_UUID.toString()) - .build(); - - when(orderFacade.findOrderById(ORDER_ID)).thenReturn(Optional.of(pendingOrder)); - when(orderFacade.updateOrder(argThat(orderToUpdate -> - orderToUpdate.orderId().equals(ORDER_ID) && - orderToUpdate.newStatus() == OrderStatus.PROCESSING - ))).thenReturn(processingOrder); - when(orderGrpcMapper.toUpdateOrderResponse(processingOrder)).thenReturn(expectedResponse); - - // when - orderGrpcService.updateOrder(request, responseObserver); - - // then - verify(orderFacade).findOrderById(ORDER_ID); - verify(orderFacade).updateOrder(any(OrderToUpdate.class)); - verify(orderGrpcMapper).toUpdateOrderResponse(processingOrder); - verify(responseObserver).onNext(expectedResponse); - verify(responseObserver).onCompleted(); - verify(responseObserver, never()).onError(any()); - } - - @Test - @DisplayName("Should return NOT_FOUND when order does not exist for update") - void should_return_not_found_when_order_does_not_exist_for_update() { - // given - UpdateOrderRequest request = UpdateOrderRequest.newBuilder() - .setOrderId(ORDER_UUID.toString()) - .build(); - - when(orderFacade.findOrderById(ORDER_ID)).thenReturn(Optional.empty()); - - // when - orderGrpcService.updateOrder(request, responseObserver); - - // then - ArgumentCaptor exceptionCaptor = ArgumentCaptor.forClass(StatusRuntimeException.class); - verify(responseObserver).onError(exceptionCaptor.capture()); - - StatusRuntimeException exception = exceptionCaptor.getValue(); - assertThat(exception.getStatus().getCode()).isEqualTo(Status.NOT_FOUND.getCode()); - - verify(responseObserver, never()).onNext(any()); - verify(responseObserver, never()).onCompleted(); - } - - @Test - @DisplayName("Should return INTERNAL error when update fails with unexpected exception") - void should_return_internal_error_when_update_fails() { - // given - UpdateOrderRequest request = UpdateOrderRequest.newBuilder() - .setOrderId(ORDER_UUID.toString()) - .build(); - - when(orderFacade.findOrderById(ORDER_ID)).thenThrow(new RuntimeException("Unexpected error")); - - // when - orderGrpcService.updateOrder(request, responseObserver); - - // then - ArgumentCaptor exceptionCaptor = ArgumentCaptor.forClass(StatusRuntimeException.class); - verify(responseObserver).onError(exceptionCaptor.capture()); - - StatusRuntimeException exception = exceptionCaptor.getValue(); - assertThat(exception.getStatus().getCode()).isEqualTo(Status.INTERNAL.getCode()); - - verify(responseObserver, never()).onNext(any()); - verify(responseObserver, never()).onCompleted(); - } - } - - @Nested - @DisplayName("Delete Order Tests") - class DeleteOrderTests { - - - private final StreamObserver responseObserver = mock(StreamObserver.class); - - @Test - @DisplayName("Should delete order successfully") - void should_delete_order_successfully() { - // given - DeleteOrderRequest request = DeleteOrderRequest.newBuilder() - .setOrderId(ORDER_UUID.toString()) - .build(); - - // when - orderGrpcService.deleteOrder(request, responseObserver); - - // then - verify(orderFacade).deleteOrder(ORDER_ID); - verify(responseObserver).onNext(argThat(DeleteOrderResponse::getSuccess)); - verify(responseObserver).onCompleted(); - verify(responseObserver, never()).onError(any()); - } - - @Test - @DisplayName("Should handle exception during order deletion") - void should_handle_exception_during_order_deletion() { - // given - DeleteOrderRequest request = DeleteOrderRequest.newBuilder() - .setOrderId(ORDER_UUID.toString()) - .build(); - - doThrow(new RuntimeException("Delete failed")).when(orderFacade).deleteOrder(ORDER_ID); - - // when - orderGrpcService.deleteOrder(request, responseObserver); - - // then - verify(responseObserver).onError(any(StatusRuntimeException.class)); - verify(responseObserver, never()).onNext(any()); - verify(responseObserver, never()).onCompleted(); - } - } @Nested @DisplayName("Get Order Status Tests") diff --git a/src/test/java/com/ecmsp/orderservice/order/adapter/repository/inmemory/InMemoryOrderRepositoryTest.java b/src/test/java/com/ecmsp/orderservice/order/adapter/repository/inmemory/InMemoryOrderRepositoryTest.java index edb4c1c..734f5c4 100644 --- a/src/test/java/com/ecmsp/orderservice/order/adapter/repository/inmemory/InMemoryOrderRepositoryTest.java +++ b/src/test/java/com/ecmsp/orderservice/order/adapter/repository/inmemory/InMemoryOrderRepositoryTest.java @@ -26,6 +26,7 @@ class InMemoryOrderRepositoryTest { private static final Order ORDER_1 = new Order( /* orderId = */ ORDER_1_ID, + /* reservationId = */ null, /* clientId = */ CLIENT_1_ID, /* orderStatus = */ OrderStatus.PENDING, /* date = */ DATE_2025_07_10_15_00_00, @@ -34,6 +35,7 @@ class InMemoryOrderRepositoryTest { private static final Order ORDER_2 = new Order( /* orderId = */ ORDER_2_ID, + /* reservationId = */ null, /* clientId = */ CLIENT_2_ID, /* orderStatus = */ OrderStatus.PENDING, /* date = */ DATE_2025_07_11_15_00_00, @@ -146,6 +148,7 @@ void should_update_order() { // and: Order updatedOrder = new Order( ORDER_1_ID, + null, CLIENT_1_ID, OrderStatus.PROCESSING, DATE_2025_07_10_15_00_00, @@ -171,6 +174,7 @@ void should_throw_exception_when_update_order_that_does_not_exist() { // and: Order updatedOrder2 = new Order( ORDER_2_ID, + null, CLIENT_2_ID, OrderStatus.PROCESSING, DATE_2025_07_11_15_00_00, diff --git a/src/test/java/com/ecmsp/orderservice/order/api/OrdersControllerTest.java b/src/test/java/com/ecmsp/orderservice/order/api/OrdersControllerTest.java index b1bf07d..1722d75 100644 --- a/src/test/java/com/ecmsp/orderservice/order/api/OrdersControllerTest.java +++ b/src/test/java/com/ecmsp/orderservice/order/api/OrdersControllerTest.java @@ -23,6 +23,7 @@ public class OrdersControllerTest { private static final Order ORDER_1 = new Order( /* orderId = */ new OrderId(UUID.fromString("3745fd3b-62b1-40a1-ab32-57aa2ecf562f")), + /* reservationId = */ null, /* clientId = */ new ClientId(UUID.fromString("b74d2425-a3ad-4138-ae70-3c4ecbcf5803")), /* orderStatus = */ OrderStatus.PENDING, /* date = */ LocalDateTime.of(2025, 7, 10, 15, 0, 0), @@ -31,6 +32,7 @@ public class OrdersControllerTest { private static final Order ORDER_2 = new Order( /* orderId = */ new OrderId(UUID.fromString("605c2114-faaa-447d-adfd-582408389958")), + /* reservationId = */ null, /* clientId = */ new ClientId(UUID.fromString("85f30610-467c-49a1-a50a-9686aa6089dc")), /* orderStatus = */ OrderStatus.PENDING, /* date = */ LocalDateTime.of(2025, 7, 11, 6, 0, 0), diff --git a/src/test/java/com/ecmsp/orderservice/order/domain/OrderFacadeTest.java b/src/test/java/com/ecmsp/orderservice/order/domain/OrderFacadeTest.java index 4fd40ed..6c7cedc 100644 --- a/src/test/java/com/ecmsp/orderservice/order/domain/OrderFacadeTest.java +++ b/src/test/java/com/ecmsp/orderservice/order/domain/OrderFacadeTest.java @@ -17,20 +17,28 @@ public class OrderFacadeTest { private static final OrderId ORDER_1_ID = new OrderId(UUID.fromString("123e4567-e89b-12d3-a456-426614174000")); private static final OrderId ORDER_2_ID = new OrderId(UUID.fromString("9e349a18-1203-4224-829c-dc15700c68a5")); + private static final ReservationId RESERVATION_1_ID = new ReservationId(UUID.fromString("c5e75ab0-a110-4a2a-b6f4-c4573e6f548e")); + private static final ClientId CLIENT_1_ID = new ClientId(UUID.fromString("b5d1eec8-c3ea-4b55-8cec-900b5c018381")); private static final ClientId CLIENT_2_ID = new ClientId(UUID.fromString("b259c7f1-483b-4700-accc-1554542eb8f5")); private static final List ITEMS = List.of( new OrderItem( /* itemId = */ new ItemId(UUID.fromString("66d155e8-2d57-44fa-9adc-580e1e4f9cc9")), + /* variantId = */ null, /* quantity = */ 2, /* price = */ BigDecimal.valueOf(10), + /* imageUrl = */ null, + /* description = */ null, /* isReturnable = */ true ), new OrderItem( /* itemId = */ new ItemId(UUID.fromString("473c1579-12b1-49b0-b90e-253782c874a5")), + /* variantId = */ null, /* quantity = */ 1, /* price = */ BigDecimal.valueOf(20), + /* imageUrl = */ null, + /* description = */ null, /* isReturnable = */ false ) ); @@ -41,6 +49,7 @@ public class OrderFacadeTest { private static final Order ORDER_1 = new Order( /* orderId = */ ORDER_1_ID, + /* reservationId = */ null, /* clientId = */ CLIENT_1_ID, /* orderStatus = */ OrderStatus.PENDING, /* date = */ DATE_2025_07_10_15_00_00, @@ -49,6 +58,7 @@ public class OrderFacadeTest { private static final Order ORDER_2 = new Order( /* orderId = */ ORDER_2_ID, + /* reservationId = */ null, /* clientId = */ CLIENT_2_ID, /* orderStatus = */ OrderStatus.PENDING, /* date = */ DATE_2025_07_11_15_00_00, @@ -70,12 +80,13 @@ void should_create_order() { // when: - Order createdOrder = facade.createOrder(new OrderToCreate(CLIENT_1_ID, ITEMS), new Context(null)); + Order createdOrder = facade.createOrder(new OrderToCreate(RESERVATION_1_ID, CLIENT_1_ID, ITEMS), new Context(null)); // then: assertThat(createdOrder).isEqualTo( new Order( /* orderId = */ ORDER_1_ID, + /* reservationId = */ RESERVATION_1_ID, /* clientId = */ CLIENT_1_ID, /* orderStatus = */ OrderStatus.PENDING, /* date = */ DATE_2025_07_10_15_00_00, @@ -101,7 +112,7 @@ void should_fail_when_create_order_with_existing_id() { // when: var error = assertThatThrownBy(() -> // Trying to create an order with the same ID: ORDER_1_ID - facade.createOrder(new OrderToCreate(CLIENT_1_ID, ITEMS), new Context(null)) + facade.createOrder(new OrderToCreate(RESERVATION_1_ID, CLIENT_1_ID, ITEMS), new Context(null)) ); // then: @@ -135,6 +146,7 @@ void should_update_order() { assertThat(updatedOrder).isEqualTo( new Order( /* orderId = */ ORDER_1_ID, + /* reservationId = */ null, /* clientId = */ CLIENT_1_ID, /* orderStatus = */ OrderStatus.PAID, /* date = */ DATE_2025_07_10_15_00_00, @@ -275,12 +287,15 @@ void should_return_true_when_order_is_returnable() { List returnableItems = List.of( new OrderItem( new ItemId(UUID.fromString("66d155e8-2d57-44fa-9adc-580e1e4f9cc9")), + null, 2, BigDecimal.valueOf(10), + null, + null, true ) ); - Order returnableOrder = new Order(ORDER_1_ID, CLIENT_1_ID, OrderStatus.PAID, recentDate, returnableItems); + Order returnableOrder = new Order(ORDER_1_ID, null, CLIENT_1_ID, OrderStatus.PAID, recentDate, returnableItems); TestOrderRepository orderRepository = new TestOrderRepository(List.of(returnableOrder)); OrderFacade facade = new DefaultOrderFacade( @@ -306,12 +321,15 @@ void should_return_false_when_order_is_too_old() { List returnableItems = List.of( new OrderItem( new ItemId(UUID.fromString("66d155e8-2d57-44fa-9adc-580e1e4f9cc9")), + null, 2, BigDecimal.valueOf(10), + null, + null, true ) ); - Order oldOrder = new Order(ORDER_1_ID, CLIENT_1_ID, OrderStatus.PAID, oldDate, returnableItems); + Order oldOrder = new Order(ORDER_1_ID, null, CLIENT_1_ID, OrderStatus.PAID, oldDate, returnableItems); TestOrderRepository orderRepository = new TestOrderRepository(List.of(oldOrder)); OrderFacade facade = new DefaultOrderFacade( @@ -337,12 +355,15 @@ void should_return_false_when_no_items_are_returnable() { List nonReturnableItems = List.of( new OrderItem( new ItemId(UUID.fromString("66d155e8-2d57-44fa-9adc-580e1e4f9cc9")), + null, 2, BigDecimal.valueOf(10), + null, + null, false // not returnable ) ); - Order nonReturnableOrder = new Order(ORDER_1_ID, CLIENT_1_ID, OrderStatus.PAID, recentDate, nonReturnableItems); + Order nonReturnableOrder = new Order(ORDER_1_ID, null, CLIENT_1_ID, OrderStatus.PAID, recentDate, nonReturnableItems); TestOrderRepository orderRepository = new TestOrderRepository(List.of(nonReturnableOrder)); OrderFacade facade = new DefaultOrderFacade( @@ -367,18 +388,24 @@ void should_return_only_returnable_items_within_return_period() { LocalDateTime recentDate = LocalDateTime.now().minusDays(7); // 7 days ago OrderItem returnableItem = new OrderItem( new ItemId(UUID.fromString("66d155e8-2d57-44fa-9adc-580e1e4f9cc9")), + null, 2, BigDecimal.valueOf(10), + null, + null, true ); OrderItem nonReturnableItem = new OrderItem( new ItemId(UUID.fromString("473c1579-12b1-49b0-b90e-253782c874a5")), + null, 1, BigDecimal.valueOf(20), + null, + null, false ); List mixedItems = List.of(returnableItem, nonReturnableItem); - Order mixedOrder = new Order(ORDER_1_ID, CLIENT_1_ID, OrderStatus.PAID, recentDate, mixedItems); + Order mixedOrder = new Order(ORDER_1_ID, null, CLIENT_1_ID, OrderStatus.PAID, recentDate, mixedItems); TestOrderRepository orderRepository = new TestOrderRepository(List.of(mixedOrder)); OrderFacade facade = new DefaultOrderFacade( @@ -403,11 +430,14 @@ void should_return_empty_list_when_order_is_outside_return_period() { LocalDateTime oldDate = LocalDateTime.now().minusDays(20); // 20 days ago OrderItem returnableItem = new OrderItem( new ItemId(UUID.fromString("66d155e8-2d57-44fa-9adc-580e1e4f9cc9")), + null, 2, BigDecimal.valueOf(10), + null, + null, true ); - Order oldOrder = new Order(ORDER_1_ID, CLIENT_1_ID, OrderStatus.PAID, oldDate, List.of(returnableItem)); + Order oldOrder = new Order(ORDER_1_ID, null, CLIENT_1_ID, OrderStatus.PAID, oldDate, List.of(returnableItem)); TestOrderRepository orderRepository = new TestOrderRepository(List.of(oldOrder)); OrderFacade facade = new DefaultOrderFacade(