lock all stateful mutations
This commit is contained in:
@@ -33,6 +33,7 @@ import org.springframework.security.authentication.InternalAuthenticationService
|
|||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.dao.DataIntegrityViolationException;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
@@ -109,7 +110,14 @@ public class AuthController {
|
|||||||
user.setRole(User.Role.CUSTOMER);
|
user.setRole(User.Role.CUSTOMER);
|
||||||
user.setActive(true);
|
user.setActive(true);
|
||||||
|
|
||||||
User savedUser = userRepository.save(user);
|
User savedUser;
|
||||||
|
try {
|
||||||
|
savedUser = userRepository.save(user);
|
||||||
|
} catch (DataIntegrityViolationException e) {
|
||||||
|
Map<String, String> error = new HashMap<>();
|
||||||
|
error.put("message", "Username, email, or phone already exists");
|
||||||
|
return ResponseEntity.status(HttpStatus.CONFLICT).body(error);
|
||||||
|
}
|
||||||
|
|
||||||
emailService.sendWelcome(savedUser);
|
emailService.sendWelcome(savedUser);
|
||||||
|
|
||||||
@@ -183,9 +191,12 @@ public class AuthController {
|
|||||||
return ResponseEntity.ok(toUserInfoResponse(user));
|
return ResponseEntity.ok(toUserInfoResponse(user));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
@PutMapping("/me")
|
@PutMapping("/me")
|
||||||
public ResponseEntity<?> updateProfile(@Valid @RequestBody ProfileUpdateRequest request) {
|
public ResponseEntity<?> updateProfile(@Valid @RequestBody ProfileUpdateRequest request) {
|
||||||
User user = getAuthenticatedUser();
|
Long userId = AuthenticationHelper.getAuthenticatedUserId();
|
||||||
|
User user = userRepository.findByIdForUpdate(userId)
|
||||||
|
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
|
||||||
boolean invalidateToken = false;
|
boolean invalidateToken = false;
|
||||||
|
|
||||||
String username = trimToNull(request.getUsername());
|
String username = trimToNull(request.getUsername());
|
||||||
@@ -244,7 +255,14 @@ public class AuthController {
|
|||||||
user.setTokenVersion(user.getTokenVersion() + 1);
|
user.setTokenVersion(user.getTokenVersion() + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
User updatedUser = userRepository.save(user);
|
User updatedUser;
|
||||||
|
try {
|
||||||
|
updatedUser = userRepository.save(user);
|
||||||
|
} catch (DataIntegrityViolationException e) {
|
||||||
|
Map<String, String> error = new HashMap<>();
|
||||||
|
error.put("message", "Username, email, or phone already exists");
|
||||||
|
return ResponseEntity.status(HttpStatus.CONFLICT).body(error);
|
||||||
|
}
|
||||||
return ResponseEntity.ok(toUserInfoResponse(updatedUser));
|
return ResponseEntity.ok(toUserInfoResponse(updatedUser));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
package com.petshop.backend.repository;
|
package com.petshop.backend.repository;
|
||||||
|
|
||||||
import com.petshop.backend.entity.Cart;
|
import com.petshop.backend.entity.Cart;
|
||||||
|
import jakarta.persistence.LockModeType;
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.Lock;
|
||||||
import org.springframework.data.jpa.repository.Query;
|
import org.springframework.data.jpa.repository.Query;
|
||||||
import org.springframework.data.repository.query.Param;
|
import org.springframework.data.repository.query.Param;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
@@ -20,4 +22,8 @@ public interface CartRepository extends JpaRepository<Cart, Long> {
|
|||||||
Optional<Cart> findActiveCartByUserAndStore(@Param("userId") Long userId,
|
Optional<Cart> findActiveCartByUserAndStore(@Param("userId") Long userId,
|
||||||
@Param("storeId") Long storeId,
|
@Param("storeId") Long storeId,
|
||||||
@Param("status") String status);
|
@Param("status") String status);
|
||||||
|
|
||||||
|
@Lock(LockModeType.PESSIMISTIC_WRITE)
|
||||||
|
@Query("SELECT c FROM Cart c WHERE c.cartId = :id")
|
||||||
|
Optional<Cart> findByIdForUpdate(@Param("id") Long id);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
package com.petshop.backend.repository;
|
package com.petshop.backend.repository;
|
||||||
|
|
||||||
import com.petshop.backend.entity.Coupon;
|
import com.petshop.backend.entity.Coupon;
|
||||||
|
import jakarta.persistence.LockModeType;
|
||||||
import org.springframework.data.domain.Page;
|
import org.springframework.data.domain.Page;
|
||||||
import org.springframework.data.domain.Pageable;
|
import org.springframework.data.domain.Pageable;
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.Lock;
|
||||||
import org.springframework.data.jpa.repository.Query;
|
import org.springframework.data.jpa.repository.Query;
|
||||||
import org.springframework.data.repository.query.Param;
|
import org.springframework.data.repository.query.Param;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
@@ -16,6 +18,10 @@ public interface CouponRepository extends JpaRepository<Coupon, Long> {
|
|||||||
Optional<Coupon> findByCouponCode(String couponCode);
|
Optional<Coupon> findByCouponCode(String couponCode);
|
||||||
|
|
||||||
Optional<Coupon> findByCouponCodeIgnoreCase(String couponCode);
|
Optional<Coupon> findByCouponCodeIgnoreCase(String couponCode);
|
||||||
|
|
||||||
|
@Lock(LockModeType.PESSIMISTIC_WRITE)
|
||||||
|
@Query("SELECT c FROM Coupon c WHERE c.couponId = :id")
|
||||||
|
Optional<Coupon> findByIdForUpdate(@Param("id") Long id);
|
||||||
@Query("SELECT c FROM Coupon c WHERE " +
|
@Query("SELECT c FROM Coupon c WHERE " +
|
||||||
"(:q IS NULL OR LOWER(c.couponCode) LIKE LOWER(CONCAT('%', :q, '%'))) AND " +
|
"(:q IS NULL OR LOWER(c.couponCode) LIKE LOWER(CONCAT('%', :q, '%'))) AND " +
|
||||||
"(:active IS NULL OR c.active = :active)")
|
"(:active IS NULL OR c.active = :active)")
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
package com.petshop.backend.repository;
|
package com.petshop.backend.repository;
|
||||||
|
|
||||||
import com.petshop.backend.entity.Inventory;
|
import com.petshop.backend.entity.Inventory;
|
||||||
|
import jakarta.persistence.LockModeType;
|
||||||
import org.springframework.data.domain.Page;
|
import org.springframework.data.domain.Page;
|
||||||
import org.springframework.data.domain.Pageable;
|
import org.springframework.data.domain.Pageable;
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.Lock;
|
||||||
import org.springframework.data.jpa.repository.Query;
|
import org.springframework.data.jpa.repository.Query;
|
||||||
import org.springframework.data.repository.query.Param;
|
import org.springframework.data.repository.query.Param;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
@@ -19,6 +21,14 @@ public interface InventoryRepository extends JpaRepository<Inventory, Long> {
|
|||||||
@Query("SELECT i FROM Inventory i WHERE i.product.prodId = :productId AND i.store.storeId = :storeId")
|
@Query("SELECT i FROM Inventory i WHERE i.product.prodId = :productId AND i.store.storeId = :storeId")
|
||||||
Optional<Inventory> findByProductIdAndStoreId(@Param("productId") Long productId, @Param("storeId") Long storeId);
|
Optional<Inventory> findByProductIdAndStoreId(@Param("productId") Long productId, @Param("storeId") Long storeId);
|
||||||
|
|
||||||
|
@Lock(LockModeType.PESSIMISTIC_WRITE)
|
||||||
|
@Query("SELECT i FROM Inventory i WHERE i.product.prodId = :productId AND i.store.storeId = :storeId")
|
||||||
|
Optional<Inventory> findByProductIdAndStoreIdForUpdate(@Param("productId") Long productId, @Param("storeId") Long storeId);
|
||||||
|
|
||||||
|
@Lock(LockModeType.PESSIMISTIC_WRITE)
|
||||||
|
@Query("SELECT i FROM Inventory i WHERE i.inventoryId = :id")
|
||||||
|
Optional<Inventory> findByIdForUpdate(@Param("id") Long id);
|
||||||
|
|
||||||
@Query("SELECT i FROM Inventory i LEFT JOIN i.store s WHERE " +
|
@Query("SELECT i FROM Inventory i LEFT JOIN i.store s WHERE " +
|
||||||
"(:q IS NULL OR (" +
|
"(:q IS NULL OR (" +
|
||||||
"LOWER(i.product.prodName) LIKE LOWER(CONCAT('%', :q, '%')) OR " +
|
"LOWER(i.product.prodName) LIKE LOWER(CONCAT('%', :q, '%')) OR " +
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
package com.petshop.backend.repository;
|
package com.petshop.backend.repository;
|
||||||
|
|
||||||
import com.petshop.backend.entity.PasswordResetToken;
|
import com.petshop.backend.entity.PasswordResetToken;
|
||||||
|
import jakarta.persistence.LockModeType;
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.Lock;
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
import org.springframework.data.repository.query.Param;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
@@ -14,4 +18,8 @@ public interface PasswordResetTokenRepository extends JpaRepository<PasswordRese
|
|||||||
List<PasswordResetToken> findByUser_IdAndUsedAtIsNull(Long userId);
|
List<PasswordResetToken> findByUser_IdAndUsedAtIsNull(Long userId);
|
||||||
|
|
||||||
Optional<PasswordResetToken> findByTokenHashAndUsedAtIsNullAndExpiresAtAfter(String tokenHash, LocalDateTime now);
|
Optional<PasswordResetToken> findByTokenHashAndUsedAtIsNullAndExpiresAtAfter(String tokenHash, LocalDateTime now);
|
||||||
|
|
||||||
|
@Lock(LockModeType.PESSIMISTIC_WRITE)
|
||||||
|
@Query("SELECT t FROM PasswordResetToken t WHERE t.tokenHash = :hash AND t.usedAt IS NULL AND t.expiresAt > :now")
|
||||||
|
Optional<PasswordResetToken> findByTokenHashForUpdate(@Param("hash") String hash, @Param("now") LocalDateTime now);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
package com.petshop.backend.repository;
|
package com.petshop.backend.repository;
|
||||||
|
|
||||||
import com.petshop.backend.entity.Pet;
|
import com.petshop.backend.entity.Pet;
|
||||||
|
import jakarta.persistence.LockModeType;
|
||||||
import org.springframework.data.domain.Page;
|
import org.springframework.data.domain.Page;
|
||||||
import org.springframework.data.domain.Pageable;
|
import org.springframework.data.domain.Pageable;
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.Lock;
|
||||||
import org.springframework.data.jpa.repository.Query;
|
import org.springframework.data.jpa.repository.Query;
|
||||||
import org.springframework.data.repository.query.Param;
|
import org.springframework.data.repository.query.Param;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
@@ -37,6 +39,10 @@ public interface PetRepository extends JpaRepository<Pet, Long> {
|
|||||||
List<Pet> findAllByOwner_IdOrderByPetNameAsc(Long ownerId);
|
List<Pet> findAllByOwner_IdOrderByPetNameAsc(Long ownerId);
|
||||||
Optional<Pet> findByIdAndOwner_Id(Long id, Long ownerId);
|
Optional<Pet> findByIdAndOwner_Id(Long id, Long ownerId);
|
||||||
|
|
||||||
|
@Lock(LockModeType.PESSIMISTIC_WRITE)
|
||||||
|
@Query("SELECT p FROM Pet p WHERE p.petId = :id")
|
||||||
|
Optional<Pet> findByIdForUpdate(@Param("id") Long id);
|
||||||
|
|
||||||
@Query("SELECT p FROM Pet p WHERE " +
|
@Query("SELECT p FROM Pet p WHERE " +
|
||||||
"(:q IS NULL OR LOWER(p.petName) LIKE LOWER(CONCAT('%', :q, '%')) OR LOWER(p.petSpecies) LIKE LOWER(CONCAT('%', :q, '%')) OR LOWER(COALESCE(p.petBreed, '')) LIKE LOWER(CONCAT('%', :q, '%'))) AND " +
|
"(:q IS NULL OR LOWER(p.petName) LIKE LOWER(CONCAT('%', :q, '%')) OR LOWER(p.petSpecies) LIKE LOWER(CONCAT('%', :q, '%')) OR LOWER(COALESCE(p.petBreed, '')) LIKE LOWER(CONCAT('%', :q, '%'))) AND " +
|
||||||
"(:species IS NULL OR LOWER(p.petSpecies) = LOWER(:species)) AND " +
|
"(:species IS NULL OR LOWER(p.petSpecies) = LOWER(:species)) AND " +
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
package com.petshop.backend.repository;
|
package com.petshop.backend.repository;
|
||||||
|
|
||||||
import com.petshop.backend.entity.User;
|
import com.petshop.backend.entity.User;
|
||||||
|
import jakarta.persistence.LockModeType;
|
||||||
import org.springframework.data.domain.Page;
|
import org.springframework.data.domain.Page;
|
||||||
import org.springframework.data.domain.Pageable;
|
import org.springframework.data.domain.Pageable;
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.Lock;
|
||||||
import org.springframework.data.jpa.repository.Query;
|
import org.springframework.data.jpa.repository.Query;
|
||||||
import org.springframework.data.repository.query.Param;
|
import org.springframework.data.repository.query.Param;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
@@ -32,6 +34,10 @@ public interface UserRepository extends JpaRepository<User, Long> {
|
|||||||
"LOWER(COALESCE(u.phone, '')) LIKE LOWER(CONCAT('%', :q, '%'))")
|
"LOWER(COALESCE(u.phone, '')) LIKE LOWER(CONCAT('%', :q, '%'))")
|
||||||
Page<User> searchUsers(@Param("q") String query, Pageable pageable);
|
Page<User> searchUsers(@Param("q") String query, Pageable pageable);
|
||||||
|
|
||||||
|
@Lock(LockModeType.PESSIMISTIC_WRITE)
|
||||||
|
@Query("SELECT u FROM User u WHERE u.id = :id")
|
||||||
|
Optional<User> findByIdForUpdate(@Param("id") Long id);
|
||||||
|
|
||||||
@Query("SELECT u FROM User u WHERE u.role = :role AND (" +
|
@Query("SELECT u FROM User u WHERE u.role = :role AND (" +
|
||||||
"LOWER(COALESCE(u.username, '')) LIKE LOWER(CONCAT('%', :q, '%')) OR " +
|
"LOWER(COALESCE(u.username, '')) LIKE LOWER(CONCAT('%', :q, '%')) OR " +
|
||||||
"LOWER(COALESCE(u.firstName, '')) LIKE LOWER(CONCAT('%', :q, '%')) OR " +
|
"LOWER(COALESCE(u.firstName, '')) LIKE LOWER(CONCAT('%', :q, '%')) OR " +
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ public class AdoptionService {
|
|||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public AdoptionResponse createAdoption(AdoptionRequest request) {
|
public AdoptionResponse createAdoption(AdoptionRequest request) {
|
||||||
Pet pet = petRepository.findById(request.getPetId())
|
Pet pet = petRepository.findByIdForUpdate(request.getPetId())
|
||||||
.orElseThrow(() -> new ResourceNotFoundException("Pet not found with id: " + request.getPetId()));
|
.orElseThrow(() -> new ResourceNotFoundException("Pet not found with id: " + request.getPetId()));
|
||||||
|
|
||||||
User customer = userRepository.findById(request.getCustomerId())
|
User customer = userRepository.findById(request.getCustomerId())
|
||||||
@@ -120,7 +120,7 @@ public class AdoptionService {
|
|||||||
Adoption adoption = adoptionRepository.findById(id)
|
Adoption adoption = adoptionRepository.findById(id)
|
||||||
.orElseThrow(() -> new ResourceNotFoundException("Adoption not found with id: " + id));
|
.orElseThrow(() -> new ResourceNotFoundException("Adoption not found with id: " + id));
|
||||||
|
|
||||||
Pet pet = petRepository.findById(request.getPetId())
|
Pet pet = petRepository.findByIdForUpdate(request.getPetId())
|
||||||
.orElseThrow(() -> new ResourceNotFoundException("Pet not found with id: " + request.getPetId()));
|
.orElseThrow(() -> new ResourceNotFoundException("Pet not found with id: " + request.getPetId()));
|
||||||
|
|
||||||
User customer = userRepository.findById(request.getCustomerId())
|
User customer = userRepository.findById(request.getCustomerId())
|
||||||
@@ -160,7 +160,7 @@ public class AdoptionService {
|
|||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public AdoptionResponse requestAdoption(Long customerId, Long petId, Long employeeId, Long sourceStoreId, LocalDate adoptionDate) {
|
public AdoptionResponse requestAdoption(Long customerId, Long petId, Long employeeId, Long sourceStoreId, LocalDate adoptionDate) {
|
||||||
Pet pet = petRepository.findById(petId)
|
Pet pet = petRepository.findByIdForUpdate(petId)
|
||||||
.orElseThrow(() -> new ResourceNotFoundException("Pet not found"));
|
.orElseThrow(() -> new ResourceNotFoundException("Pet not found"));
|
||||||
|
|
||||||
// Verify the pet is actually located at the claimed store
|
// Verify the pet is actually located at the claimed store
|
||||||
|
|||||||
@@ -362,7 +362,7 @@ public class CartService {
|
|||||||
throw new BusinessException("Unauthorized");
|
throw new BusinessException("Unauthorized");
|
||||||
}
|
}
|
||||||
|
|
||||||
Cart cart = cartRepository.findById(cartId)
|
Cart cart = cartRepository.findByIdForUpdate(cartId)
|
||||||
.orElseThrow(() -> new BusinessException("Cart not found"));
|
.orElseThrow(() -> new BusinessException("Cart not found"));
|
||||||
|
|
||||||
if (!cart.getUser().getId().equals(userId)) {
|
if (!cart.getUser().getId().equals(userId)) {
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ public class InventoryService {
|
|||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public InventoryResponse updateInventory(Long id, InventoryRequest request) {
|
public InventoryResponse updateInventory(Long id, InventoryRequest request) {
|
||||||
Inventory inventory = inventoryRepository.findById(id)
|
Inventory inventory = inventoryRepository.findByIdForUpdate(id)
|
||||||
.orElseThrow(() -> new ResourceNotFoundException("Inventory not found with id: " + id));
|
.orElseThrow(() -> new ResourceNotFoundException("Inventory not found with id: " + id));
|
||||||
|
|
||||||
Product product = productRepository.findById(request.getProdId())
|
Product product = productRepository.findById(request.getProdId())
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ public class PasswordResetService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
PasswordResetToken token = passwordResetTokenRepository
|
PasswordResetToken token = passwordResetTokenRepository
|
||||||
.findByTokenHashAndUsedAtIsNullAndExpiresAtAfter(hashToken(normalizedToken), LocalDateTime.now())
|
.findByTokenHashForUpdate(hashToken(normalizedToken), LocalDateTime.now())
|
||||||
.orElseThrow(() -> new BusinessException("Reset token is invalid or has expired"));
|
.orElseThrow(() -> new BusinessException("Reset token is invalid or has expired"));
|
||||||
|
|
||||||
User user = token.getUser();
|
User user = token.getUser();
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ public class SaleService {
|
|||||||
sale.setChannel(request.getChannel() != null ? request.getChannel() : "IN_STORE");
|
sale.setChannel(request.getChannel() != null ? request.getChannel() : "IN_STORE");
|
||||||
|
|
||||||
if (request.getCouponId() != null) {
|
if (request.getCouponId() != null) {
|
||||||
Coupon coupon = couponRepository.findById(request.getCouponId())
|
Coupon coupon = couponRepository.findByIdForUpdate(request.getCouponId())
|
||||||
.orElseThrow(() -> new ResourceNotFoundException("Coupon not found with id: " + request.getCouponId()));
|
.orElseThrow(() -> new ResourceNotFoundException("Coupon not found with id: " + request.getCouponId()));
|
||||||
validateCoupon(coupon);
|
validateCoupon(coupon);
|
||||||
sale.setCoupon(coupon);
|
sale.setCoupon(coupon);
|
||||||
@@ -96,7 +96,7 @@ public class SaleService {
|
|||||||
|
|
||||||
User customer = null;
|
User customer = null;
|
||||||
if (request.getCustomerId() != null) {
|
if (request.getCustomerId() != null) {
|
||||||
customer = userRepository.findById(request.getCustomerId())
|
customer = userRepository.findByIdForUpdate(request.getCustomerId())
|
||||||
.orElseThrow(() -> new ResourceNotFoundException("Customer not found with id: " + request.getCustomerId()));
|
.orElseThrow(() -> new ResourceNotFoundException("Customer not found with id: " + request.getCustomerId()));
|
||||||
sale.setCustomer(customer);
|
sale.setCustomer(customer);
|
||||||
}
|
}
|
||||||
@@ -144,7 +144,7 @@ public class SaleService {
|
|||||||
" for product: " + product.getProdName());
|
" for product: " + product.getProdName());
|
||||||
}
|
}
|
||||||
|
|
||||||
Inventory inventory = inventoryRepository.findByProductIdAndStoreId(itemRequest.getProdId(), store.getStoreId())
|
Inventory inventory = inventoryRepository.findByProductIdAndStoreIdForUpdate(itemRequest.getProdId(), store.getStoreId())
|
||||||
.orElseThrow(() -> new ResourceNotFoundException("Inventory not found for product " + itemRequest.getProdId() + " at store " + store.getStoreId()));
|
.orElseThrow(() -> new ResourceNotFoundException("Inventory not found for product " + itemRequest.getProdId() + " at store " + store.getStoreId()));
|
||||||
|
|
||||||
inventory.setQuantity(inventory.getQuantity() + itemRequest.getQuantity());
|
inventory.setQuantity(inventory.getQuantity() + itemRequest.getQuantity());
|
||||||
@@ -208,7 +208,7 @@ public class SaleService {
|
|||||||
Product product = productRepository.findById(itemRequest.getProdId())
|
Product product = productRepository.findById(itemRequest.getProdId())
|
||||||
.orElseThrow(() -> new ResourceNotFoundException("Product not found with id: " + itemRequest.getProdId()));
|
.orElseThrow(() -> new ResourceNotFoundException("Product not found with id: " + itemRequest.getProdId()));
|
||||||
|
|
||||||
Inventory inventory = inventoryRepository.findByProductIdAndStoreId(itemRequest.getProdId(), store.getStoreId())
|
Inventory inventory = inventoryRepository.findByProductIdAndStoreIdForUpdate(itemRequest.getProdId(), store.getStoreId())
|
||||||
.orElseThrow(() -> new ResourceNotFoundException("Inventory not found for product " + itemRequest.getProdId() + " at store " + store.getStoreId()));
|
.orElseThrow(() -> new ResourceNotFoundException("Inventory not found for product " + itemRequest.getProdId() + " at store " + store.getStoreId()));
|
||||||
|
|
||||||
if (inventory.getQuantity() < itemRequest.getQuantity()) {
|
if (inventory.getQuantity() < itemRequest.getQuantity()) {
|
||||||
@@ -289,6 +289,12 @@ public class SaleService {
|
|||||||
if (coupon.getEndsAt() != null && now.isAfter(coupon.getEndsAt())) {
|
if (coupon.getEndsAt() != null && now.isAfter(coupon.getEndsAt())) {
|
||||||
throw new BusinessException("Coupon has expired");
|
throw new BusinessException("Coupon has expired");
|
||||||
}
|
}
|
||||||
|
if (coupon.getUsageLimit() != null) {
|
||||||
|
long used = saleRepository.countByCoupon_CouponId(coupon.getCouponId());
|
||||||
|
if (used >= coupon.getUsageLimit()) {
|
||||||
|
throw new BusinessException("Coupon usage limit has been reached");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private BigDecimal calculateCouponDiscount(Coupon coupon, BigDecimal subtotal) {
|
private BigDecimal calculateCouponDiscount(Coupon coupon, BigDecimal subtotal) {
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ public class UserService {
|
|||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public UserResponse updateUser(Long id, UserRequest request, User.Role requiredRole) {
|
public UserResponse updateUser(Long id, UserRequest request, User.Role requiredRole) {
|
||||||
User user = userRepository.findById(id)
|
User user = userRepository.findByIdForUpdate(id)
|
||||||
.orElseThrow(() -> new ResourceNotFoundException("User not found with id: " + id));
|
.orElseThrow(() -> new ResourceNotFoundException("User not found with id: " + id));
|
||||||
|
|
||||||
requireRoleOrNotFound(user, requiredRole, id);
|
requireRoleOrNotFound(user, requiredRole, id);
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE users ADD CONSTRAINT uq_users_phone UNIQUE (phone);
|
||||||
Reference in New Issue
Block a user