OrderService.java

package com.learning.yasminishop.order;

import com.learning.yasminishop.cart.CartItemRepository;
import com.learning.yasminishop.common.dto.PaginationResponse;
import com.learning.yasminishop.common.entity.*;
import com.learning.yasminishop.common.enumeration.EOrderStatus;
import com.learning.yasminishop.common.exception.AppException;
import com.learning.yasminishop.common.exception.ErrorCode;
import com.learning.yasminishop.notification.NotificationService;
import com.learning.yasminishop.order.dto.filter.OrderFilter;
import com.learning.yasminishop.order.dto.request.OrderRequest;
import com.learning.yasminishop.order.dto.response.OrderAdminResponse;
import com.learning.yasminishop.order.dto.response.OrderResponse;
import com.learning.yasminishop.order.mapper.OrderMapper;
import com.learning.yasminishop.user.UserRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
@Slf4j
public class OrderService {
    private final OrderRepository orderRepository;
    private final UserRepository userRepository;
    private final CartItemRepository cartItemRepository;

    private final NotificationService notificationService;

    private final OrderMapper orderMapper;


    @Transactional
    public OrderResponse create(OrderRequest orderRequest) {

        // get the user
        String email = SecurityContextHolder.getContext().getAuthentication().getName();
        User user = userRepository.findByEmail(email).orElseThrow(() -> new AppException(ErrorCode.USER_NOT_FOUND));

        // get the cart items of the user
        List<CartItem> cartItems = cartItemRepository.findAllByUserAndProductIsAvailable(user, true);
        Set<String> cartItemRequestIds = orderRequest.getCartItemIds();

        // Extract IDs from cartItems
        Set<String> cartItemIds = cartItems.stream()
                .map(CartItem::getId)
                .collect(Collectors.toSet());

        // Check if all cartItemRequestIds are in cartItemIds
        for (String requestId : cartItemRequestIds) {
            if (!cartItemIds.contains(requestId)) {
                throw new AppException(ErrorCode.CART_ITEM_NOT_FOUND);
            }
        }

        List<CartItem> cartItemsToOrder = cartItemRepository.findAllById(cartItemRequestIds);

        // create the order
        Order order = createAOrder(orderRequest, user, cartItemsToOrder);

        return orderMapper.toOrderResponse(orderRepository.save(order));
    }


    public List<OrderResponse> getAllOrderByUser() {
        String email = SecurityContextHolder.getContext().getAuthentication().getName();
        User user = userRepository.findByEmail(email).orElseThrow(() -> new AppException(ErrorCode.USER_NOT_FOUND));

        return orderRepository.findAllByUserOrderByLastModifiedDateDesc(user)
                .stream()
                .map(orderMapper::toOrderResponse)
                .toList();
    }

    public PaginationResponse<OrderAdminResponse> getAllOrders(OrderFilter orderFilter, Pageable pageable) {

        Page<Order> orders = orderRepository.findAll(
                Specification.where(OrderSpecifications.hasStatus(orderFilter.getStatus()))
                , pageable);

        return PaginationResponse.<OrderAdminResponse>builder()
                .page(pageable.getPageNumber() + 1)
                .total(orders.getTotalElements())
                .itemsPerPage(pageable.getPageSize())
                .data(orders.map(orderMapper::toOrderAdminResponse).toList())
                .build();
    }

    @PreAuthorize("hasRole('USER')")
    public OrderResponse getOrderById(String id) {
        String email = SecurityContextHolder.getContext().getAuthentication().getName();
        User user = userRepository.findByEmail(email).orElseThrow(() -> new AppException(ErrorCode.USER_NOT_FOUND));

        Order order = orderRepository.findByIdAndUser(id, user).orElseThrow(
                () -> new AppException(ErrorCode.ORDER_NOT_FOUND)
        );

        return orderMapper.toOrderResponse(order);
    }

    public OrderAdminResponse getOrderByIdForAdmin(String id) {
        Order order = orderRepository.findById(id).orElseThrow(
                () -> new AppException(ErrorCode.ORDER_NOT_FOUND)
        );
        return orderMapper.toOrderAdminResponse(order);
    }

    @Transactional
    public void updateOrderStatus(String id, String status) {
        Order order = orderRepository.findById(id).orElseThrow(
                () -> new AppException(ErrorCode.ORDER_NOT_FOUND)
        );

        order.setStatus(EOrderStatus.valueOf(status));
        orderRepository.save(order);

        sendNotification(order);
    }

    private Order createAOrder(OrderRequest orderRequest, User user, List<CartItem> cartItemsToOrder) {
        Order order = orderMapper.toOrder(orderRequest);
        order.setUser(user);
        order = orderRepository.save(order);

        Order finalOrder = order;
        Set<OrderItem> orderItems = cartItemsToOrder
                .stream()
                .map(item -> {
                    OrderItem orderItem = orderMapper.toOrderItem(item);
                    orderItem.setOrder(finalOrder);
                    return orderItem;
                })
                .collect(Collectors.toSet());

        BigDecimal totalPrice = orderItems.stream()
                .map(OrderItem::getPrice)
                .reduce(BigDecimal.ZERO, BigDecimal::add);

        Integer totalQuantity = orderItems.stream()
                .map(OrderItem::getQuantity)
                .reduce(0, Integer::sum);

        order.setOrderItems(orderItems);
        order.setTotalPrice(totalPrice);
        order.setTotalQuantity(totalQuantity);
        order.setStatus(EOrderStatus.PENDING);

        // delete the cart items
        cartItemRepository.deleteAll(cartItemsToOrder);

        return order;
    }

    private void sendNotification(Order order) {
        OrderItem orderItem = order.getOrderItems().stream().findFirst().orElseThrow(() -> new AppException(ErrorCode.ORDER_NOT_FOUND));
        Notification notification = Notification.builder()
                .user(order.getUser())
                .title("Your order status has been updated")
                .content("Your order status has been updated to " + order.getStatus())
                .isRead(false)
                .thumbnail(orderItem.getProduct().getThumbnail())
                .link("/order/" + order.getId())
                .build();

        notificationService.createNotification(notification);
    }

}