From ab0a717d951e8357ecb95255e8f7664ebef4a178 Mon Sep 17 00:00:00 2001 From: voody Date: Sun, 21 Jul 2024 23:15:59 +0300 Subject: [PATCH 1/5] implemented order model --- .../academy/controller/OrderController.java | 122 ++++++++++++++++++ .../java/mate/academy/dto/order/OrderDto.java | 19 +++ .../mate/academy/dto/order/OrderItemDto.java | 12 ++ .../dto/order/PlaceOrderRequestDto.java | 10 ++ .../order/UpdateOrderStatusRequestDto.java | 11 ++ .../mate/academy/mapper/OrderItemMapper.java | 18 +++ .../java/mate/academy/mapper/OrderMapper.java | 16 +++ src/main/java/mate/academy/model/Order.java | 61 +++++++++ .../java/mate/academy/model/OrderItem.java | 43 ++++++ .../repository/order/OrderRepository.java | 10 ++ .../orderitem/OrderItemRepository.java | 13 ++ .../mate/academy/service/OrderService.java | 19 +++ .../service/impl/OrderServiceImpl.java | 116 +++++++++++++++++ .../changes/15-create-orders-table.yaml | 52 ++++++++ .../changes/16-create-order_items-table.yaml | 53 ++++++++ .../db/changelog/db.changelog-master.yaml | 5 +- 16 files changed, 579 insertions(+), 1 deletion(-) create mode 100644 src/main/java/mate/academy/controller/OrderController.java create mode 100644 src/main/java/mate/academy/dto/order/OrderDto.java create mode 100644 src/main/java/mate/academy/dto/order/OrderItemDto.java create mode 100644 src/main/java/mate/academy/dto/order/PlaceOrderRequestDto.java create mode 100644 src/main/java/mate/academy/dto/order/UpdateOrderStatusRequestDto.java create mode 100644 src/main/java/mate/academy/mapper/OrderItemMapper.java create mode 100644 src/main/java/mate/academy/mapper/OrderMapper.java create mode 100644 src/main/java/mate/academy/model/Order.java create mode 100644 src/main/java/mate/academy/model/OrderItem.java create mode 100644 src/main/java/mate/academy/repository/order/OrderRepository.java create mode 100644 src/main/java/mate/academy/repository/orderitem/OrderItemRepository.java create mode 100644 src/main/java/mate/academy/service/OrderService.java create mode 100644 src/main/java/mate/academy/service/impl/OrderServiceImpl.java create mode 100644 src/main/resources/db/changelog/changes/15-create-orders-table.yaml create mode 100644 src/main/resources/db/changelog/changes/16-create-order_items-table.yaml diff --git a/src/main/java/mate/academy/controller/OrderController.java b/src/main/java/mate/academy/controller/OrderController.java new file mode 100644 index 0000000..d17a5cf --- /dev/null +++ b/src/main/java/mate/academy/controller/OrderController.java @@ -0,0 +1,122 @@ +package mate.academy.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import java.util.List; +import lombok.RequiredArgsConstructor; +import mate.academy.dto.order.OrderDto; +import mate.academy.dto.order.OrderItemDto; +import mate.academy.dto.order.PlaceOrderRequestDto; +import mate.academy.dto.order.UpdateOrderStatusRequestDto; +import mate.academy.model.User; +import mate.academy.service.OrderService; +import org.springdoc.core.annotations.ParameterObject; +import org.springframework.data.domain.Pageable; +import org.springframework.data.web.PageableDefault; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/orders") +@RequiredArgsConstructor +@Tag(name = "Order Controller", description = "Operations pertaining to orders in Online Book App") +public class OrderController { + private final OrderService orderService; + + @PreAuthorize("hasRole('USER')") + @PostMapping + @Operation(summary = "Place an order", + description = "Allows a user to place an order with the items in their shopping cart.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "Order placed successfully"), + @ApiResponse(responseCode = "400", description = "Invalid input data"), + @ApiResponse(responseCode = "401", description = "Unauthorized"), + @ApiResponse(responseCode = "404", description = "User not found") + }) + public OrderDto placeOrder( + @AuthenticationPrincipal User userDetails, + @RequestBody @Parameter(description = "Details for placing an order", required = true) + PlaceOrderRequestDto requestDto) { + return orderService.createOrder(userDetails.getId(), requestDto.getShippingAddress()); + } + + @PreAuthorize("hasRole('USER')") + @GetMapping + @Operation(summary = "Get order history", + description = "Allows a user to view their order history.") + @ApiResponses({ + @ApiResponse(responseCode = "200", + description = "Order history retrieved successfully"), + @ApiResponse(responseCode = "401", description = "Unauthorized"), + @ApiResponse(responseCode = "404", description = "User not found") + }) + public List getOrderHistory( + @AuthenticationPrincipal User userDetails, + @ParameterObject @PageableDefault Pageable pageable) { + return orderService.getOrderHistory(userDetails.getId(), pageable); + } + + @PreAuthorize("hasRole('USER')") + @GetMapping("/{orderId}/items") + @Operation(summary = "Get order items", + description = "Allows a user to view all items in a specific order.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "Order items retrieved successfully"), + @ApiResponse(responseCode = "401", description = "Unauthorized"), + @ApiResponse(responseCode = "404", description = "Order not found") + }) + public List getOrderItems( + @PathVariable + @Parameter(description = "ID of the order to retrieve items from", required = true) + Long orderId, + @ParameterObject @PageableDefault Pageable pageable) { + return orderService.getOrderItems(orderId, pageable); + } + + @PreAuthorize("hasRole('USER')") + @GetMapping("/{orderId}/items/{itemId}") + @Operation(summary = "Get specific order item", + description = "Allows a user to view a specific item in an order.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "Order item retrieved successfully"), + @ApiResponse(responseCode = "401", description = "Unauthorized"), + @ApiResponse(responseCode = "404", description = "Order or item not found") + }) + public OrderItemDto getOrderItem( + @PathVariable + @Parameter(description = "ID of the order to retrieve the item from", required = true) + Long orderId, + @PathVariable @Parameter(description = "ID of the item to retrieve", required = true) + Long itemId) { + return orderService.getOrderItem(orderId, itemId); + } + + @PreAuthorize("hasRole('ADMIN')") + @PatchMapping("/{orderId}") + @Operation(summary = "Update order status", + description = "Allows an admin to update the status of an order.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "Order status updated successfully"), + @ApiResponse(responseCode = "400", description = "Invalid status"), + @ApiResponse(responseCode = "401", description = "Unauthorized"), + @ApiResponse(responseCode = "404", description = "Order not found") + }) + public void updateOrderStatus( + @PathVariable + @Parameter(description = "ID of the order to update", required = true) Long orderId, + @RequestBody + @Parameter(description = "Details for updating the order status", required = true) + UpdateOrderStatusRequestDto requestDto) { + orderService.updateOrderStatus(orderId, requestDto.getStatus()); + } +} diff --git a/src/main/java/mate/academy/dto/order/OrderDto.java b/src/main/java/mate/academy/dto/order/OrderDto.java new file mode 100644 index 0000000..f1f21bb --- /dev/null +++ b/src/main/java/mate/academy/dto/order/OrderDto.java @@ -0,0 +1,19 @@ +package mate.academy.dto.order; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.Set; + +public record OrderDto( + @NotNull Long id, + @NotNull Long userId, + @NotBlank String status, + @NotNull @Positive BigDecimal total, + @NotNull LocalDateTime orderDate, + @NotBlank String shippingAddress, + @NotNull Set orderItems +) { +} diff --git a/src/main/java/mate/academy/dto/order/OrderItemDto.java b/src/main/java/mate/academy/dto/order/OrderItemDto.java new file mode 100644 index 0000000..9e36ef2 --- /dev/null +++ b/src/main/java/mate/academy/dto/order/OrderItemDto.java @@ -0,0 +1,12 @@ +package mate.academy.dto.order; + +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; +import java.math.BigDecimal; + +public record OrderItemDto(@NotNull Long id, + @NotNull Long orderId, + @NotNull Long bookId, + @Positive int quantity, + @Positive BigDecimal price) { +} diff --git a/src/main/java/mate/academy/dto/order/PlaceOrderRequestDto.java b/src/main/java/mate/academy/dto/order/PlaceOrderRequestDto.java new file mode 100644 index 0000000..ab759bb --- /dev/null +++ b/src/main/java/mate/academy/dto/order/PlaceOrderRequestDto.java @@ -0,0 +1,10 @@ +package mate.academy.dto.order; + +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +@Data +public class PlaceOrderRequestDto { + @NotBlank + private String shippingAddress; +} diff --git a/src/main/java/mate/academy/dto/order/UpdateOrderStatusRequestDto.java b/src/main/java/mate/academy/dto/order/UpdateOrderStatusRequestDto.java new file mode 100644 index 0000000..33d00c6 --- /dev/null +++ b/src/main/java/mate/academy/dto/order/UpdateOrderStatusRequestDto.java @@ -0,0 +1,11 @@ +package mate.academy.dto.order; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import mate.academy.model.Order; + +@Data +public class UpdateOrderStatusRequestDto { + @NotNull + private Order.Status status; +} diff --git a/src/main/java/mate/academy/mapper/OrderItemMapper.java b/src/main/java/mate/academy/mapper/OrderItemMapper.java new file mode 100644 index 0000000..734eea7 --- /dev/null +++ b/src/main/java/mate/academy/mapper/OrderItemMapper.java @@ -0,0 +1,18 @@ +package mate.academy.mapper; + +import mate.academy.config.MapperConfig; +import mate.academy.dto.order.OrderItemDto; +import mate.academy.model.OrderItem; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +@Mapper(config = MapperConfig.class) +public interface OrderItemMapper { + @Mapping(source = "order.id", target = "orderId") + @Mapping(source = "book.id", target = "bookId") + OrderItemDto toDto(OrderItem orderItem); + + @Mapping(source = "orderId", target = "order.id") + @Mapping(source = "bookId", target = "book.id") + OrderItem toModel(OrderItemDto orderItemDto); +} diff --git a/src/main/java/mate/academy/mapper/OrderMapper.java b/src/main/java/mate/academy/mapper/OrderMapper.java new file mode 100644 index 0000000..e66262b --- /dev/null +++ b/src/main/java/mate/academy/mapper/OrderMapper.java @@ -0,0 +1,16 @@ +package mate.academy.mapper; + +import mate.academy.config.MapperConfig; +import mate.academy.dto.order.OrderDto; +import mate.academy.model.Order; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +@Mapper(config = MapperConfig.class, uses = OrderItemMapper.class) +public interface OrderMapper { + @Mapping(source = "user.id", target = "userId") + OrderDto toDto(Order order); + + @Mapping(source = "userId", target = "user.id") + Order toModel(OrderDto orderDto); +} diff --git a/src/main/java/mate/academy/model/Order.java b/src/main/java/mate/academy/model/Order.java new file mode 100644 index 0000000..c95eb78 --- /dev/null +++ b/src/main/java/mate/academy/model/Order.java @@ -0,0 +1,61 @@ +package mate.academy.model; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.HashSet; +import java.util.Set; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import org.hibernate.annotations.SQLDelete; +import org.hibernate.annotations.SQLRestriction; + +@Entity +@Getter +@Setter +@SQLDelete(sql = "UPDATE orders set is_deleted = true WHERE id=?") +@SQLRestriction("is_deleted=false") +@Table(name = "orders") +public class Order { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + @ManyToOne(fetch = FetchType.LAZY, optional = false) + @JoinColumn(name = "user_id", nullable = false) + private User user; + @Enumerated(EnumType.STRING) + @Column(nullable = false) + private Status status; + @Column(nullable = false) + private BigDecimal total; + @Column(nullable = false) + private LocalDateTime orderDate; + @Column(nullable = false) + private String shippingAddress; + @OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true) + @ToString.Exclude + @EqualsAndHashCode.Exclude + private Set orderItems = new HashSet<>(); + @Column(nullable = false, name = "is_deleted", columnDefinition = "BIT") + private boolean isDeleted = false; + + public enum Status { + COMPLETED, + PENDING, + DELIVERED + } +} diff --git a/src/main/java/mate/academy/model/OrderItem.java b/src/main/java/mate/academy/model/OrderItem.java new file mode 100644 index 0000000..d219b8a --- /dev/null +++ b/src/main/java/mate/academy/model/OrderItem.java @@ -0,0 +1,43 @@ +package mate.academy.model; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import java.math.BigDecimal; +import lombok.Getter; +import lombok.Setter; +import org.hibernate.annotations.SQLDelete; +import org.hibernate.annotations.SQLRestriction; + +@Entity +@Getter +@Setter +@SQLDelete(sql = "UPDATE order_items set is_deleted = true WHERE id=?") +@SQLRestriction("is_deleted=false") +@Table(name = "order_items") +public class OrderItem { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY, optional = false) + @JoinColumn(name = "order_id", nullable = false) + private Order order; + + @ManyToOne(fetch = FetchType.LAZY, optional = false) + @JoinColumn(name = "book_id", nullable = false) + private Book book; + + @Column(nullable = false) + private int quantity; + @Column(nullable = false) + private BigDecimal price; + @Column(nullable = false, name = "is_deleted", columnDefinition = "BIT") + private boolean isDeleted = false; +} diff --git a/src/main/java/mate/academy/repository/order/OrderRepository.java b/src/main/java/mate/academy/repository/order/OrderRepository.java new file mode 100644 index 0000000..7266f7b --- /dev/null +++ b/src/main/java/mate/academy/repository/order/OrderRepository.java @@ -0,0 +1,10 @@ +package mate.academy.repository.order; + +import java.util.List; +import mate.academy.model.Order; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface OrderRepository extends JpaRepository { + List findByUserId(Long userId, Pageable pageable); +} diff --git a/src/main/java/mate/academy/repository/orderitem/OrderItemRepository.java b/src/main/java/mate/academy/repository/orderitem/OrderItemRepository.java new file mode 100644 index 0000000..0cb7bf5 --- /dev/null +++ b/src/main/java/mate/academy/repository/orderitem/OrderItemRepository.java @@ -0,0 +1,13 @@ +package mate.academy.repository.orderitem; + +import java.util.List; +import java.util.Optional; +import mate.academy.model.OrderItem; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface OrderItemRepository extends JpaRepository { + List findByOrderId(Long orderId, Pageable pageable); + + Optional findByOrderIdAndId(Long orderId, Long id); +} diff --git a/src/main/java/mate/academy/service/OrderService.java b/src/main/java/mate/academy/service/OrderService.java new file mode 100644 index 0000000..8cc8569 --- /dev/null +++ b/src/main/java/mate/academy/service/OrderService.java @@ -0,0 +1,19 @@ +package mate.academy.service; + +import java.util.List; +import mate.academy.dto.order.OrderDto; +import mate.academy.dto.order.OrderItemDto; +import mate.academy.model.Order; +import org.springframework.data.domain.Pageable; + +public interface OrderService { + OrderDto createOrder(Long userId, String shippingAddress); + + List getOrderHistory(Long userId, Pageable pageable); + + List getOrderItems(Long orderId, Pageable pageable); + + OrderItemDto getOrderItem(Long orderId, Long itemId); + + void updateOrderStatus(Long orderId, Order.Status status); +} diff --git a/src/main/java/mate/academy/service/impl/OrderServiceImpl.java b/src/main/java/mate/academy/service/impl/OrderServiceImpl.java new file mode 100644 index 0000000..23ce3c5 --- /dev/null +++ b/src/main/java/mate/academy/service/impl/OrderServiceImpl.java @@ -0,0 +1,116 @@ +package mate.academy.service.impl; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; +import lombok.RequiredArgsConstructor; +import mate.academy.dto.order.OrderDto; +import mate.academy.dto.order.OrderItemDto; +import mate.academy.exception.EntityNotFoundException; +import mate.academy.mapper.OrderItemMapper; +import mate.academy.mapper.OrderMapper; +import mate.academy.model.CartItem; +import mate.academy.model.Order; +import mate.academy.model.OrderItem; +import mate.academy.model.ShoppingCart; +import mate.academy.model.User; +import mate.academy.repository.order.OrderRepository; +import mate.academy.repository.orderitem.OrderItemRepository; +import mate.academy.repository.shoppingcart.ShoppingCartRepository; +import mate.academy.repository.user.UserRepository; +import mate.academy.service.OrderService; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class OrderServiceImpl implements OrderService { + private final ShoppingCartRepository shoppingCartRepository; + private final UserRepository userRepository; + private final OrderRepository orderRepository; + private final OrderItemRepository orderItemRepository; + private final OrderMapper orderMapper; + private final OrderItemMapper orderItemMapper; + + @Transactional + @Override + public OrderDto createOrder(Long userId, String shippingAddress) { + ShoppingCart shoppingCart = shoppingCartRepository.findByUserId(userId); + if (shoppingCart == null || shoppingCart.getCartItems().isEmpty()) { + throw new EntityNotFoundException("Shopping cart is empty"); + } + + User user = userRepository.findById(userId) + .orElseThrow(() -> new EntityNotFoundException("User not found")); + + Order order = makeOrder(shippingAddress, user); + + BigDecimal total = BigDecimal.ZERO; + + for (CartItem cartItem : shoppingCart.getCartItems()) { + OrderItem orderItem = new OrderItem(); + orderItem.setOrder(order); + orderItem.setBook(cartItem.getBook()); + orderItem.setQuantity(cartItem.getQuantity()); + orderItem.setPrice(cartItem.getBook().getPrice() + .multiply(BigDecimal.valueOf(cartItem.getQuantity()))); + orderItem.setDeleted(false); + order.getOrderItems().add(orderItem); + + total = total.add(orderItem.getPrice()); + } + + order.setTotal(total); + orderRepository.save(order); + orderItemRepository.saveAll(order.getOrderItems()); + + shoppingCart.getCartItems().clear(); + shoppingCartRepository.save(shoppingCart); + + return orderMapper.toDto(order); + } + + @Transactional(readOnly = true) + @Override + public List getOrderHistory(Long userId, Pageable pageable) { + return orderRepository.findByUserId(userId, pageable).stream() + .map(orderMapper::toDto) + .toList(); + } + + @Transactional(readOnly = true) + @Override + public List getOrderItems(Long orderId, Pageable pageable) { + return orderItemRepository.findByOrderId(orderId, pageable).stream() + .map(orderItemMapper::toDto) + .toList(); + } + + @Transactional(readOnly = true) + @Override + public OrderItemDto getOrderItem(Long orderId, Long itemId) { + OrderItem orderItem = orderItemRepository.findByOrderIdAndId(orderId, itemId) + .orElseThrow(() -> new EntityNotFoundException("Order item not found")); + return orderItemMapper.toDto(orderItem); + } + + @Transactional + @Override + public void updateOrderStatus(Long orderId, Order.Status status) { + Order order = orderRepository.findById(orderId) + .orElseThrow(() -> new EntityNotFoundException("Order not found")); + order.setStatus(status); + orderRepository.save(order); + } + + private static Order makeOrder(String shippingAddress, User user) { + Order order = new Order(); + order.setUser(user); + order.setStatus(Order.Status.PENDING); + order.setOrderDate(LocalDateTime.now()); + order.setShippingAddress(shippingAddress); + order.setDeleted(false); + return order; + } +} diff --git a/src/main/resources/db/changelog/changes/15-create-orders-table.yaml b/src/main/resources/db/changelog/changes/15-create-orders-table.yaml new file mode 100644 index 0000000..63de4ae --- /dev/null +++ b/src/main/resources/db/changelog/changes/15-create-orders-table.yaml @@ -0,0 +1,52 @@ +databaseChangeLog: + - changeSet: + id: 15-create-order-table + author: kdjh + changes: + - createTable: + tableName: orders + columns: + - column: + name: id + type: BIGINT + autoIncrement: true + constraints: + primaryKey: true + nullable: false + - column: + name: user_id + type: BIGINT + constraints: + nullable: false + - column: + name: status + type: VARCHAR(50) + constraints: + nullable: false + - column: + name: total + type: DECIMAL(19,2) + constraints: + nullable: false + - column: + name: order_date + type: DATETIME + constraints: + nullable: false + - column: + name: shipping_address + type: VARCHAR(255) + constraints: + nullable: false + - column: + name: is_deleted + type: BIT + defaultValueBoolean: false + constraints: + nullable: false + - addForeignKeyConstraint: + baseTableName: orders + baseColumnNames: user_id + referencedTableName: users + referencedColumnNames: id + constraintName: fk_order_user \ No newline at end of file diff --git a/src/main/resources/db/changelog/changes/16-create-order_items-table.yaml b/src/main/resources/db/changelog/changes/16-create-order_items-table.yaml new file mode 100644 index 0000000..41ad3bc --- /dev/null +++ b/src/main/resources/db/changelog/changes/16-create-order_items-table.yaml @@ -0,0 +1,53 @@ +databaseChangeLog: + - changeSet: + id: 16-create-order-item-table + author: jhgho + changes: + - createTable: + tableName: order_items + columns: + - column: + name: id + type: BIGINT + autoIncrement: true + constraints: + primaryKey: true + nullable: false + - column: + name: order_id + type: BIGINT + constraints: + nullable: false + - column: + name: book_id + type: BIGINT + constraints: + nullable: false + - column: + name: quantity + type: INT + constraints: + nullable: false + - column: + name: price + type: DECIMAL(19,2) + constraints: + nullable: false + - column: + name: is_deleted + type: BIT + defaultValueBoolean: false + constraints: + nullable: false + - addForeignKeyConstraint: + baseTableName: order_items + baseColumnNames: order_id + referencedTableName: orders + referencedColumnNames: id + constraintName: fk_order-item_order + - addForeignKeyConstraint: + baseTableName: order_items + baseColumnNames: book_id + referencedTableName: books + referencedColumnNames: id + constraintName: fk_order-item_book \ No newline at end of file diff --git a/src/main/resources/db/changelog/db.changelog-master.yaml b/src/main/resources/db/changelog/db.changelog-master.yaml index 446cc01..c4debc5 100644 --- a/src/main/resources/db/changelog/db.changelog-master.yaml +++ b/src/main/resources/db/changelog/db.changelog-master.yaml @@ -23,5 +23,8 @@ databaseChangeLog: file: db/changelog/changes/11-add-foreign-key-constraints.yaml - include: file: db/changelog/changes/12-insert-data-to-categories-table.yaml - + - include: + file: db/changelog/changes/15-create-orders-table.yaml + - include: + file: db/changelog/changes/16-create-order_items-table.yaml From d4de0e69aa4c04bffea5fa279598db8125cfbf26 Mon Sep 17 00:00:00 2001 From: voody Date: Tue, 23 Jul 2024 13:43:34 +0300 Subject: [PATCH 2/5] applied suggested changes --- .../service/impl/OrderServiceImpl.java | 72 ++++++++++++------- 1 file changed, 46 insertions(+), 26 deletions(-) diff --git a/src/main/java/mate/academy/service/impl/OrderServiceImpl.java b/src/main/java/mate/academy/service/impl/OrderServiceImpl.java index 23ce3c5..839ff84 100644 --- a/src/main/java/mate/academy/service/impl/OrderServiceImpl.java +++ b/src/main/java/mate/academy/service/impl/OrderServiceImpl.java @@ -36,37 +36,16 @@ public class OrderServiceImpl implements OrderService { @Transactional @Override public OrderDto createOrder(Long userId, String shippingAddress) { - ShoppingCart shoppingCart = shoppingCartRepository.findByUserId(userId); - if (shoppingCart == null || shoppingCart.getCartItems().isEmpty()) { - throw new EntityNotFoundException("Shopping cart is empty"); - } - - User user = userRepository.findById(userId) - .orElseThrow(() -> new EntityNotFoundException("User not found")); + ShoppingCart shoppingCart = getShoppingCart(userId); + User user = getUser(userId); Order order = makeOrder(shippingAddress, user); - - BigDecimal total = BigDecimal.ZERO; - - for (CartItem cartItem : shoppingCart.getCartItems()) { - OrderItem orderItem = new OrderItem(); - orderItem.setOrder(order); - orderItem.setBook(cartItem.getBook()); - orderItem.setQuantity(cartItem.getQuantity()); - orderItem.setPrice(cartItem.getBook().getPrice() - .multiply(BigDecimal.valueOf(cartItem.getQuantity()))); - orderItem.setDeleted(false); - order.getOrderItems().add(orderItem); - - total = total.add(orderItem.getPrice()); - } + BigDecimal total = calculateTotalAndAddOrderItems(shoppingCart, order); order.setTotal(total); - orderRepository.save(order); - orderItemRepository.saveAll(order.getOrderItems()); + saveOrderAndOrderItems(order); - shoppingCart.getCartItems().clear(); - shoppingCartRepository.save(shoppingCart); + clearShoppingCart(shoppingCart); return orderMapper.toDto(order); } @@ -113,4 +92,45 @@ private static Order makeOrder(String shippingAddress, User user) { order.setDeleted(false); return order; } + + private ShoppingCart getShoppingCart(Long userId) { + ShoppingCart shoppingCart = shoppingCartRepository.findByUserId(userId); + if (shoppingCart == null || shoppingCart.getCartItems().isEmpty()) { + throw new EntityNotFoundException("Shopping cart is empty"); + } + return shoppingCart; + } + + private User getUser(Long userId) { + return userRepository.findById(userId) + .orElseThrow(() -> new EntityNotFoundException("User not found")); + } + + private BigDecimal calculateTotalAndAddOrderItems(ShoppingCart shoppingCart, Order order) { + BigDecimal total = BigDecimal.ZERO; + + for (CartItem cartItem : shoppingCart.getCartItems()) { + OrderItem orderItem = new OrderItem(); + orderItem.setOrder(order); + orderItem.setBook(cartItem.getBook()); + orderItem.setQuantity(cartItem.getQuantity()); + orderItem.setPrice(cartItem.getBook().getPrice() + .multiply(BigDecimal.valueOf(cartItem.getQuantity()))); + orderItem.setDeleted(false); + order.getOrderItems().add(orderItem); + + total = total.add(orderItem.getPrice()); + } + return total; + } + + private void saveOrderAndOrderItems(Order order) { + orderRepository.save(order); + orderItemRepository.saveAll(order.getOrderItems()); + } + + private void clearShoppingCart(ShoppingCart shoppingCart) { + shoppingCart.getCartItems().clear(); + shoppingCartRepository.save(shoppingCart); + } } From f781d08c273bf1a5afb50d15bf9bb99b96ccd63f Mon Sep 17 00:00:00 2001 From: voody Date: Thu, 25 Jul 2024 18:00:57 +0300 Subject: [PATCH 3/5] applied suggested changes --- .../java/mate/academy/model/OrderItem.java | 1 - .../academy/service/impl/OrderServiceImpl.java | 18 ++++++++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/main/java/mate/academy/model/OrderItem.java b/src/main/java/mate/academy/model/OrderItem.java index d219b8a..9ae8a26 100644 --- a/src/main/java/mate/academy/model/OrderItem.java +++ b/src/main/java/mate/academy/model/OrderItem.java @@ -33,7 +33,6 @@ public class OrderItem { @ManyToOne(fetch = FetchType.LAZY, optional = false) @JoinColumn(name = "book_id", nullable = false) private Book book; - @Column(nullable = false) private int quantity; @Column(nullable = false) diff --git a/src/main/java/mate/academy/service/impl/OrderServiceImpl.java b/src/main/java/mate/academy/service/impl/OrderServiceImpl.java index 839ff84..6469e4f 100644 --- a/src/main/java/mate/academy/service/impl/OrderServiceImpl.java +++ b/src/main/java/mate/academy/service/impl/OrderServiceImpl.java @@ -3,6 +3,8 @@ import java.math.BigDecimal; import java.time.LocalDateTime; import java.util.List; +import java.util.Set; + import lombok.RequiredArgsConstructor; import mate.academy.dto.order.OrderDto; import mate.academy.dto.order.OrderItemDto; @@ -40,7 +42,8 @@ public OrderDto createOrder(Long userId, String shippingAddress) { User user = getUser(userId); Order order = makeOrder(shippingAddress, user); - BigDecimal total = calculateTotalAndAddOrderItems(shoppingCart, order); + addOrderItemsFromCart(shoppingCart, order); + BigDecimal total = calculateTotal(order.getOrderItems()); order.setTotal(total); saveOrderAndOrderItems(order); @@ -83,7 +86,7 @@ public void updateOrderStatus(Long orderId, Order.Status status) { orderRepository.save(order); } - private static Order makeOrder(String shippingAddress, User user) { + private Order makeOrder(String shippingAddress, User user) { Order order = new Order(); order.setUser(user); order.setStatus(Order.Status.PENDING); @@ -106,9 +109,7 @@ private User getUser(Long userId) { .orElseThrow(() -> new EntityNotFoundException("User not found")); } - private BigDecimal calculateTotalAndAddOrderItems(ShoppingCart shoppingCart, Order order) { - BigDecimal total = BigDecimal.ZERO; - + private void addOrderItemsFromCart(ShoppingCart shoppingCart, Order order) { for (CartItem cartItem : shoppingCart.getCartItems()) { OrderItem orderItem = new OrderItem(); orderItem.setOrder(order); @@ -118,8 +119,13 @@ private BigDecimal calculateTotalAndAddOrderItems(ShoppingCart shoppingCart, Ord .multiply(BigDecimal.valueOf(cartItem.getQuantity()))); orderItem.setDeleted(false); order.getOrderItems().add(orderItem); + } + } - total = total.add(orderItem.getPrice()); + private BigDecimal calculateTotal(Set orderItems) { + BigDecimal total = BigDecimal.ZERO; + for (OrderItem item : orderItems) { + total = total.add(item.getPrice()); } return total; } From f14f18160c90c5ec1d8cc5697f453dab43e4aac2 Mon Sep 17 00:00:00 2001 From: voody Date: Thu, 25 Jul 2024 18:04:24 +0300 Subject: [PATCH 4/5] applied suggested changes --- src/main/java/mate/academy/service/impl/OrderServiceImpl.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/mate/academy/service/impl/OrderServiceImpl.java b/src/main/java/mate/academy/service/impl/OrderServiceImpl.java index 6469e4f..2239dd9 100644 --- a/src/main/java/mate/academy/service/impl/OrderServiceImpl.java +++ b/src/main/java/mate/academy/service/impl/OrderServiceImpl.java @@ -4,7 +4,6 @@ import java.time.LocalDateTime; import java.util.List; import java.util.Set; - import lombok.RequiredArgsConstructor; import mate.academy.dto.order.OrderDto; import mate.academy.dto.order.OrderItemDto; From fb65d91d7110f8fd65883daea8a833cbccf6ac23 Mon Sep 17 00:00:00 2001 From: voody Date: Fri, 26 Jul 2024 22:01:48 +0300 Subject: [PATCH 5/5] applied suggested changes --- .../academy/controller/OrderController.java | 6 ++-- .../java/mate/academy/dto/order/OrderDto.java | 17 ++++------ .../mate/academy/dto/order/OrderItemDto.java | 13 ++++---- .../dto/order/PlaceOrderRequestDto.java | 8 ++--- .../order/UpdateOrderStatusRequestDto.java | 8 ++--- .../exception/OrderProcessingException.java | 7 ++++ src/main/java/mate/academy/model/Order.java | 4 +-- .../shoppingcart/ShoppingCartRepository.java | 3 +- .../mate/academy/service/OrderService.java | 3 +- .../service/impl/OrderServiceImpl.java | 33 +++++++++---------- .../service/impl/ShoppingCartServiceImpl.java | 21 ++++++------ 11 files changed, 60 insertions(+), 63 deletions(-) create mode 100644 src/main/java/mate/academy/exception/OrderProcessingException.java diff --git a/src/main/java/mate/academy/controller/OrderController.java b/src/main/java/mate/academy/controller/OrderController.java index d17a5cf..bcdef1e 100644 --- a/src/main/java/mate/academy/controller/OrderController.java +++ b/src/main/java/mate/academy/controller/OrderController.java @@ -47,7 +47,7 @@ public OrderDto placeOrder( @AuthenticationPrincipal User userDetails, @RequestBody @Parameter(description = "Details for placing an order", required = true) PlaceOrderRequestDto requestDto) { - return orderService.createOrder(userDetails.getId(), requestDto.getShippingAddress()); + return orderService.createOrder(userDetails.getId(), requestDto.shippingAddress()); } @PreAuthorize("hasRole('USER')") @@ -111,12 +111,12 @@ public OrderItemDto getOrderItem( @ApiResponse(responseCode = "401", description = "Unauthorized"), @ApiResponse(responseCode = "404", description = "Order not found") }) - public void updateOrderStatus( + public OrderDto updateOrderStatus( @PathVariable @Parameter(description = "ID of the order to update", required = true) Long orderId, @RequestBody @Parameter(description = "Details for updating the order status", required = true) UpdateOrderStatusRequestDto requestDto) { - orderService.updateOrderStatus(orderId, requestDto.getStatus()); + return orderService.updateOrderStatus(orderId, requestDto.status().name()); } } diff --git a/src/main/java/mate/academy/dto/order/OrderDto.java b/src/main/java/mate/academy/dto/order/OrderDto.java index f1f21bb..352d721 100644 --- a/src/main/java/mate/academy/dto/order/OrderDto.java +++ b/src/main/java/mate/academy/dto/order/OrderDto.java @@ -1,19 +1,16 @@ package mate.academy.dto.order; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Positive; import java.math.BigDecimal; import java.time.LocalDateTime; import java.util.Set; public record OrderDto( - @NotNull Long id, - @NotNull Long userId, - @NotBlank String status, - @NotNull @Positive BigDecimal total, - @NotNull LocalDateTime orderDate, - @NotBlank String shippingAddress, - @NotNull Set orderItems + Long id, + Long userId, + String status, + BigDecimal total, + LocalDateTime orderDate, + String shippingAddress, + Set orderItems ) { } diff --git a/src/main/java/mate/academy/dto/order/OrderItemDto.java b/src/main/java/mate/academy/dto/order/OrderItemDto.java index 9e36ef2..a21847d 100644 --- a/src/main/java/mate/academy/dto/order/OrderItemDto.java +++ b/src/main/java/mate/academy/dto/order/OrderItemDto.java @@ -1,12 +1,11 @@ package mate.academy.dto.order; -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Positive; import java.math.BigDecimal; -public record OrderItemDto(@NotNull Long id, - @NotNull Long orderId, - @NotNull Long bookId, - @Positive int quantity, - @Positive BigDecimal price) { +public record OrderItemDto( + Long id, + Long orderId, + Long bookId, + int quantity, + BigDecimal price) { } diff --git a/src/main/java/mate/academy/dto/order/PlaceOrderRequestDto.java b/src/main/java/mate/academy/dto/order/PlaceOrderRequestDto.java index ab759bb..77dc0d0 100644 --- a/src/main/java/mate/academy/dto/order/PlaceOrderRequestDto.java +++ b/src/main/java/mate/academy/dto/order/PlaceOrderRequestDto.java @@ -1,10 +1,8 @@ package mate.academy.dto.order; import jakarta.validation.constraints.NotBlank; -import lombok.Data; -@Data -public class PlaceOrderRequestDto { - @NotBlank - private String shippingAddress; +public record PlaceOrderRequestDto( + @NotBlank String shippingAddress +) { } diff --git a/src/main/java/mate/academy/dto/order/UpdateOrderStatusRequestDto.java b/src/main/java/mate/academy/dto/order/UpdateOrderStatusRequestDto.java index 33d00c6..3652fc0 100644 --- a/src/main/java/mate/academy/dto/order/UpdateOrderStatusRequestDto.java +++ b/src/main/java/mate/academy/dto/order/UpdateOrderStatusRequestDto.java @@ -1,11 +1,9 @@ package mate.academy.dto.order; import jakarta.validation.constraints.NotNull; -import lombok.Data; import mate.academy.model.Order; -@Data -public class UpdateOrderStatusRequestDto { - @NotNull - private Order.Status status; +public record UpdateOrderStatusRequestDto( + @NotNull Order.Status status +) { } diff --git a/src/main/java/mate/academy/exception/OrderProcessingException.java b/src/main/java/mate/academy/exception/OrderProcessingException.java new file mode 100644 index 0000000..454ae3c --- /dev/null +++ b/src/main/java/mate/academy/exception/OrderProcessingException.java @@ -0,0 +1,7 @@ +package mate.academy.exception; + +public class OrderProcessingException extends RuntimeException { + public OrderProcessingException(String message) { + super(message); + } +} diff --git a/src/main/java/mate/academy/model/Order.java b/src/main/java/mate/academy/model/Order.java index c95eb78..d5eac5a 100644 --- a/src/main/java/mate/academy/model/Order.java +++ b/src/main/java/mate/academy/model/Order.java @@ -39,11 +39,11 @@ public class Order { private User user; @Enumerated(EnumType.STRING) @Column(nullable = false) - private Status status; + private Status status = Status.PENDING; @Column(nullable = false) private BigDecimal total; @Column(nullable = false) - private LocalDateTime orderDate; + private LocalDateTime orderDate = LocalDateTime.now(); @Column(nullable = false) private String shippingAddress; @OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true) diff --git a/src/main/java/mate/academy/repository/shoppingcart/ShoppingCartRepository.java b/src/main/java/mate/academy/repository/shoppingcart/ShoppingCartRepository.java index c6966f4..9b7e760 100644 --- a/src/main/java/mate/academy/repository/shoppingcart/ShoppingCartRepository.java +++ b/src/main/java/mate/academy/repository/shoppingcart/ShoppingCartRepository.java @@ -1,5 +1,6 @@ package mate.academy.repository.shoppingcart; +import java.util.Optional; import mate.academy.model.ShoppingCart; import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.JpaRepository; @@ -8,5 +9,5 @@ @Repository public interface ShoppingCartRepository extends JpaRepository { @EntityGraph(attributePaths = {"cartItems", "cartItems.book"}) - ShoppingCart findByUserId(Long userId); + Optional findByUserId(Long userId); } diff --git a/src/main/java/mate/academy/service/OrderService.java b/src/main/java/mate/academy/service/OrderService.java index 8cc8569..1754881 100644 --- a/src/main/java/mate/academy/service/OrderService.java +++ b/src/main/java/mate/academy/service/OrderService.java @@ -3,7 +3,6 @@ import java.util.List; import mate.academy.dto.order.OrderDto; import mate.academy.dto.order.OrderItemDto; -import mate.academy.model.Order; import org.springframework.data.domain.Pageable; public interface OrderService { @@ -15,5 +14,5 @@ public interface OrderService { OrderItemDto getOrderItem(Long orderId, Long itemId); - void updateOrderStatus(Long orderId, Order.Status status); + OrderDto updateOrderStatus(Long orderId, String status); } diff --git a/src/main/java/mate/academy/service/impl/OrderServiceImpl.java b/src/main/java/mate/academy/service/impl/OrderServiceImpl.java index 2239dd9..c8e9208 100644 --- a/src/main/java/mate/academy/service/impl/OrderServiceImpl.java +++ b/src/main/java/mate/academy/service/impl/OrderServiceImpl.java @@ -1,13 +1,13 @@ package mate.academy.service.impl; import java.math.BigDecimal; -import java.time.LocalDateTime; import java.util.List; import java.util.Set; import lombok.RequiredArgsConstructor; import mate.academy.dto.order.OrderDto; import mate.academy.dto.order.OrderItemDto; import mate.academy.exception.EntityNotFoundException; +import mate.academy.exception.OrderProcessingException; import mate.academy.mapper.OrderItemMapper; import mate.academy.mapper.OrderMapper; import mate.academy.model.CartItem; @@ -52,7 +52,6 @@ public OrderDto createOrder(Long userId, String shippingAddress) { return orderMapper.toDto(order); } - @Transactional(readOnly = true) @Override public List getOrderHistory(Long userId, Pageable pageable) { return orderRepository.findByUserId(userId, pageable).stream() @@ -60,7 +59,6 @@ public List getOrderHistory(Long userId, Pageable pageable) { .toList(); } - @Transactional(readOnly = true) @Override public List getOrderItems(Long orderId, Pageable pageable) { return orderItemRepository.findByOrderId(orderId, pageable).stream() @@ -68,39 +66,38 @@ public List getOrderItems(Long orderId, Pageable pageable) { .toList(); } - @Transactional(readOnly = true) @Override public OrderItemDto getOrderItem(Long orderId, Long itemId) { OrderItem orderItem = orderItemRepository.findByOrderIdAndId(orderId, itemId) - .orElseThrow(() -> new EntityNotFoundException("Order item not found")); + .orElseThrow( + () -> new OrderProcessingException("Order item with id " + itemId + + " not found")); return orderItemMapper.toDto(orderItem); } - @Transactional @Override - public void updateOrderStatus(Long orderId, Order.Status status) { + public OrderDto updateOrderStatus(Long orderId, String status) { Order order = orderRepository.findById(orderId) - .orElseThrow(() -> new EntityNotFoundException("Order not found")); - order.setStatus(status); - orderRepository.save(order); + .orElseThrow( + () -> new OrderProcessingException("Order with id " + orderId + + " not found")); + + order.setStatus(Order.Status.valueOf(status)); + Order updatedOrder = orderRepository.save(order); + + return orderMapper.toDto(updatedOrder); } private Order makeOrder(String shippingAddress, User user) { Order order = new Order(); order.setUser(user); - order.setStatus(Order.Status.PENDING); - order.setOrderDate(LocalDateTime.now()); order.setShippingAddress(shippingAddress); - order.setDeleted(false); return order; } private ShoppingCart getShoppingCart(Long userId) { - ShoppingCart shoppingCart = shoppingCartRepository.findByUserId(userId); - if (shoppingCart == null || shoppingCart.getCartItems().isEmpty()) { - throw new EntityNotFoundException("Shopping cart is empty"); - } - return shoppingCart; + return shoppingCartRepository.findByUserId(userId) + .orElseThrow(() -> new OrderProcessingException("Shopping cart is empty")); } private User getUser(Long userId) { diff --git a/src/main/java/mate/academy/service/impl/ShoppingCartServiceImpl.java b/src/main/java/mate/academy/service/impl/ShoppingCartServiceImpl.java index 05b94a6..29cb295 100644 --- a/src/main/java/mate/academy/service/impl/ShoppingCartServiceImpl.java +++ b/src/main/java/mate/academy/service/impl/ShoppingCartServiceImpl.java @@ -28,12 +28,15 @@ public class ShoppingCartServiceImpl implements ShoppingCartService { private final UserRepository userRepository; public ShoppingCartDto getShoppingCartForUser(Long userId) { - return shoppingCartMapper.toDto(shoppingCartRepository.findByUserId(userId)); + return shoppingCartMapper.toDto(shoppingCartRepository.findByUserId(userId) + .orElseThrow(() -> new EntityNotFoundException("Cant get shopping cart for" + + " user with id " + userId))); } @Transactional public ShoppingCartDto addToCart(Long userId, Long bookId, int quantity) { - ShoppingCart shoppingCart = shoppingCartRepository.findByUserId(userId); + ShoppingCart shoppingCart = shoppingCartRepository.findByUserId(userId) + .orElseThrow(() -> new EntityNotFoundException("Cant add book with id " + bookId)); if (shoppingCart == null) { shoppingCart = createNewShoppingCart(userId); } @@ -57,10 +60,9 @@ public ShoppingCartDto addToCart(Long userId, Long bookId, int quantity) { @Transactional public ShoppingCartDto updateCartItemQuantity(Long cartItemId, Long userId, int quantity) { - ShoppingCart shoppingCart = shoppingCartRepository.findByUserId(userId); - if (shoppingCart == null) { - throw new EntityNotFoundException("Shopping cart not found for user id: " + userId); - } + ShoppingCart shoppingCart = shoppingCartRepository.findByUserId(userId) + .orElseThrow(() -> new EntityNotFoundException("Shopping cart not found for " + + "user id: " + userId)); CartItem cartItem = cartItemRepository.findByIdAndShoppingCartId(cartItemId, shoppingCart.getId()) @@ -74,10 +76,9 @@ public ShoppingCartDto updateCartItemQuantity(Long cartItemId, Long userId, int @Transactional public void removeCartItem(Long cartItemId, Long userId) { - ShoppingCart shoppingCart = shoppingCartRepository.findByUserId(userId); - if (shoppingCart == null) { - throw new EntityNotFoundException("Shopping cart not found for user id: " + userId); - } + ShoppingCart shoppingCart = shoppingCartRepository.findByUserId(userId) + .orElseThrow(() -> new EntityNotFoundException("Shopping cart not found " + + "for user id: " + userId)); CartItem cartItem = cartItemRepository.findByIdAndShoppingCartId(cartItemId, shoppingCart.getId())