package com.paymentlink.service; import com.paymentlink.model.entity.Product; import com.paymentlink.repository.ProductRepository; import com.paymentlink.reservation.ReservationService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.List; import java.util.Optional; @Service public class ProductService { private static final Logger logger = LoggerFactory.getLogger(ProductService.class); private final ProductRepository productRepository; private final ReservationService reservationService; private final NodeIdService nodeIdService; public ProductService(ProductRepository productRepository, ReservationService reservationService, NodeIdService nodeIdService) { this.productRepository = productRepository; this.reservationService = reservationService; this.nodeIdService = nodeIdService; } /** * Get all products with adjusted stock (minus reservations) */ public List getAllProducts() { List products = productRepository.findAll(); return adjustStockForReservations(products); } /** * Get products by category with adjusted stock */ public List getProductsByCategory(String category) { List products = productRepository.findByCategory(category); return adjustStockForReservations(products); } /** * Search products with adjusted stock */ public List searchProducts(String query, String category) { List products; if (category != null && !category.isEmpty()) { products = productRepository.searchProductsByCategory(query, category); } else { products = productRepository.searchProducts(query); } return adjustStockForReservations(products); } /** * Get products in stock (after reservations) */ public List getProductsInStock() { List products = productRepository.findAll(); products = adjustStockForReservations(products); return products.stream() .filter(p -> p.getStock() == null || p.getStock() > 0) .toList(); } /** * Get product by ID with adjusted stock */ public Optional getProductById(Long id) { Optional product = productRepository.findById(id); if (product.isPresent()) { Product p = product.get(); if (p.getStock() != null) { int reserved = reservationService.getReservedCount(p.getId()); p.setStock(Math.max(0, p.getStock() - reserved)); } } return product; } /** * Get product by ID without adjustment (for internal use) */ public Optional getProductByIdRaw(Long id) { return productRepository.findById(id); } /** * Get all categories */ public List getAllCategories() { return productRepository.findDistinctCategories(); } /** * Create a new product */ @Transactional public Product createProduct(Product product) { product.setNodeId(nodeIdService.getNodeId()); logger.info("Creating product: {}", product.getName()); return productRepository.save(product); } /** * Update a product */ @Transactional public Optional updateProduct(Long id, Product updates) { return productRepository.findById(id).map(existing -> { if (updates.getName() != null) existing.setName(updates.getName()); if (updates.getDescription() != null) existing.setDescription(updates.getDescription()); if (updates.getPrice() != null) existing.setPrice(updates.getPrice()); if (updates.getCurrency() != null) existing.setCurrency(updates.getCurrency()); if (updates.getImage() != null) existing.setImage(updates.getImage()); if (updates.getStock() != null) existing.setStock(updates.getStock()); if (updates.getCategory() != null) existing.setCategory(updates.getCategory()); if (updates.getTaxIncluded() != null) existing.setTaxIncluded(updates.getTaxIncluded()); logger.info("Updating product: {}", id); return productRepository.save(existing); }); } /** * Delete a product */ @Transactional public boolean deleteProduct(Long id) { if (productRepository.existsById(id)) { productRepository.deleteById(id); logger.info("Deleted product: {}", id); return true; } return false; } /** * Update stock for a product */ @Transactional public void updateStock(Long productId, int newStock) { productRepository.findById(productId).ifPresent(product -> { product.setStock(newStock); productRepository.save(product); logger.info("Updated stock for product {}: {}", productId, newStock); }); } /** * Decrease stock (for order completion) */ @Transactional public void decreaseStock(Long productId, int quantity) { productRepository.findById(productId).ifPresent(product -> { if (product.getStock() != null) { int newStock = product.getStock() - quantity; product.setStock(Math.max(0, newStock)); productRepository.save(product); logger.info("Decreased stock for product {}: {} -> {}", productId, product.getStock() + quantity, newStock); } }); } /** * Adjust stock counts for reservations */ private List adjustStockForReservations(List products) { return products.stream().map(p -> { if (p.getStock() != null) { int reserved = reservationService.getReservedCount(p.getId()); p.setStock(Math.max(0, p.getStock() - reserved)); } return p; }).toList(); } }