Merge branch 'main' into AttachmentsToChat

This commit is contained in:
Alex
2026-04-14 00:57:16 -06:00
333 changed files with 1524 additions and 369 deletions

1
.gitignore vendored
View File

@@ -2,5 +2,4 @@
.local/
commit-patches/
temp_photos/
uploads/
.env

5
backend/.gitignore vendored
View File

@@ -46,7 +46,10 @@ build/
### Project Specific ###
src/test/
tmp/
uploads/
uploads/*
!uploads/avatars/
!uploads/pets/
!uploads/products/
### Temp and backup files ###
*.backup

View File

@@ -9,5 +9,6 @@ RUN mvn -q -DskipTests package
FROM eclipse-temurin:25-jre
WORKDIR /app
COPY --from=build /app/target/*.jar app.jar
COPY uploads ./uploads
EXPOSE 8080
ENTRYPOINT ["java","-jar","app.jar"]

View File

@@ -151,6 +151,12 @@
<configuration>
<mainClass>com.petshop.backend.DevStackApplication</mainClass>
<classpathScope>runtime</classpathScope>
<systemProperties>
<systemProperty>
<key>UPLOAD_BASE_DIR</key>
<value>${project.basedir}/uploads</value>
</systemProperty>
</systemProperties>
</configuration>
</execution>
<execution>

View File

@@ -1,11 +1,15 @@
package com.petshop.backend.controller;
import com.petshop.backend.dto.auth.AvatarUploadResponse;
import com.petshop.backend.dto.auth.ForgotPasswordRequest;
import com.petshop.backend.dto.auth.ForgotPasswordResponse;
import com.petshop.backend.dto.auth.LoginRequest;
import com.petshop.backend.dto.auth.LoginResponse;
import com.petshop.backend.dto.auth.ProfileUpdateRequest;
import com.petshop.backend.dto.auth.RegisterRequest;
import com.petshop.backend.dto.auth.RegisterResponse;
import com.petshop.backend.dto.auth.ResetPasswordRequest;
import com.petshop.backend.dto.auth.ResetPasswordResponse;
import com.petshop.backend.dto.auth.UserInfoResponse;
import com.petshop.backend.entity.StoreLocation;
import com.petshop.backend.entity.User;
@@ -13,6 +17,7 @@ import com.petshop.backend.repository.UserRepository;
import com.petshop.backend.security.JwtUtil;
import com.petshop.backend.service.ActivityLogService;
import com.petshop.backend.service.AvatarStorageService;
import com.petshop.backend.service.PasswordResetService;
import com.petshop.backend.util.AuthenticationHelper;
import com.petshop.backend.util.PhoneUtils;
import jakarta.validation.Valid;
@@ -49,14 +54,16 @@ public class AuthController {
private final PasswordEncoder passwordEncoder;
private final AvatarStorageService avatarStorageService;
private final ActivityLogService activityLogService;
private final PasswordResetService passwordResetService;
public AuthController(AuthenticationManager authenticationManager, UserRepository userRepository, JwtUtil jwtUtil, PasswordEncoder passwordEncoder, AvatarStorageService avatarStorageService, ActivityLogService activityLogService) {
public AuthController(AuthenticationManager authenticationManager, UserRepository userRepository, JwtUtil jwtUtil, PasswordEncoder passwordEncoder, AvatarStorageService avatarStorageService, ActivityLogService activityLogService, PasswordResetService passwordResetService) {
this.authenticationManager = authenticationManager;
this.userRepository = userRepository;
this.jwtUtil = jwtUtil;
this.passwordEncoder = passwordEncoder;
this.avatarStorageService = avatarStorageService;
this.activityLogService = activityLogService;
this.passwordResetService = passwordResetService;
}
@PostMapping("/register")
@@ -153,6 +160,16 @@ public class AuthController {
}
}
@PostMapping("/forgot-password")
public ResponseEntity<ForgotPasswordResponse> forgotPassword(@Valid @RequestBody ForgotPasswordRequest request) {
return ResponseEntity.ok(passwordResetService.createResetToken(request.getUsernameOrEmail()));
}
@PostMapping("/reset-password")
public ResponseEntity<ResetPasswordResponse> resetPassword(@Valid @RequestBody ResetPasswordRequest request) {
return ResponseEntity.ok(passwordResetService.resetPassword(request.getToken(), request.getNewPassword()));
}
@Transactional(readOnly = true)
@GetMapping("/me")
public ResponseEntity<UserInfoResponse> getCurrentUser() {

View File

@@ -0,0 +1,17 @@
package com.petshop.backend.dto.auth;
import jakarta.validation.constraints.NotBlank;
public class ForgotPasswordRequest {
@NotBlank(message = "Username or email is required")
private String usernameOrEmail;
public String getUsernameOrEmail() {
return usernameOrEmail;
}
public void setUsernameOrEmail(String usernameOrEmail) {
this.usernameOrEmail = usernameOrEmail;
}
}

View File

@@ -0,0 +1,20 @@
package com.petshop.backend.dto.auth;
public class ForgotPasswordResponse {
private final String message;
private final String resetToken;
public ForgotPasswordResponse(String message, String resetToken) {
this.message = message;
this.resetToken = resetToken;
}
public String getMessage() {
return message;
}
public String getResetToken() {
return resetToken;
}
}

View File

@@ -0,0 +1,30 @@
package com.petshop.backend.dto.auth;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
public class ResetPasswordRequest {
@NotBlank(message = "Reset token is required")
private String token;
@NotBlank(message = "Password is required")
@Size(min = 6, message = "Password must be at least 6 characters")
private String newPassword;
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public String getNewPassword() {
return newPassword;
}
public void setNewPassword(String newPassword) {
this.newPassword = newPassword;
}
}

View File

@@ -0,0 +1,14 @@
package com.petshop.backend.dto.auth;
public class ResetPasswordResponse {
private final String message;
public ResetPasswordResponse(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
}

View File

@@ -11,8 +11,11 @@ public class CartResponse {
private List<CartItemResponse> items;
private BigDecimal subtotalAmount;
private BigDecimal discountAmount;
private BigDecimal pointsDiscountAmount;
private BigDecimal totalAmount;
private String couponCode;
private Boolean pointsApplied;
private Integer availableLoyaltyPoints;
public CartResponse() {
}
@@ -40,4 +43,14 @@ public class CartResponse {
public String getCouponCode() { return couponCode; }
public void setCouponCode(String couponCode) { this.couponCode = couponCode; }
public BigDecimal getPointsDiscountAmount() { return pointsDiscountAmount; }
public void setPointsDiscountAmount(BigDecimal pointsDiscountAmount) { this.pointsDiscountAmount = pointsDiscountAmount; }
public Boolean getPointsApplied() { return pointsApplied; }
public void setPointsApplied(Boolean pointsApplied) { this.pointsApplied = pointsApplied; }
public Integer getAvailableLoyaltyPoints() { return availableLoyaltyPoints; }
public void setAvailableLoyaltyPoints(Integer availableLoyaltyPoints) { this.availableLoyaltyPoints = availableLoyaltyPoints; }
}

View File

@@ -5,7 +5,6 @@ import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import java.util.List;
import java.util.Objects;
import java.math.BigDecimal;
public class SaleRequest {
@NotNull(message = "Store ID is required")
@@ -29,9 +28,6 @@ public class SaleRequest {
private Long cartId;
private Integer pointsUsed;
private BigDecimal pointsDiscountAmount;
public Long getStoreId() {
return storeId;
@@ -105,21 +101,6 @@ public class SaleRequest {
this.cartId = cartId;
}
public Integer getPointsUsed() {
return pointsUsed;
}
public void setPointsUsed(Integer pointsUsed) {
this.pointsUsed = pointsUsed;
}
public BigDecimal getPointsDiscountAmount() {
return pointsDiscountAmount;
}
public void setPointsDiscountAmount(BigDecimal pointsDiscountAmount) {
this.pointsDiscountAmount = pointsDiscountAmount;
}
@Override
public boolean equals(Object o) {

View File

@@ -18,9 +18,8 @@ public class SaleResponse {
private BigDecimal subtotalAmount;
private BigDecimal couponDiscountAmount;
private BigDecimal employeeDiscountAmount;
private BigDecimal loyaltyDiscountAmount;
private Integer pointsEarned;
private Integer pointsUsed;
private BigDecimal pointsDiscountAmount;
private String channel;
private Long couponId;
private Long cartId;
@@ -129,6 +128,15 @@ public class SaleResponse {
this.employeeDiscountAmount = employeeDiscountAmount;
}
public BigDecimal getLoyaltyDiscountAmount() {
return loyaltyDiscountAmount;
}
public void setLoyaltyDiscountAmount(BigDecimal loyaltyDiscountAmount) {
this.loyaltyDiscountAmount = loyaltyDiscountAmount;
}
public Integer getPointsEarned() {
return pointsEarned;
}
@@ -137,22 +145,6 @@ public class SaleResponse {
this.pointsEarned = pointsEarned;
}
public Integer getPointsUsed() {
return pointsUsed;
}
public void setPointsUsed(Integer pointsUsed) {
this.pointsUsed = pointsUsed;
}
public BigDecimal getPointsDiscountAmount() {
return pointsDiscountAmount;
}
public void setPointsDiscountAmount(BigDecimal pointsDiscountAmount) {
this.pointsDiscountAmount = pointsDiscountAmount;
}
public String getChannel() {
return channel;
}

View File

@@ -1,11 +1,13 @@
package com.petshop.backend.entity;
import jakarta.persistence.*;
import org.hibernate.annotations.Immutable;
import java.time.LocalDateTime;
import java.util.Objects;
@Entity
@Immutable
@Table(name = "activityLog")
public class ActivityLog {

View File

@@ -40,6 +40,24 @@ public class Cart {
@Column(nullable = false, precision = 10, scale = 2)
private BigDecimal totalAmount = BigDecimal.ZERO;
@Column(nullable = false)
private Boolean pointsApplied = false;
@Column(nullable = false, precision = 10, scale = 2)
private BigDecimal pointsDiscountAmount = BigDecimal.ZERO;
@Column(nullable = false)
private Boolean checkoutPending = false;
@Column(precision = 10, scale = 2)
private BigDecimal checkoutAmount;
@Column
private LocalDateTime checkoutStartedAt;
@Column(length = 255)
private String checkoutPaymentIntentId;
@CreationTimestamp
@Column(name = "created_at", updatable = false)
private LocalDateTime createdAt;
@@ -115,6 +133,54 @@ public class Cart {
this.totalAmount = totalAmount;
}
public Boolean getPointsApplied() {
return pointsApplied;
}
public void setPointsApplied(Boolean pointsApplied) {
this.pointsApplied = pointsApplied;
}
public BigDecimal getPointsDiscountAmount() {
return pointsDiscountAmount;
}
public void setPointsDiscountAmount(BigDecimal pointsDiscountAmount) {
this.pointsDiscountAmount = pointsDiscountAmount;
}
public Boolean getCheckoutPending() {
return checkoutPending;
}
public void setCheckoutPending(Boolean checkoutPending) {
this.checkoutPending = checkoutPending;
}
public BigDecimal getCheckoutAmount() {
return checkoutAmount;
}
public void setCheckoutAmount(BigDecimal checkoutAmount) {
this.checkoutAmount = checkoutAmount;
}
public LocalDateTime getCheckoutStartedAt() {
return checkoutStartedAt;
}
public void setCheckoutStartedAt(LocalDateTime checkoutStartedAt) {
this.checkoutStartedAt = checkoutStartedAt;
}
public String getCheckoutPaymentIntentId() {
return checkoutPaymentIntentId;
}
public void setCheckoutPaymentIntentId(String checkoutPaymentIntentId) {
this.checkoutPaymentIntentId = checkoutPaymentIntentId;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}

View File

@@ -0,0 +1,102 @@
package com.petshop.backend.entity;
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 org.hibernate.annotations.CreationTimestamp;
import java.time.LocalDateTime;
import java.util.Objects;
@Entity
@Table(name = "passwordResetToken")
public class PasswordResetToken {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "userId", nullable = false)
private User user;
@Column(nullable = false, unique = true, length = 64)
private String tokenHash;
@Column(nullable = false)
private LocalDateTime expiresAt;
@Column
private LocalDateTime usedAt;
@CreationTimestamp
@Column(name = "created_at", updatable = false)
private LocalDateTime createdAt;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public String getTokenHash() {
return tokenHash;
}
public void setTokenHash(String tokenHash) {
this.tokenHash = tokenHash;
}
public LocalDateTime getExpiresAt() {
return expiresAt;
}
public void setExpiresAt(LocalDateTime expiresAt) {
this.expiresAt = expiresAt;
}
public LocalDateTime getUsedAt() {
return usedAt;
}
public void setUsedAt(LocalDateTime usedAt) {
this.usedAt = usedAt;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PasswordResetToken that = (PasswordResetToken) o;
return Objects.equals(id, that.id);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
}

View File

@@ -66,14 +66,12 @@ public class Sale {
@Column(nullable = false, precision = 10, scale = 2)
private BigDecimal employeeDiscountAmount = BigDecimal.ZERO;
@Column(nullable = false)
private Integer pointsEarned = 0;
@Column(nullable = false)
private Integer pointsUsed = 0;
@Column(nullable = false, precision = 10, scale = 2)
private BigDecimal pointsDiscountAmount = BigDecimal.ZERO;
private BigDecimal loyaltyDiscountAmount = BigDecimal.ZERO;
@Column(nullable = false)
private Integer pointsEarned = 0;
@OneToMany(mappedBy = "sale", cascade = CascadeType.ALL)
private List<SaleItem> items = new ArrayList<>();
@@ -209,6 +207,15 @@ public class Sale {
this.employeeDiscountAmount = employeeDiscountAmount;
}
public BigDecimal getLoyaltyDiscountAmount() {
return loyaltyDiscountAmount;
}
public void setLoyaltyDiscountAmount(BigDecimal loyaltyDiscountAmount) {
this.loyaltyDiscountAmount = loyaltyDiscountAmount;
}
public Integer getPointsEarned() {
return pointsEarned;
}
@@ -217,22 +224,6 @@ public class Sale {
this.pointsEarned = pointsEarned;
}
public Integer getPointsUsed() {
return pointsUsed;
}
public void setPointsUsed(Integer pointsUsed) {
this.pointsUsed = pointsUsed;
}
public BigDecimal getPointsDiscountAmount() {
return pointsDiscountAmount;
}
public void setPointsDiscountAmount(BigDecimal pointsDiscountAmount) {
this.pointsDiscountAmount = pointsDiscountAmount;
}
public List<SaleItem> getItems() {
return items;
}

View File

@@ -0,0 +1,17 @@
package com.petshop.backend.repository;
import com.petshop.backend.entity.PasswordResetToken;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
@Repository
public interface PasswordResetTokenRepository extends JpaRepository<PasswordResetToken, Long> {
List<PasswordResetToken> findByUser_IdAndUsedAtIsNull(Long userId);
Optional<PasswordResetToken> findByTokenHashAndUsedAtIsNullAndExpiresAtAfter(String tokenHash, LocalDateTime now);
}

View File

@@ -9,6 +9,7 @@ import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
@Repository
public interface SaleRepository extends JpaRepository<Sale, Long> {
@@ -25,4 +26,6 @@ public interface SaleRepository extends JpaRepository<Sale, Long> {
Page<Sale> searchSales(@Param("q") String query, @Param("paymentMethod") String paymentMethod, @Param("storeId") Long storeId, @Param("isRefund") Boolean isRefund, Pageable pageable);
List<Sale> findByOriginalSaleSaleId(Long originalSaleId);
Optional<Sale> findByCartCartId(Long cartId);
}

View File

@@ -50,7 +50,12 @@ public class SecurityConfig {
http.cors(cors -> cors.configurationSource(corsConfigurationSource()))
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/v1/auth/login", "/api/v1/auth/register").permitAll()
.requestMatchers(
"/api/v1/auth/login",
"/api/v1/auth/register",
"/api/v1/auth/forgot-password",
"/api/v1/auth/reset-password"
).permitAll()
.requestMatchers("/api/v1/health").permitAll()
.requestMatchers("/ws/chat/**", "/ws/chat-sockjs/**").permitAll()
.requestMatchers("/swagger-ui/**", "/v3/api-docs/**", "/swagger-ui.html").permitAll()

View File

@@ -1,6 +1,7 @@
package com.petshop.backend.service;
import com.petshop.backend.entity.User;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.PathResource;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType;
@@ -8,6 +9,7 @@ import org.springframework.http.MediaTypeFactory;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import jakarta.annotation.PostConstruct;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -22,7 +24,15 @@ public class AvatarStorageService {
private static final String STORED_PREFIX = "/uploads/avatars/";
private static final String OWNER_ENDPOINT = "/api/v1/auth/me/avatar/file";
private final Path avatarDirectory = Paths.get("uploads", "avatars").toAbsolutePath().normalize();
@Value("${app.upload.base-dir:uploads}")
private String uploadBaseDir;
private Path avatarDirectory;
@PostConstruct
private void init() {
avatarDirectory = Paths.get(uploadBaseDir, "avatars").toAbsolutePath().normalize();
}
public String storeAvatar(MultipartFile file) throws IOException {
Files.createDirectories(avatarDirectory);
@@ -101,7 +111,7 @@ public class AvatarStorageService {
}
String extension = originalFilename.substring(extensionIndex).toLowerCase(Locale.ROOT);
return switch (extension) {
case ".jpg", ".jpeg", ".png", ".gif" -> extension;
case ".jpg", ".jpeg", ".png", ".gif", ".webp" -> extension;
default -> ".jpg";
};
}

View File

@@ -1,6 +1,8 @@
package com.petshop.backend.service;
import com.petshop.backend.dto.cart.*;
import com.petshop.backend.dto.sale.SaleItemRequest;
import com.petshop.backend.dto.sale.SaleRequest;
import com.petshop.backend.entity.*;
import com.petshop.backend.exception.BusinessException;
import com.petshop.backend.exception.ResourceNotFoundException;
@@ -22,12 +24,16 @@ import java.util.List;
@Service
public class CartService {
private static final int LOYALTY_POINTS_PER_DOLLAR = 20;
private final CartRepository cartRepository;
private final CartItemRepository cartItemRepository;
private final UserRepository userRepository;
private final StoreRepository storeRepository;
private final ProductRepository productRepository;
private final CouponRepository couponRepository;
private final SaleRepository saleRepository;
private final SaleService saleService;
@Value("${stripe.secret-key:}")
private String stripeSecretKey;
@@ -37,13 +43,17 @@ public class CartService {
UserRepository userRepository,
StoreRepository storeRepository,
ProductRepository productRepository,
CouponRepository couponRepository) {
CouponRepository couponRepository,
SaleRepository saleRepository,
SaleService saleService) {
this.cartRepository = cartRepository;
this.cartItemRepository = cartItemRepository;
this.userRepository = userRepository;
this.storeRepository = storeRepository;
this.productRepository = productRepository;
this.couponRepository = couponRepository;
this.saleRepository = saleRepository;
this.saleService = saleService;
}
@PostConstruct
@@ -77,10 +87,14 @@ public class CartService {
newCart.setUser(user);
newCart.setStore(store);
newCart.setCartStatus("ACTIVE");
newCart.setPointsApplied(false);
newCart.setPointsDiscountAmount(BigDecimal.ZERO);
return cartRepository.save(newCart);
});
requireNotCheckoutPending(cart);
cartItemRepository.findByCartCartIdAndProductProdId(cart.getCartId(), product.getProdId())
.ifPresentOrElse(
existing -> existing.setQuantity(existing.getQuantity() + request.getQuantity()),
@@ -112,6 +126,8 @@ public class CartService {
throw new BusinessException("Cart is not active");
}
requireNotCheckoutPending(item.getCart());
if (request.getQuantity() < 1) {
throw new BusinessException("Quantity must be at least 1");
}
@@ -136,6 +152,8 @@ public class CartService {
throw new BusinessException("Cart is not active");
}
requireNotCheckoutPending(item.getCart());
Cart cart = item.getCart();
cartItemRepository.delete(item);
recalculate(cart);
@@ -147,11 +165,14 @@ public class CartService {
public void clearCart(Long userId, Long storeId) {
cartRepository.findActiveCartByUserAndStore(userId, storeId, "ACTIVE")
.ifPresent(cart -> {
requireNotCheckoutPending(cart);
cartItemRepository.deleteByCartCartId(cart.getCartId());
cart.setSubtotalAmount(BigDecimal.ZERO);
cart.setDiscountAmount(BigDecimal.ZERO);
cart.setPointsDiscountAmount(BigDecimal.ZERO);
cart.setTotalAmount(BigDecimal.ZERO);
cart.setCoupon(null);
cart.setPointsApplied(false);
cartRepository.save(cart);
});
}
@@ -162,6 +183,8 @@ public class CartService {
.findActiveCartByUserAndStore(userId, storeId, "ACTIVE")
.orElseThrow(() -> new BusinessException("No active cart found"));
requireNotCheckoutPending(cart);
Coupon coupon = couponRepository.findByCouponCodeIgnoreCase(couponCode)
.orElseThrow(() -> new BusinessException("Invalid coupon code"));
@@ -189,12 +212,28 @@ public class CartService {
return toResponse(cart);
}
@Transactional
public CartResponse applyPoints(Long userId, Long storeId, Boolean useLoyaltyPoints) {
Cart cart = cartRepository
.findActiveCartByUserAndStore(userId, storeId, "ACTIVE")
.orElseThrow(() -> new BusinessException("No active cart found"));
requireNotCheckoutPending(cart);
cart.setPointsApplied(Boolean.TRUE.equals(useLoyaltyPoints));
recalculate(cart);
return toResponse(cart);
}
@Transactional
public CheckoutResponse checkout(Long userId, Long storeId) {
Cart cart = cartRepository
.findActiveCartByUserAndStore(userId, storeId, "ACTIVE")
.orElseThrow(() -> new BusinessException("No active cart found"));
if (Boolean.TRUE.equals(cart.getCheckoutPending())) {
throw new BusinessException("Checkout already in progress for this cart");
}
List<CartItem> items = cartItemRepository.findByCartCartId(cart.getCartId());
if (items.isEmpty()) {
throw new BusinessException("Cart is empty");
@@ -219,6 +258,12 @@ public class CartService {
PaymentIntent intent = PaymentIntent.create(params);
cart.setCheckoutPending(true);
cart.setCheckoutAmount(cart.getTotalAmount());
cart.setCheckoutStartedAt(LocalDateTime.now());
cart.setCheckoutPaymentIntentId(intent.getId());
cartRepository.save(cart);
return new CheckoutResponse(
cart.getCartId(),
intent.getClientSecret(),
@@ -242,7 +287,29 @@ public class CartService {
throw new BusinessException("Payment has not been completed");
}
Long cartId = Long.parseLong(intent.getMetadata().get("cartId"));
String cartIdMetadata = intent.getMetadata() != null ? intent.getMetadata().get("cartId") : null;
String userIdMetadata = intent.getMetadata() != null ? intent.getMetadata().get("userId") : null;
if (cartIdMetadata == null || cartIdMetadata.isBlank()) {
throw new BusinessException("Payment metadata is missing cart information");
}
if (userIdMetadata == null || userIdMetadata.isBlank()) {
throw new BusinessException("Payment metadata is missing user information");
}
Long cartId;
Long metadataUserId;
try {
cartId = Long.parseLong(cartIdMetadata);
metadataUserId = Long.parseLong(userIdMetadata);
} catch (NumberFormatException ex) {
throw new BusinessException("Payment metadata is invalid");
}
if (!metadataUserId.equals(userId)) {
throw new BusinessException("Unauthorized");
}
Cart cart = cartRepository.findById(cartId)
.orElseThrow(() -> new BusinessException("Cart not found"));
@@ -250,7 +317,74 @@ public class CartService {
throw new BusinessException("Unauthorized");
}
if (!Boolean.TRUE.equals(cart.getCheckoutPending())) {
throw new BusinessException("Cart checkout was not initiated");
}
if (!paymentIntentId.equals(cart.getCheckoutPaymentIntentId())) {
throw new BusinessException("Payment intent mismatch");
}
if (cart.getCheckoutAmount() == null) {
throw new BusinessException("Checkout amount snapshot is missing");
}
if (!cart.getCartStatus().equals("ACTIVE")) {
throw new BusinessException("Cart is not in active state");
}
List<CartItem> items = cartItemRepository.findByCartCartId(cart.getCartId());
if (items.isEmpty()) {
throw new BusinessException("Cart items were removed during checkout");
}
BigDecimal recalculatedTotal = recalculateTotalAmount(cart);
if (recalculatedTotal.compareTo(cart.getCheckoutAmount()) != 0) {
throw new BusinessException("Cart total changed during checkout");
}
long storedAmountInCents = cart.getCheckoutAmount()
.multiply(BigDecimal.valueOf(100))
.setScale(0, RoundingMode.HALF_UP)
.longValue();
if (intent.getAmount() != storedAmountInCents) {
throw new BusinessException("Stripe charged amount does not match expected amount");
}
if (saleRepository.findByCartCartId(cart.getCartId()).isPresent()) {
cart.setCartStatus("CHECKED_OUT");
cart.setCheckoutPending(false);
cart.setCheckoutAmount(null);
cart.setCheckoutStartedAt(null);
cart.setCheckoutPaymentIntentId(null);
cartRepository.save(cart);
return;
}
SaleRequest saleRequest = new SaleRequest();
saleRequest.setStoreId(cart.getStore().getStoreId());
saleRequest.setCustomerId(cart.getUser().getId());
saleRequest.setCartId(cart.getCartId());
saleRequest.setCouponId(cart.getCoupon() != null ? cart.getCoupon().getCouponId() : null);
saleRequest.setPaymentMethod("Card");
saleRequest.setChannel("WEBSITE");
saleRequest.setItems(cartItemRepository.findByCartCartId(cart.getCartId()).stream()
.map(item -> {
SaleItemRequest saleItemRequest = new SaleItemRequest();
saleItemRequest.setProdId(item.getProduct().getProdId());
saleItemRequest.setQuantity(item.getQuantity());
return saleItemRequest;
})
.toList());
saleService.createSale(saleRequest);
cart.setCartStatus("CHECKED_OUT");
cart.setCheckoutPending(false);
cart.setCheckoutAmount(null);
cart.setCheckoutStartedAt(null);
cart.setCheckoutPaymentIntentId(null);
cartRepository.save(cart);
} catch (StripeException e) {
@@ -258,6 +392,12 @@ public class CartService {
}
}
private void requireNotCheckoutPending(Cart cart) {
if (Boolean.TRUE.equals(cart.getCheckoutPending())) {
throw new BusinessException("Cannot modify cart while checkout is in progress");
}
}
private void recalculate(Cart cart) {
List<CartItem> items = cartItemRepository.findByCartCartId(cart.getCartId());
@@ -283,10 +423,61 @@ public class CartService {
discount = discount.max(BigDecimal.ZERO).min(subtotal);
cart.setDiscountAmount(discount);
cart.setTotalAmount(subtotal.subtract(discount).max(BigDecimal.ZERO));
BigDecimal remainingAfterCoupon = subtotal.subtract(discount).max(BigDecimal.ZERO);
BigDecimal pointsDiscount = calculatePointsDiscount(cart.getUser(), remainingAfterCoupon, Boolean.TRUE.equals(cart.getPointsApplied()));
cart.setPointsDiscountAmount(pointsDiscount);
cart.setTotalAmount(remainingAfterCoupon.subtract(pointsDiscount).max(BigDecimal.ZERO));
cartRepository.save(cart);
}
private BigDecimal recalculateTotalAmount(Cart cart) {
List<CartItem> items = cartItemRepository.findByCartCartId(cart.getCartId());
BigDecimal subtotal = items.stream()
.map(i -> i.getUnitPrice().multiply(BigDecimal.valueOf(i.getQuantity())))
.reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal discount = BigDecimal.ZERO;
Coupon coupon = cart.getCoupon();
if (coupon != null) {
if ("PERCENTAGE".equalsIgnoreCase(coupon.getDiscountType())) {
discount = subtotal.multiply(coupon.getDiscountValue())
.divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP);
}
else if ("FIXED".equalsIgnoreCase(coupon.getDiscountType())) {
discount = coupon.getDiscountValue().min(subtotal);
}
}
discount = discount.max(BigDecimal.ZERO).min(subtotal);
BigDecimal remainingAfterCoupon = subtotal.subtract(discount).max(BigDecimal.ZERO);
BigDecimal pointsDiscount = calculatePointsDiscount(cart.getUser(), remainingAfterCoupon, Boolean.TRUE.equals(cart.getPointsApplied()));
return remainingAfterCoupon.subtract(pointsDiscount).max(BigDecimal.ZERO);
}
private BigDecimal calculatePointsDiscount(User user, BigDecimal remainingAmount, boolean pointsApplied) {
if (!pointsApplied || user == null || remainingAmount.compareTo(BigDecimal.ZERO) <= 0) {
return BigDecimal.ZERO;
}
int availablePoints = user.getLoyaltyPoints() != null ? user.getLoyaltyPoints() : 0;
int wholeDollars = availablePoints / LOYALTY_POINTS_PER_DOLLAR;
if (wholeDollars <= 0) {
return BigDecimal.ZERO;
}
BigDecimal maxRedeemable = remainingAmount.setScale(0, RoundingMode.DOWN);
return BigDecimal.valueOf(wholeDollars)
.min(maxRedeemable)
.setScale(2, RoundingMode.HALF_UP);
}
private CartResponse toResponse(Cart cart) {
List<CartItemResponse> itemResponses = cartItemRepository
.findByCartCartId(cart.getCartId())
@@ -309,8 +500,11 @@ public class CartService {
response.setItems(itemResponses);
response.setSubtotalAmount(cart.getSubtotalAmount());
response.setDiscountAmount(cart.getDiscountAmount());
response.setPointsDiscountAmount(cart.getPointsDiscountAmount());
response.setTotalAmount(cart.getTotalAmount());
response.setCouponCode(cart.getCoupon() != null ? cart.getCoupon().getCouponCode() : null);
response.setPointsApplied(cart.getPointsApplied());
response.setAvailableLoyaltyPoints(cart.getUser() != null ? cart.getUser().getLoyaltyPoints() : null);
return response;
}

View File

@@ -1,5 +1,6 @@
package com.petshop.backend.service;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.PathResource;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType;
@@ -21,20 +22,31 @@ public class CatalogImageStorageService {
private static final String PET_PREFIX = "/uploads/pets/";
private static final String PRODUCT_PREFIX = "/uploads/products/";
@Value("${app.upload.base-dir:uploads}")
private String uploadBaseDir;
public String storePetImage(MultipartFile file) throws IOException {
return storeImage(file, Paths.get("uploads", "pets").toAbsolutePath().normalize(), PET_PREFIX);
return storeImage(file, Paths.get(uploadBaseDir, "pets").toAbsolutePath().normalize(), PET_PREFIX);
}
public String storeProductImage(MultipartFile file) throws IOException {
return storeImage(file, Paths.get("uploads", "products").toAbsolutePath().normalize(), PRODUCT_PREFIX);
return storeImage(file, Paths.get(uploadBaseDir, "products").toAbsolutePath().normalize(), PRODUCT_PREFIX);
}
public Resource loadPetImage(String storedPath) {
return new PathResource(resolveStoredPath(storedPath, Paths.get("uploads", "pets").toAbsolutePath().normalize(), PET_PREFIX));
Resource resource = new PathResource(resolveStoredPath(storedPath, Paths.get(uploadBaseDir, "pets").toAbsolutePath().normalize(), PET_PREFIX));
if (!resource.exists()) {
throw new IllegalArgumentException("Image file was not found");
}
return resource;
}
public Resource loadProductImage(String storedPath) {
return new PathResource(resolveStoredPath(storedPath, Paths.get("uploads", "products").toAbsolutePath().normalize(), PRODUCT_PREFIX));
Resource resource = new PathResource(resolveStoredPath(storedPath, Paths.get(uploadBaseDir, "products").toAbsolutePath().normalize(), PRODUCT_PREFIX));
if (!resource.exists()) {
throw new IllegalArgumentException("Image file was not found");
}
return resource;
}
public MediaType resolveMediaType(Resource resource) {
@@ -42,11 +54,11 @@ public class CatalogImageStorageService {
}
public void deletePetImage(String storedPath) throws IOException {
deleteImage(storedPath, Paths.get("uploads", "pets").toAbsolutePath().normalize(), PET_PREFIX);
deleteImage(storedPath, Paths.get(uploadBaseDir, "pets").toAbsolutePath().normalize(), PET_PREFIX);
}
public void deleteProductImage(String storedPath) throws IOException {
deleteImage(storedPath, Paths.get("uploads", "products").toAbsolutePath().normalize(), PRODUCT_PREFIX);
deleteImage(storedPath, Paths.get(uploadBaseDir, "products").toAbsolutePath().normalize(), PRODUCT_PREFIX);
}
private String storeImage(MultipartFile file, Path directory, String prefix) throws IOException {
@@ -90,7 +102,7 @@ public class CatalogImageStorageService {
}
String extension = originalFilename.substring(extensionIndex).toLowerCase(Locale.ROOT);
return switch (extension) {
case ".jpg", ".jpeg", ".png", ".gif" -> extension;
case ".jpg", ".jpeg", ".png", ".gif", ".webp" -> extension;
default -> ".jpg";
};
}

View File

@@ -0,0 +1,136 @@
package com.petshop.backend.service;
import com.petshop.backend.dto.auth.ForgotPasswordResponse;
import com.petshop.backend.dto.auth.ResetPasswordResponse;
import com.petshop.backend.entity.PasswordResetToken;
import com.petshop.backend.entity.User;
import com.petshop.backend.exception.BusinessException;
import com.petshop.backend.repository.PasswordResetTokenRepository;
import com.petshop.backend.repository.UserRepository;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.time.LocalDateTime;
import java.util.Base64;
import java.util.Optional;
@Service
public class PasswordResetService {
private static final int RESET_TOKEN_BYTES = 32;
private static final long RESET_TOKEN_MINUTES = 30;
private final PasswordResetTokenRepository passwordResetTokenRepository;
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
private final SecureRandom secureRandom = new SecureRandom();
public PasswordResetService(PasswordResetTokenRepository passwordResetTokenRepository,
UserRepository userRepository,
PasswordEncoder passwordEncoder) {
this.passwordResetTokenRepository = passwordResetTokenRepository;
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
}
@Transactional
public ForgotPasswordResponse createResetToken(String usernameOrEmail) {
String normalized = trimToNull(usernameOrEmail);
if (normalized == null) {
throw new BusinessException("Username or email is required");
}
Optional<User> user = userRepository.findByEmail(normalized)
.or(() -> userRepository.findByUsername(normalized));
if (user.isEmpty() || !Boolean.TRUE.equals(user.get().getActive())) {
return new ForgotPasswordResponse(
"If an account matches that username or email, a reset token has been generated.",
null
);
}
User managedUser = user.get();
LocalDateTime now = LocalDateTime.now();
invalidateOutstandingTokens(managedUser.getId(), now);
String rawToken = generateRawToken();
PasswordResetToken resetToken = new PasswordResetToken();
resetToken.setUser(managedUser);
resetToken.setTokenHash(hashToken(rawToken));
resetToken.setExpiresAt(now.plusMinutes(RESET_TOKEN_MINUTES));
passwordResetTokenRepository.save(resetToken);
return new ForgotPasswordResponse(
"If an account matches that username or email, a reset token has been generated.",
rawToken
);
}
@Transactional
public ResetPasswordResponse resetPassword(String rawToken, String newPassword) {
String normalizedToken = trimToNull(rawToken);
if (normalizedToken == null) {
throw new BusinessException("Reset token is required");
}
PasswordResetToken token = passwordResetTokenRepository
.findByTokenHashAndUsedAtIsNullAndExpiresAtAfter(hashToken(normalizedToken), LocalDateTime.now())
.orElseThrow(() -> new BusinessException("Reset token is invalid or has expired"));
User user = token.getUser();
if (!Boolean.TRUE.equals(user.getActive())) {
throw new BusinessException("User account is inactive");
}
LocalDateTime now = LocalDateTime.now();
user.setPassword(passwordEncoder.encode(newPassword));
user.setTokenVersion(user.getTokenVersion() + 1);
userRepository.save(user);
token.setUsedAt(now);
passwordResetTokenRepository.save(token);
invalidateOutstandingTokens(user.getId(), now);
return new ResetPasswordResponse("Password reset successfully");
}
private void invalidateOutstandingTokens(Long userId, LocalDateTime usedAt) {
for (PasswordResetToken token : passwordResetTokenRepository.findByUser_IdAndUsedAtIsNull(userId)) {
token.setUsedAt(usedAt);
}
}
private String generateRawToken() {
byte[] bytes = new byte[RESET_TOKEN_BYTES];
secureRandom.nextBytes(bytes);
return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes);
}
private String hashToken(String rawToken) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hashed = digest.digest(rawToken.getBytes(StandardCharsets.UTF_8));
StringBuilder builder = new StringBuilder(hashed.length * 2);
for (byte value : hashed) {
builder.append(String.format("%02x", value));
}
return builder.toString();
} catch (NoSuchAlgorithmException ex) {
throw new IllegalStateException("SHA-256 is not available", ex);
}
}
private String trimToNull(String value) {
if (value == null) {
return null;
}
String trimmed = value.trim();
return trimmed.isEmpty() ? null : trimmed;
}
}

View File

@@ -22,6 +22,7 @@ import java.util.List;
public class SaleService {
private static final BigDecimal EMPLOYEE_DISCOUNT_PERCENT = new BigDecimal("0.10");
private static final int LOYALTY_POINTS_PER_DOLLAR = 20;
private final SaleRepository saleRepository;
private final ProductRepository productRepository;
@@ -56,7 +57,9 @@ public class SaleService {
@Transactional
public SaleResponse createSale(SaleRequest request) {
User employee = AuthenticationHelper.getAuthenticatedUser(userRepository);
User actor = AuthenticationHelper.getAuthenticatedUser(userRepository);
boolean websiteSale = request.getChannel() != null && request.getChannel().equalsIgnoreCase("WEBSITE");
User employee = websiteSale ? resolveWebsiteSaleEmployee(request.getStoreId()) : actor;
StoreLocation store = storeRepository.findById(request.getStoreId())
.orElseThrow(() -> new ResourceNotFoundException("Store not found with id: " + request.getStoreId()));
@@ -96,6 +99,11 @@ public class SaleService {
sale.setCustomer(customer);
}
if (websiteSale && customer == null) {
customer = actor;
sale.setCustomer(customer);
}
if (sale.getIsRefund() && request.getOriginalSaleId() != null) {
Sale originalSale = saleRepository.findById(request.getOriginalSaleId())
.orElseThrow(() -> new ResourceNotFoundException("Original sale not found with id: " + request.getOriginalSaleId()));
@@ -152,34 +160,17 @@ public class SaleService {
saleItems.add(saleItem);
subtotalAmount = subtotalAmount.add(itemTotal);
}
Sale originalSale = sale.getOriginalSale();
BigDecimal originalSubtotal = originalSale.getSubtotalAmount() != null
? originalSale.getSubtotalAmount()
: originalSale.getItems().stream()
.map(i -> i.getUnitPrice().multiply(BigDecimal.valueOf(Math.abs(i.getQuantity()))))
.reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal refundRatio = originalSubtotal.compareTo(BigDecimal.ZERO) != 0
? subtotalAmount.divide(originalSubtotal, 10, RoundingMode.HALF_UP)
: BigDecimal.ONE;
BigDecimal refundTotal = originalSale.getTotalAmount().abs()
.multiply(refundRatio).setScale(2, RoundingMode.HALF_UP);
sale.setSubtotalAmount(subtotalAmount.negate());
sale.setTotalAmount(refundTotal.negate());
User refundCustomer = customer != null ? customer : originalSale.getCustomer();
if (refundCustomer != null) {
int pointsToRestore = BigDecimal.valueOf(originalSale.getPointsUsed())
.multiply(refundRatio).setScale(0, RoundingMode.FLOOR).intValue();
int pointsToDeduct = BigDecimal.valueOf(originalSale.getPointsEarned())
.multiply(refundRatio).setScale(0, RoundingMode.FLOOR).intValue();
refundCustomer.setLoyaltyPoints(refundCustomer.getLoyaltyPoints() + pointsToRestore - pointsToDeduct);
userRepository.save(refundCustomer);
}
subtotalAmount = subtotalAmount.negate();
sale.setSubtotalAmount(subtotalAmount);
sale.setTotalAmount(subtotalAmount);
sale.setCouponDiscountAmount(BigDecimal.ZERO);
sale.setEmployeeDiscountAmount(BigDecimal.ZERO);
sale.setLoyaltyDiscountAmount(BigDecimal.ZERO);
sale.setPointsEarned(0);
} else {
if (request.getItems() == null || request.getItems().isEmpty()) {
throw new BusinessException("At least one item is required");
}
for (var itemRequest : request.getItems()) {
Product product = productRepository.findById(itemRequest.getProdId())
.orElseThrow(() -> new ResourceNotFoundException("Product not found with id: " + itemRequest.getProdId()));
@@ -212,28 +203,22 @@ public class SaleService {
BigDecimal couponDiscount = calculateCouponDiscount(sale.getCoupon(), subtotalAmount);
sale.setCouponDiscountAmount(couponDiscount);
BigDecimal pointsDiscount = BigDecimal.ZERO;
int pointsUsed = 0;
if (customer != null && request.getPointsUsed() != null && request.getPointsUsed() > 0) {
if (customer.getLoyaltyPoints() < request.getPointsUsed()) {
throw new BusinessException("Customer does not have enough loyalty points");
}
pointsUsed = request.getPointsUsed();
pointsDiscount = calculatePointsDiscount(pointsUsed);
customer.setLoyaltyPoints(customer.getLoyaltyPoints() - pointsUsed);
}
sale.setPointsUsed(pointsUsed);
sale.setPointsDiscountAmount(pointsDiscount);
BigDecimal employeeDiscount = calculateEmployeeDiscount(customer, subtotalAmount.subtract(couponDiscount).subtract(pointsDiscount));
BigDecimal employeeDiscount = calculateEmployeeDiscount(customer, subtotalAmount.subtract(couponDiscount));
sale.setEmployeeDiscountAmount(employeeDiscount);
BigDecimal finalTotal = subtotalAmount.subtract(couponDiscount).subtract(pointsDiscount).subtract(employeeDiscount);
boolean useLoyaltyPoints = sale.getCart() != null && Boolean.TRUE.equals(sale.getCart().getPointsApplied());
BigDecimal loyaltyDiscount = calculateLoyaltyDiscount(customer, subtotalAmount.subtract(couponDiscount).subtract(employeeDiscount), useLoyaltyPoints);
sale.setLoyaltyDiscountAmount(loyaltyDiscount);
BigDecimal finalTotal = subtotalAmount.subtract(couponDiscount).subtract(employeeDiscount).subtract(loyaltyDiscount);
sale.setTotalAmount(finalTotal.max(BigDecimal.ZERO));
int pointsUsed = toPointsUsed(loyaltyDiscount);
sale.setPointsEarned(sale.getTotalAmount().setScale(0, RoundingMode.FLOOR).intValue());
if (customer != null) {
customer.setLoyaltyPoints(customer.getLoyaltyPoints() + sale.getPointsEarned());
int currentPoints = customer.getLoyaltyPoints() != null ? customer.getLoyaltyPoints() : 0;
int updatedPoints = currentPoints - pointsUsed + sale.getPointsEarned();
customer.setLoyaltyPoints(Math.max(updatedPoints, 0));
userRepository.save(customer);
}
}
@@ -277,10 +262,6 @@ public class SaleService {
return discount.min(subtotal).setScale(2, RoundingMode.HALF_UP);
}
private BigDecimal calculatePointsDiscount(int pointsUsed) {
return new BigDecimal(pointsUsed).divide(new BigDecimal("20"), 2, RoundingMode.HALF_UP);
}
private BigDecimal calculateEmployeeDiscount(User customer, BigDecimal remainingAmount) {
if (customer == null || remainingAmount.compareTo(BigDecimal.ZERO) <= 0) {
return BigDecimal.ZERO;
@@ -293,6 +274,36 @@ public class SaleService {
return BigDecimal.ZERO;
}
private BigDecimal calculateLoyaltyDiscount(User customer, BigDecimal remainingAmount, boolean useLoyaltyPoints) {
if (!useLoyaltyPoints || customer == null || remainingAmount.compareTo(BigDecimal.ZERO) <= 0) {
return BigDecimal.ZERO;
}
int availablePoints = customer.getLoyaltyPoints() != null ? customer.getLoyaltyPoints() : 0;
int wholeDollars = availablePoints / LOYALTY_POINTS_PER_DOLLAR;
if (wholeDollars <= 0) {
return BigDecimal.ZERO;
}
BigDecimal maxRedeemable = remainingAmount.setScale(0, RoundingMode.DOWN);
return BigDecimal.valueOf(wholeDollars)
.min(maxRedeemable)
.setScale(2, RoundingMode.HALF_UP);
}
private int toPointsUsed(BigDecimal loyaltyDiscount) {
if (loyaltyDiscount == null || loyaltyDiscount.compareTo(BigDecimal.ZERO) <= 0) {
return 0;
}
return loyaltyDiscount.setScale(0, RoundingMode.DOWN).intValue() * LOYALTY_POINTS_PER_DOLLAR;
}
private User resolveWebsiteSaleEmployee(Long storeId) {
return userRepository.findFirstByPrimaryStoreStoreIdAndRoleAndActiveTrueOrderByIdAsc(storeId, User.Role.STAFF)
.or(() -> userRepository.findFirstByRoleAndActiveTrueOrderByIdAsc(User.Role.ADMIN))
.orElseThrow(() -> new BusinessException("No active employee available for website sale"));
}
private SaleResponse mapToResponse(Sale sale) {
SaleResponse response = new SaleResponse();
response.setSaleId(sale.getSaleId());
@@ -314,9 +325,8 @@ public class SaleService {
response.setSubtotalAmount(sale.getSubtotalAmount());
response.setCouponDiscountAmount(sale.getCouponDiscountAmount());
response.setEmployeeDiscountAmount(sale.getEmployeeDiscountAmount());
response.setLoyaltyDiscountAmount(sale.getLoyaltyDiscountAmount());
response.setPointsEarned(sale.getPointsEarned());
response.setPointsUsed(sale.getPointsUsed());
response.setPointsDiscountAmount(sale.getPointsDiscountAmount());
response.setChannel(sale.getChannel());
if (sale.getCoupon() != null) {
response.setCouponId(sale.getCoupon().getCouponId());

View File

@@ -47,6 +47,10 @@ springdoc:
swagger-ui:
path: /swagger-ui
app:
upload:
base-dir: ${UPLOAD_BASE_DIR:uploads}
jwt:
secret: ${JWT_SECRET}
expiration: ${JWT_EXPIRATION:86400000}

View File

@@ -1,4 +1,3 @@
CREATE TABLE IF NOT EXISTS storeLocation (
storeId BIGINT AUTO_INCREMENT PRIMARY KEY,
storeName VARCHAR(100) NOT NULL,
@@ -192,6 +191,12 @@ CREATE TABLE IF NOT EXISTS cart (
subtotalAmount DECIMAL(10, 2) NOT NULL DEFAULT 0.00,
discountAmount DECIMAL(10, 2) NOT NULL DEFAULT 0.00,
totalAmount DECIMAL(10, 2) NOT NULL DEFAULT 0.00,
pointsApplied BOOLEAN NOT NULL DEFAULT FALSE,
pointsDiscountAmount DECIMAL(10, 2) NOT NULL DEFAULT 0.00,
checkoutPending BOOLEAN NOT NULL DEFAULT FALSE,
checkoutAmount DECIMAL(10, 2),
checkoutStartedAt DATETIME,
checkoutPaymentIntentId VARCHAR(255),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
CONSTRAINT fk_cart_user FOREIGN KEY (userId) REFERENCES users(id),
@@ -227,9 +232,13 @@ CREATE TABLE IF NOT EXISTS sale (
subtotalAmount DECIMAL(10, 2) NULL,
couponDiscountAmount DECIMAL(10, 2) NOT NULL DEFAULT 0.00,
employeeDiscountAmount DECIMAL(10, 2) NOT NULL DEFAULT 0.00,
pointsUsed INT NOT NULL DEFAULT 0,
loyaltyDiscountAmount DECIMAL(10, 2) NOT NULL DEFAULT 0.00,
pointsEarned INT NOT NULL DEFAULT 0,
pointsDiscountAmount DECIMAL(10, 2) NOT NULL DEFAULT 0.00,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
CONSTRAINT uq_sale_cart_id UNIQUE (cartId),
CONSTRAINT fk_sale_employee FOREIGN KEY (employeeId) REFERENCES users(id),
CONSTRAINT fk_sale_store FOREIGN KEY (storeId) REFERENCES storeLocation(storeId),
CONSTRAINT fk_sale_customer FOREIGN KEY (customerId) REFERENCES users(id) ON DELETE SET NULL,
@@ -275,6 +284,20 @@ CREATE TABLE IF NOT EXISTS refund_item (
CONSTRAINT fk_refund_item_product FOREIGN KEY (prod_id) REFERENCES product(prodId)
);
CREATE TABLE IF NOT EXISTS passwordResetToken (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
userId BIGINT NOT NULL,
tokenHash VARCHAR(64) NOT NULL,
expiresAt DATETIME NOT NULL,
usedAt DATETIME NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT uq_password_reset_token_hash UNIQUE (tokenHash),
CONSTRAINT fk_password_reset_token_user FOREIGN KEY (userId) REFERENCES users(id) ON DELETE CASCADE
);
CREATE INDEX idx_password_reset_token_user ON passwordResetToken(userId);
CREATE INDEX idx_password_reset_token_expires ON passwordResetToken(expiresAt);
CREATE TABLE IF NOT EXISTS conversation (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
customerId BIGINT NOT NULL,
@@ -307,6 +330,10 @@ CREATE TABLE IF NOT EXISTS activityLog (
logId BIGINT AUTO_INCREMENT PRIMARY KEY,
userId BIGINT NOT NULL,
storeId BIGINT NULL,
usernameSnapshot VARCHAR(50) NULL,
fullNameSnapshot VARCHAR(100) NULL,
roleSnapshot VARCHAR(20) NULL,
storeNameSnapshot VARCHAR(100) NULL,
activity TEXT NOT NULL,
logTimestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_activity_log_user FOREIGN KEY (userId) REFERENCES users(id),
@@ -339,3 +366,4 @@ CREATE INDEX idx_cart_user ON cart(userId);
CREATE INDEX idx_conversation_customer ON conversation(customerId);
CREATE INDEX idx_conversation_staff ON conversation(staffId);
CREATE INDEX idx_activity_log_store ON activityLog(storeId);
CREATE INDEX idx_activity_log_timestamp_id ON activityLog(logTimestamp, logId);

View File

@@ -4,6 +4,7 @@ SET FOREIGN_KEY_CHECKS = 0;
DELETE FROM activityLog;
DELETE FROM message;
DELETE FROM conversation;
DELETE FROM passwordResetToken;
DELETE FROM refund_item;
DELETE FROM refund;
DELETE FROM saleItem;
@@ -45,6 +46,7 @@ ALTER TABLE refund AUTO_INCREMENT = 1;
ALTER TABLE refund_item AUTO_INCREMENT = 1;
ALTER TABLE conversation AUTO_INCREMENT = 1;
ALTER TABLE message AUTO_INCREMENT = 1;
ALTER TABLE passwordResetToken AUTO_INCREMENT = 1;
ALTER TABLE activityLog AUTO_INCREMENT = 1;
SET FOREIGN_KEY_CHECKS = 1;
@@ -1739,3 +1741,509 @@ INSERT INTO message (id, conversationId, senderId, content, attachmentUrl, attac
(118, 30, 8, 'Happy to help. Please share the order number or the pet name involved.', NULL, NULL, NULL, NULL, '2026-03-02 09:05:00', 1),
(119, 30, 45, 'Order #1030 is the one I meant, and the pet is Kiki.', NULL, NULL, NULL, NULL, '2026-03-02 09:10:00', 1),
(120, 30, 8, 'Thanks, the account and order are now updated on this conversation.', NULL, NULL, NULL, NULL, '2026-03-02 09:15:00', 0);
INSERT INTO users (username, password, email, firstName, lastName, fullName, phone, avatarUrl, role, staffRole, primaryStoreId, loyaltyPoints, active, tokenVersion)
SELECT 'ai.bot',
'$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq',
'bot@petshop.com',
'AI',
'Bot',
'AI Bot',
'000-000-0000',
'https://images.petshop.local/users/bot.webp',
'STAFF',
'CUSTOMER_SERVICE',
NULL,
0,
1,
0
FROM DUAL
WHERE NOT EXISTS (
SELECT 1
FROM users
WHERE username = 'ai.bot'
);
UPDATE users
SET
username = CASE id
WHEN 15 THEN 'customer'
WHEN 16 THEN 'maya.brown'
WHEN 17 THEN 'noah.clark'
WHEN 18 THEN 'avery.wilson'
WHEN 19 THEN 'leah.martinez'
WHEN 20 THEN 'julian.anderson'
WHEN 21 THEN 'zoe.taylor'
WHEN 22 THEN 'ethan.parker'
WHEN 23 THEN 'ruby.evans'
WHEN 24 THEN 'caleb.scott'
WHEN 25 THEN 'ivy.adams'
WHEN 26 THEN 'isaac.baker'
WHEN 27 THEN 'hannah.hall'
WHEN 28 THEN 'mason.rivera'
WHEN 29 THEN 'aria.mitchell'
WHEN 30 THEN 'wyatt.collins'
WHEN 31 THEN 'elena.morris'
WHEN 32 THEN 'leo.cook'
WHEN 33 THEN 'grace.bell'
WHEN 34 THEN 'hudson.reed'
WHEN 35 THEN 'claire.murphy'
WHEN 36 THEN 'omar.bailey'
WHEN 37 THEN 'naomi.cooper'
WHEN 38 THEN 'jasper.richardson'
WHEN 39 THEN 'sofia.cox'
WHEN 40 THEN 'miles.howard'
WHEN 41 THEN 'audrey.ward'
WHEN 42 THEN 'nathan.torres'
WHEN 43 THEN 'jade.peterson'
WHEN 44 THEN 'rowan.gray'
WHEN 45 THEN 'lila.ramirez'
WHEN 46 THEN 'eli.james'
WHEN 47 THEN 'violet.watson'
WHEN 48 THEN 'gavin.brooks'
WHEN 49 THEN 'stella.kelly'
WHEN 50 THEN 'adrian.sanders'
WHEN 51 THEN 'hazel.price'
WHEN 52 THEN 'connor.bennett'
WHEN 53 THEN 'sadie.wood'
WHEN 54 THEN 'xavier.barnes'
WHEN 55 THEN 'alice.ross'
WHEN 56 THEN 'roman.henderson'
WHEN 57 THEN 'lucy.coleman'
WHEN 58 THEN 'evan.jenkins'
WHEN 59 THEN 'mila.perry'
WHEN 60 THEN 'cole.powell'
WHEN 61 THEN 'nora.long'
WHEN 62 THEN 'adam.patterson'
WHEN 63 THEN 'layla.hughes'
WHEN 64 THEN 'blake.flores'
WHEN 65 THEN 'ellie.washington'
WHEN 66 THEN 'ryan.butler'
WHEN 67 THEN 'cora.simmons'
WHEN 68 THEN 'simon.foster'
WHEN 69 THEN 'piper.gonzales'
WHEN 70 THEN 'joel.bryant'
WHEN 71 THEN 'eva.alexander'
WHEN 72 THEN 'felix.russell'
WHEN 73 THEN 'maeve.griffin'
WHEN 74 THEN 'tristan.diaz'
WHEN 75 THEN 'ariana.hayes'
WHEN 76 THEN 'declan.myers'
WHEN 77 THEN 'brooke.ford'
WHEN 78 THEN 'micah.hamilton'
WHEN 79 THEN 'bianca.graham'
WHEN 80 THEN 'jonah.sullivan'
WHEN 81 THEN 'tessa.wallace'
WHEN 82 THEN 'damian.woods'
WHEN 83 THEN 'riley.cole'
WHEN 84 THEN 'kieran.west'
WHEN 85 THEN 'sienna.jordan'
WHEN 86 THEN 'finley.owens'
WHEN 87 THEN 'maren.reynolds'
WHEN 88 THEN 'asher.fisher'
WHEN 89 THEN 'daphne.ellis'
WHEN 90 THEN 'bennett.harrison'
WHEN 91 THEN 'selena.gibson'
WHEN 92 THEN 'emmett.mcdonald'
WHEN 93 THEN 'phoebe.cruz'
WHEN 94 THEN 'sawyer.marshall'
WHEN 95 THEN 'keira.ortiz'
WHEN 96 THEN 'landon.gomez'
WHEN 97 THEN 'rosalie.murray'
WHEN 98 THEN 'malik.freeman'
WHEN 99 THEN 'esme.wells'
WHEN 100 THEN 'holden.webb'
ELSE username
END,
email = CASE id
WHEN 15 THEN 'customer@petshop.com'
WHEN 16 THEN 'maya.brown@gmail.com'
WHEN 17 THEN 'noah.clark@gmail.com'
WHEN 18 THEN 'avery.wilson@gmail.com'
WHEN 19 THEN 'leah.martinez@gmail.com'
WHEN 20 THEN 'julian.anderson@gmail.com'
WHEN 21 THEN 'zoe.taylor@gmail.com'
WHEN 22 THEN 'ethan.parker@gmail.com'
WHEN 23 THEN 'ruby.evans@gmail.com'
WHEN 24 THEN 'caleb.scott@gmail.com'
WHEN 25 THEN 'ivy.adams@gmail.com'
WHEN 26 THEN 'isaac.baker@gmail.com'
WHEN 27 THEN 'hannah.hall@gmail.com'
WHEN 28 THEN 'mason.rivera@gmail.com'
WHEN 29 THEN 'aria.mitchell@gmail.com'
WHEN 30 THEN 'wyatt.collins@gmail.com'
WHEN 31 THEN 'elena.morris@gmail.com'
WHEN 32 THEN 'leo.cook@gmail.com'
WHEN 33 THEN 'grace.bell@gmail.com'
WHEN 34 THEN 'hudson.reed@gmail.com'
WHEN 35 THEN 'claire.murphy@gmail.com'
WHEN 36 THEN 'omar.bailey@gmail.com'
WHEN 37 THEN 'naomi.cooper@gmail.com'
WHEN 38 THEN 'jasper.richardson@gmail.com'
WHEN 39 THEN 'sofia.cox@gmail.com'
WHEN 40 THEN 'miles.howard@gmail.com'
WHEN 41 THEN 'audrey.ward@gmail.com'
WHEN 42 THEN 'nathan.torres@gmail.com'
WHEN 43 THEN 'jade.peterson@gmail.com'
WHEN 44 THEN 'rowan.gray@gmail.com'
WHEN 45 THEN 'lila.ramirez@gmail.com'
WHEN 46 THEN 'eli.james@gmail.com'
WHEN 47 THEN 'violet.watson@gmail.com'
WHEN 48 THEN 'gavin.brooks@gmail.com'
WHEN 49 THEN 'stella.kelly@gmail.com'
WHEN 50 THEN 'adrian.sanders@gmail.com'
WHEN 51 THEN 'hazel.price@gmail.com'
WHEN 52 THEN 'connor.bennett@gmail.com'
WHEN 53 THEN 'sadie.wood@gmail.com'
WHEN 54 THEN 'xavier.barnes@gmail.com'
WHEN 55 THEN 'alice.ross@gmail.com'
WHEN 56 THEN 'roman.henderson@gmail.com'
WHEN 57 THEN 'lucy.coleman@gmail.com'
WHEN 58 THEN 'evan.jenkins@gmail.com'
WHEN 59 THEN 'mila.perry@gmail.com'
WHEN 60 THEN 'cole.powell@gmail.com'
WHEN 61 THEN 'nora.long@gmail.com'
WHEN 62 THEN 'adam.patterson@gmail.com'
WHEN 63 THEN 'layla.hughes@gmail.com'
WHEN 64 THEN 'blake.flores@gmail.com'
WHEN 65 THEN 'ellie.washington@gmail.com'
WHEN 66 THEN 'ryan.butler@gmail.com'
WHEN 67 THEN 'cora.simmons@gmail.com'
WHEN 68 THEN 'simon.foster@gmail.com'
WHEN 69 THEN 'piper.gonzales@gmail.com'
WHEN 70 THEN 'joel.bryant@gmail.com'
WHEN 71 THEN 'eva.alexander@gmail.com'
WHEN 72 THEN 'felix.russell@gmail.com'
WHEN 73 THEN 'maeve.griffin@gmail.com'
WHEN 74 THEN 'tristan.diaz@gmail.com'
WHEN 75 THEN 'ariana.hayes@gmail.com'
WHEN 76 THEN 'declan.myers@gmail.com'
WHEN 77 THEN 'brooke.ford@gmail.com'
WHEN 78 THEN 'micah.hamilton@gmail.com'
WHEN 79 THEN 'bianca.graham@gmail.com'
WHEN 80 THEN 'jonah.sullivan@gmail.com'
WHEN 81 THEN 'tessa.wallace@gmail.com'
WHEN 82 THEN 'damian.woods@gmail.com'
WHEN 83 THEN 'riley.cole@gmail.com'
WHEN 84 THEN 'kieran.west@gmail.com'
WHEN 85 THEN 'sienna.jordan@gmail.com'
WHEN 86 THEN 'finley.owens@gmail.com'
WHEN 87 THEN 'maren.reynolds@gmail.com'
WHEN 88 THEN 'asher.fisher@gmail.com'
WHEN 89 THEN 'daphne.ellis@gmail.com'
WHEN 90 THEN 'bennett.harrison@gmail.com'
WHEN 91 THEN 'selena.gibson@gmail.com'
WHEN 92 THEN 'emmett.mcdonald@gmail.com'
WHEN 93 THEN 'phoebe.cruz@gmail.com'
WHEN 94 THEN 'sawyer.marshall@gmail.com'
WHEN 95 THEN 'keira.ortiz@gmail.com'
WHEN 96 THEN 'landon.gomez@gmail.com'
WHEN 97 THEN 'rosalie.murray@gmail.com'
WHEN 98 THEN 'malik.freeman@gmail.com'
WHEN 99 THEN 'esme.wells@gmail.com'
WHEN 100 THEN 'holden.webb@gmail.com'
ELSE email
END,
firstName = CASE id
WHEN 15 THEN 'Test'
WHEN 16 THEN 'Maya'
WHEN 17 THEN 'Noah'
WHEN 18 THEN 'Avery'
WHEN 19 THEN 'Leah'
WHEN 20 THEN 'Julian'
WHEN 21 THEN 'Zoe'
WHEN 22 THEN 'Ethan'
WHEN 23 THEN 'Ruby'
WHEN 24 THEN 'Caleb'
WHEN 25 THEN 'Ivy'
WHEN 26 THEN 'Isaac'
WHEN 27 THEN 'Hannah'
WHEN 28 THEN 'Mason'
WHEN 29 THEN 'Aria'
WHEN 30 THEN 'Wyatt'
WHEN 31 THEN 'Elena'
WHEN 32 THEN 'Leo'
WHEN 33 THEN 'Grace'
WHEN 34 THEN 'Hudson'
WHEN 35 THEN 'Claire'
WHEN 36 THEN 'Omar'
WHEN 37 THEN 'Naomi'
WHEN 38 THEN 'Jasper'
WHEN 39 THEN 'Sofia'
WHEN 40 THEN 'Miles'
WHEN 41 THEN 'Audrey'
WHEN 42 THEN 'Nathan'
WHEN 43 THEN 'Jade'
WHEN 44 THEN 'Rowan'
WHEN 45 THEN 'Lila'
WHEN 46 THEN 'Eli'
WHEN 47 THEN 'Violet'
WHEN 48 THEN 'Gavin'
WHEN 49 THEN 'Stella'
WHEN 50 THEN 'Adrian'
WHEN 51 THEN 'Hazel'
WHEN 52 THEN 'Connor'
WHEN 53 THEN 'Sadie'
WHEN 54 THEN 'Xavier'
WHEN 55 THEN 'Alice'
WHEN 56 THEN 'Roman'
WHEN 57 THEN 'Lucy'
WHEN 58 THEN 'Evan'
WHEN 59 THEN 'Mila'
WHEN 60 THEN 'Cole'
WHEN 61 THEN 'Nora'
WHEN 62 THEN 'Adam'
WHEN 63 THEN 'Layla'
WHEN 64 THEN 'Blake'
WHEN 65 THEN 'Ellie'
WHEN 66 THEN 'Ryan'
WHEN 67 THEN 'Cora'
WHEN 68 THEN 'Simon'
WHEN 69 THEN 'Piper'
WHEN 70 THEN 'Joel'
WHEN 71 THEN 'Eva'
WHEN 72 THEN 'Felix'
WHEN 73 THEN 'Maeve'
WHEN 74 THEN 'Tristan'
WHEN 75 THEN 'Ariana'
WHEN 76 THEN 'Declan'
WHEN 77 THEN 'Brooke'
WHEN 78 THEN 'Micah'
WHEN 79 THEN 'Bianca'
WHEN 80 THEN 'Jonah'
WHEN 81 THEN 'Tessa'
WHEN 82 THEN 'Damian'
WHEN 83 THEN 'Riley'
WHEN 84 THEN 'Kieran'
WHEN 85 THEN 'Sienna'
WHEN 86 THEN 'Finley'
WHEN 87 THEN 'Maren'
WHEN 88 THEN 'Asher'
WHEN 89 THEN 'Daphne'
WHEN 90 THEN 'Bennett'
WHEN 91 THEN 'Selena'
WHEN 92 THEN 'Emmett'
WHEN 93 THEN 'Phoebe'
WHEN 94 THEN 'Sawyer'
WHEN 95 THEN 'Keira'
WHEN 96 THEN 'Landon'
WHEN 97 THEN 'Rosalie'
WHEN 98 THEN 'Malik'
WHEN 99 THEN 'Esme'
WHEN 100 THEN 'Holden'
ELSE firstName
END,
fullName = CASE id
WHEN 15 THEN 'Test Customer'
WHEN 16 THEN 'Maya Brown'
WHEN 17 THEN 'Noah Clark'
WHEN 18 THEN 'Avery Wilson'
WHEN 19 THEN 'Leah Martinez'
WHEN 20 THEN 'Julian Anderson'
WHEN 21 THEN 'Zoe Taylor'
WHEN 22 THEN 'Ethan Parker'
WHEN 23 THEN 'Ruby Evans'
WHEN 24 THEN 'Caleb Scott'
WHEN 25 THEN 'Ivy Adams'
WHEN 26 THEN 'Isaac Baker'
WHEN 27 THEN 'Hannah Hall'
WHEN 28 THEN 'Mason Rivera'
WHEN 29 THEN 'Aria Mitchell'
WHEN 30 THEN 'Wyatt Collins'
WHEN 31 THEN 'Elena Morris'
WHEN 32 THEN 'Leo Cook'
WHEN 33 THEN 'Grace Bell'
WHEN 34 THEN 'Hudson Reed'
WHEN 35 THEN 'Claire Murphy'
WHEN 36 THEN 'Omar Bailey'
WHEN 37 THEN 'Naomi Cooper'
WHEN 38 THEN 'Jasper Richardson'
WHEN 39 THEN 'Sofia Cox'
WHEN 40 THEN 'Miles Howard'
WHEN 41 THEN 'Audrey Ward'
WHEN 42 THEN 'Nathan Torres'
WHEN 43 THEN 'Jade Peterson'
WHEN 44 THEN 'Rowan Gray'
WHEN 45 THEN 'Lila Ramirez'
WHEN 46 THEN 'Eli James'
WHEN 47 THEN 'Violet Watson'
WHEN 48 THEN 'Gavin Brooks'
WHEN 49 THEN 'Stella Kelly'
WHEN 50 THEN 'Adrian Sanders'
WHEN 51 THEN 'Hazel Price'
WHEN 52 THEN 'Connor Bennett'
WHEN 53 THEN 'Sadie Wood'
WHEN 54 THEN 'Xavier Barnes'
WHEN 55 THEN 'Alice Ross'
WHEN 56 THEN 'Roman Henderson'
WHEN 57 THEN 'Lucy Coleman'
WHEN 58 THEN 'Evan Jenkins'
WHEN 59 THEN 'Mila Perry'
WHEN 60 THEN 'Cole Powell'
WHEN 61 THEN 'Nora Long'
WHEN 62 THEN 'Adam Patterson'
WHEN 63 THEN 'Layla Hughes'
WHEN 64 THEN 'Blake Flores'
WHEN 65 THEN 'Ellie Washington'
WHEN 66 THEN 'Ryan Butler'
WHEN 67 THEN 'Cora Simmons'
WHEN 68 THEN 'Simon Foster'
WHEN 69 THEN 'Piper Gonzales'
WHEN 70 THEN 'Joel Bryant'
WHEN 71 THEN 'Eva Alexander'
WHEN 72 THEN 'Felix Russell'
WHEN 73 THEN 'Maeve Griffin'
WHEN 74 THEN 'Tristan Diaz'
WHEN 75 THEN 'Ariana Hayes'
WHEN 76 THEN 'Declan Myers'
WHEN 77 THEN 'Brooke Ford'
WHEN 78 THEN 'Micah Hamilton'
WHEN 79 THEN 'Bianca Graham'
WHEN 80 THEN 'Jonah Sullivan'
WHEN 81 THEN 'Tessa Wallace'
WHEN 82 THEN 'Damian Woods'
WHEN 83 THEN 'Riley Cole'
WHEN 84 THEN 'Kieran West'
WHEN 85 THEN 'Sienna Jordan'
WHEN 86 THEN 'Finley Owens'
WHEN 87 THEN 'Maren Reynolds'
WHEN 88 THEN 'Asher Fisher'
WHEN 89 THEN 'Daphne Ellis'
WHEN 90 THEN 'Bennett Harrison'
WHEN 91 THEN 'Selena Gibson'
WHEN 92 THEN 'Emmett Mcdonald'
WHEN 93 THEN 'Phoebe Cruz'
WHEN 94 THEN 'Sawyer Marshall'
WHEN 95 THEN 'Keira Ortiz'
WHEN 96 THEN 'Landon Gomez'
WHEN 97 THEN 'Rosalie Murray'
WHEN 98 THEN 'Malik Freeman'
WHEN 99 THEN 'Esme Wells'
WHEN 100 THEN 'Holden Webb'
ELSE fullName
END,
primaryStoreId = CASE id
WHEN 15 THEN 1
WHEN 16 THEN 2
WHEN 17 THEN 3
WHEN 18 THEN 1
WHEN 19 THEN 2
WHEN 20 THEN 3
WHEN 21 THEN 1
WHEN 22 THEN 2
WHEN 23 THEN 3
WHEN 24 THEN 1
WHEN 25 THEN 2
WHEN 26 THEN 3
WHEN 27 THEN 1
WHEN 28 THEN 2
WHEN 29 THEN 3
WHEN 30 THEN 1
WHEN 31 THEN 2
WHEN 32 THEN 3
WHEN 33 THEN 1
WHEN 34 THEN 2
WHEN 35 THEN 3
WHEN 36 THEN 1
WHEN 37 THEN 2
WHEN 38 THEN 3
WHEN 39 THEN 1
WHEN 40 THEN 2
WHEN 41 THEN 3
WHEN 42 THEN 1
WHEN 43 THEN 2
WHEN 44 THEN 3
WHEN 45 THEN 1
WHEN 46 THEN 2
WHEN 47 THEN 3
WHEN 48 THEN 1
WHEN 49 THEN 2
WHEN 50 THEN 3
WHEN 51 THEN 1
WHEN 52 THEN 2
WHEN 53 THEN 3
WHEN 54 THEN 1
WHEN 55 THEN 2
WHEN 56 THEN 3
WHEN 57 THEN 1
WHEN 58 THEN 2
WHEN 59 THEN 3
WHEN 60 THEN 1
WHEN 61 THEN 2
WHEN 62 THEN 3
WHEN 63 THEN 1
WHEN 64 THEN 2
WHEN 65 THEN 3
WHEN 66 THEN 1
WHEN 67 THEN 2
WHEN 68 THEN 3
WHEN 69 THEN 1
WHEN 70 THEN 2
WHEN 71 THEN 3
WHEN 72 THEN 1
WHEN 73 THEN 2
WHEN 74 THEN 3
WHEN 75 THEN 1
WHEN 76 THEN 2
WHEN 77 THEN 3
WHEN 78 THEN 1
WHEN 79 THEN 2
WHEN 80 THEN 3
WHEN 81 THEN 1
WHEN 82 THEN 2
WHEN 83 THEN 3
WHEN 84 THEN 1
WHEN 85 THEN 2
WHEN 86 THEN 3
WHEN 87 THEN 1
WHEN 88 THEN 2
WHEN 89 THEN 3
WHEN 90 THEN 1
WHEN 91 THEN 2
WHEN 92 THEN 3
WHEN 93 THEN 1
WHEN 94 THEN 2
WHEN 95 THEN 3
WHEN 96 THEN 1
WHEN 97 THEN 2
WHEN 98 THEN 3
WHEN 99 THEN 1
WHEN 100 THEN 2
ELSE primaryStoreId
END
WHERE id BETWEEN 15 AND 100;
UPDATE users
SET fullName = TRIM(CONCAT_WS(' ', firstName, lastName))
WHERE (fullName IS NULL OR fullName = '')
AND ((firstName IS NOT NULL AND firstName <> '') OR (lastName IS NOT NULL AND lastName <> ''));
UPDATE activityLog al
LEFT JOIN users u ON u.id = al.userId
LEFT JOIN storeLocation s ON s.storeId = al.storeId
SET al.usernameSnapshot = COALESCE(al.usernameSnapshot, u.username),
al.fullNameSnapshot = COALESCE(al.fullNameSnapshot, COALESCE(NULLIF(u.fullName, ''), TRIM(CONCAT_WS(' ', u.firstName, u.lastName)))),
al.roleSnapshot = COALESCE(al.roleSnapshot, u.role),
al.storeNameSnapshot = COALESCE(al.storeNameSnapshot, s.storeName)
WHERE al.usernameSnapshot IS NULL
OR al.fullNameSnapshot IS NULL
OR al.roleSnapshot IS NULL
OR (al.storeId IS NOT NULL AND al.storeNameSnapshot IS NULL);
UPDATE users
SET avatarUrl = REPLACE(avatarUrl, 'https://images.petshop.local/users/', '/uploads/avatars/')
WHERE avatarUrl LIKE 'https://images.petshop.local/users/%';
UPDATE pet
SET imageUrl = REPLACE(imageUrl, 'https://images.petshop.local/pets/', '/uploads/pets/')
WHERE imageUrl LIKE 'https://images.petshop.local/pets/%';
UPDATE product
SET imageUrl = REPLACE(imageUrl, 'https://images.petshop.local/products/', '/uploads/products/')
WHERE imageUrl LIKE 'https://images.petshop.local/products/%';
UPDATE storeLocation
SET imageUrl = REPLACE(imageUrl, 'https://images.petshop.local/stores/', '/stores/')
WHERE imageUrl LIKE 'https://images.petshop.local/stores/%';

View File

@@ -1,21 +0,0 @@
INSERT INTO users (username, password, email, firstName, lastName, fullName, phone, avatarUrl, role, staffRole, primaryStoreId, loyaltyPoints, active, tokenVersion)
SELECT 'ai.bot',
'$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq',
'bot@petshop.com',
'AI',
'Bot',
'AI Bot',
'000-000-0000',
'https://images.petshop.local/users/bot.webp',
'STAFF',
'CUSTOMER_SERVICE',
NULL,
0,
1,
0
FROM DUAL
WHERE NOT EXISTS (
SELECT 1
FROM users
WHERE username = 'ai.bot'
);

View File

@@ -1,7 +0,0 @@
ALTER TABLE activityLog
ADD COLUMN usernameSnapshot VARCHAR(50) NULL,
ADD COLUMN fullNameSnapshot VARCHAR(100) NULL,
ADD COLUMN roleSnapshot VARCHAR(20) NULL,
ADD COLUMN storeNameSnapshot VARCHAR(100) NULL;
CREATE INDEX idx_activity_log_timestamp_id ON activityLog(logTimestamp, logId);

View File

@@ -1,3 +0,0 @@
ALTER TABLE sale
ADD COLUMN pointsUsed INT NOT NULL DEFAULT 0,
ADD COLUMN pointsDiscountAmount DECIMAL(10, 2) NOT NULL DEFAULT 0.00;

View File

@@ -311,6 +311,10 @@ CREATE TABLE IF NOT EXISTS activityLog (
logId BIGINT AUTO_INCREMENT PRIMARY KEY,
userId BIGINT NOT NULL,
storeId BIGINT NULL,
usernameSnapshot VARCHAR(50) NULL,
fullNameSnapshot VARCHAR(100) NULL,
roleSnapshot VARCHAR(20) NULL,
storeNameSnapshot VARCHAR(100) NULL,
activity TEXT NOT NULL,
logTimestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT fk_activity_log_user FOREIGN KEY (userId) REFERENCES users(id),
@@ -343,3 +347,4 @@ CREATE INDEX idx_cart_user ON cart(userId);
CREATE INDEX idx_conversation_customer ON conversation(customerId);
CREATE INDEX idx_conversation_staff ON conversation(staffId);
CREATE INDEX idx_activity_log_store ON activityLog(storeId);
CREATE INDEX idx_activity_log_timestamp_id ON activityLog(logTimestamp, logId);

View File

@@ -69,91 +69,91 @@ INSERT INTO users (id, username, password, email, firstName, lastName, fullName,
(13, 'chloe.martin', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'chloe.martin@petshop.com', 'Chloe', 'Martin', 'Chloe Martin', '403-710-0013', 'https://images.petshop.local/users/013.webp', 'STAFF', 'VETERINARY_TECH', 3, 0, 1, 0),
(14, 'owen.baker', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'owen.baker@petshop.com', 'Owen', 'Baker', 'Owen Baker', '403-710-0014', 'https://images.petshop.local/users/014.webp', 'STAFF', 'GROOMER', 3, 0, 1, 0),
(15, 'customer', '$2y$10$fgIlTHDYUOzvbczwdhQP7..YuAHr2cGODb9OBQJqole3AkiY4CGUq', 'customer@petshop.com', 'Test', 'Customer', 'Test Customer', '000-000-1002', 'https://images.petshop.local/users/015.webp', 'CUSTOMER', 'CUSTOMER', 1, 0, 1, 0),
(16, 'alex.brown', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.brown@gmail.com', 'Alex', 'Brown', 'Alex Brown', '403-730-0016', 'https://images.petshop.local/users/016.webp', 'CUSTOMER', 'CUSTOMER', 2, 12, 1, 0),
(17, 'alex.clark', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.clark@gmail.com', 'Alex', 'Clark', 'Alex Clark', '403-730-0017', 'https://images.petshop.local/users/017.webp', 'CUSTOMER', 'CUSTOMER', 3, 15, 1, 0),
(18, 'alex.wilson', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.wilson@gmail.com', 'Alex', 'Wilson', 'Alex Wilson', '403-730-0018', 'https://images.petshop.local/users/018.webp', 'CUSTOMER', 'CUSTOMER', 1, 2, 1, 0),
(19, 'alex.martinez', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.martinez@gmail.com', 'Alex', 'Martinez', 'Alex Martinez', '403-730-0019', 'https://images.petshop.local/users/019.webp', 'CUSTOMER', 'CUSTOMER', 2, 5, 1, 0),
(20, 'alex.anderson', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.anderson@gmail.com', 'Alex', 'Anderson', 'Alex Anderson', '403-730-0020', 'https://images.petshop.local/users/020.webp', 'CUSTOMER', 'CUSTOMER', 3, 12, 1, 0),
(21, 'alex.taylor', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.taylor@gmail.com', 'Alex', 'Taylor', 'Alex Taylor', '403-730-0021', 'https://images.petshop.local/users/021.webp', 'CUSTOMER', 'CUSTOMER', 1, 11, 1, 0),
(22, 'alex.parker', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.parker@gmail.com', 'Alex', 'Parker', 'Alex Parker', '403-730-0022', 'https://images.petshop.local/users/022.webp', 'CUSTOMER', 'CUSTOMER', 2, 16, 1, 0),
(23, 'alex.evans', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.evans@gmail.com', 'Alex', 'Evans', 'Alex Evans', '403-730-0023', 'https://images.petshop.local/users/023.webp', 'CUSTOMER', 'CUSTOMER', 3, 36, 1, 0),
(24, 'alex.scott', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.scott@gmail.com', 'Alex', 'Scott', 'Alex Scott', '403-730-0024', 'https://images.petshop.local/users/024.webp', 'CUSTOMER', 'CUSTOMER', 1, 5, 1, 0),
(25, 'alex.adams', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.adams@gmail.com', 'Alex', 'Adams', 'Alex Adams', '403-730-0025', 'https://images.petshop.local/users/025.webp', 'CUSTOMER', 'CUSTOMER', 2, 8, 1, 0),
(26, 'alex.baker', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.baker@gmail.com', 'Alex', 'Baker', 'Alex Baker', '403-730-0026', 'https://images.petshop.local/users/026.webp', 'CUSTOMER', 'CUSTOMER', 3, 29, 1, 0),
(27, 'alex.hall', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.hall@gmail.com', 'Alex', 'Hall', 'Alex Hall', '403-730-0027', 'https://images.petshop.local/users/027.webp', 'CUSTOMER', 'CUSTOMER', 1, 3, 1, 0),
(28, 'alex.rivera', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.rivera@gmail.com', 'Alex', 'Rivera', 'Alex Rivera', '403-730-0028', 'https://images.petshop.local/users/028.webp', 'CUSTOMER', 'CUSTOMER', 2, 13, 1, 0),
(29, 'alex.mitchell', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.mitchell@gmail.com', 'Alex', 'Mitchell', 'Alex Mitchell', '403-730-0029', 'https://images.petshop.local/users/029.webp', 'CUSTOMER', 'CUSTOMER', 3, 30, 1, 0),
(30, 'alex.collins', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.collins@gmail.com', 'Alex', 'Collins', 'Alex Collins', '403-730-0030', 'https://images.petshop.local/users/030.webp', 'CUSTOMER', 'CUSTOMER', 1, 16, 1, 0),
(31, 'alex.morris', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.morris@gmail.com', 'Alex', 'Morris', 'Alex Morris', '403-730-0031', 'https://images.petshop.local/users/031.webp', 'CUSTOMER', 'CUSTOMER', 2, 9, 1, 0),
(32, 'alex.cook', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.cook@gmail.com', 'Alex', 'Cook', 'Alex Cook', '403-730-0032', 'https://images.petshop.local/users/032.webp', 'CUSTOMER', 'CUSTOMER', 3, 19, 1, 0),
(33, 'alex.bell', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.bell@gmail.com', 'Alex', 'Bell', 'Alex Bell', '403-730-0033', 'https://images.petshop.local/users/033.webp', 'CUSTOMER', 'CUSTOMER', 1, 2, 1, 0),
(34, 'alex.reed', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.reed@gmail.com', 'Alex', 'Reed', 'Alex Reed', '403-730-0034', 'https://images.petshop.local/users/034.webp', 'CUSTOMER', 'CUSTOMER', 2, 5, 1, 0),
(35, 'alex.murphy', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.murphy@gmail.com', 'Alex', 'Murphy', 'Alex Murphy', '403-730-0035', 'https://images.petshop.local/users/035.webp', 'CUSTOMER', 'CUSTOMER', 3, 31, 1, 0),
(36, 'alex.bailey', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.bailey@gmail.com', 'Alex', 'Bailey', 'Alex Bailey', '403-730-0036', 'https://images.petshop.local/users/036.webp', 'CUSTOMER', 'CUSTOMER', 1, 6, 1, 0),
(37, 'alex.cooper', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.cooper@gmail.com', 'Alex', 'Cooper', 'Alex Cooper', '403-730-0037', 'https://images.petshop.local/users/037.webp', 'CUSTOMER', 'CUSTOMER', 2, 4, 1, 0),
(38, 'alex.richardson', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.richardson@gmail.com', 'Alex', 'Richardson', 'Alex Richardson', '403-730-0038', 'https://images.petshop.local/users/038.webp', 'CUSTOMER', 'CUSTOMER', 3, 19, 1, 0),
(39, 'alex.cox', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.cox@gmail.com', 'Alex', 'Cox', 'Alex Cox', '403-730-0039', 'https://images.petshop.local/users/039.webp', 'CUSTOMER', 'CUSTOMER', 1, 4, 1, 0),
(40, 'alex.howard', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.howard@gmail.com', 'Alex', 'Howard', 'Alex Howard', '403-730-0040', 'https://images.petshop.local/users/040.webp', 'CUSTOMER', 'CUSTOMER', 2, 12, 1, 0),
(41, 'alex.ward', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.ward@gmail.com', 'Alex', 'Ward', 'Alex Ward', '403-730-0041', 'https://images.petshop.local/users/041.webp', 'CUSTOMER', 'CUSTOMER', 3, 18, 1, 0),
(42, 'alex.torres', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.torres@gmail.com', 'Alex', 'Torres', 'Alex Torres', '403-730-0042', 'https://images.petshop.local/users/042.webp', 'CUSTOMER', 'CUSTOMER', 1, 10, 1, 0),
(43, 'alex.peterson', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.peterson@gmail.com', 'Alex', 'Peterson', 'Alex Peterson', '403-730-0043', 'https://images.petshop.local/users/043.webp', 'CUSTOMER', 'CUSTOMER', 2, 6, 1, 0),
(44, 'alex.gray', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.gray@gmail.com', 'Alex', 'Gray', 'Alex Gray', '403-730-0044', 'https://images.petshop.local/users/044.webp', 'CUSTOMER', 'CUSTOMER', 3, 11, 1, 0),
(45, 'alex.ramirez', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.ramirez@gmail.com', 'Alex', 'Ramirez', 'Alex Ramirez', '403-730-0045', 'https://images.petshop.local/users/045.webp', 'CUSTOMER', 'CUSTOMER', 1, 5, 1, 0),
(46, 'alex.james', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.james@gmail.com', 'Alex', 'James', 'Alex James', '403-730-0046', 'https://images.petshop.local/users/046.webp', 'CUSTOMER', 'CUSTOMER', 2, 28, 1, 0),
(47, 'alex.watson', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.watson@gmail.com', 'Alex', 'Watson', 'Alex Watson', '403-730-0047', 'https://images.petshop.local/users/047.webp', 'CUSTOMER', 'CUSTOMER', 3, 8, 1, 0),
(48, 'alex.brooks', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.brooks@gmail.com', 'Alex', 'Brooks', 'Alex Brooks', '403-730-0048', 'https://images.petshop.local/users/048.webp', 'CUSTOMER', 'CUSTOMER', 1, 2, 1, 0),
(49, 'alex.kelly', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.kelly@gmail.com', 'Alex', 'Kelly', 'Alex Kelly', '403-730-0049', 'https://images.petshop.local/users/049.webp', 'CUSTOMER', 'CUSTOMER', 2, 16, 1, 0),
(50, 'alex.sanders', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.sanders@gmail.com', 'Alex', 'Sanders', 'Alex Sanders', '403-730-0050', 'https://images.petshop.local/users/050.webp', 'CUSTOMER', 'CUSTOMER', 3, 21, 1, 0),
(51, 'alex.price', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.price@gmail.com', 'Alex', 'Price', 'Alex Price', '403-730-0051', 'https://images.petshop.local/users/051.webp', 'CUSTOMER', 'CUSTOMER', 1, 7, 1, 0),
(52, 'alex.bennett', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.bennett@gmail.com', 'Alex', 'Bennett', 'Alex Bennett', '403-730-0052', 'https://images.petshop.local/users/052.webp', 'CUSTOMER', 'CUSTOMER', 2, 17, 1, 0),
(53, 'alex.wood', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.wood@gmail.com', 'Alex', 'Wood', 'Alex Wood', '403-730-0053', 'https://images.petshop.local/users/053.webp', 'CUSTOMER', 'CUSTOMER', 3, 10, 1, 0),
(54, 'alex.barnes', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.barnes@gmail.com', 'Alex', 'Barnes', 'Alex Barnes', '403-730-0054', 'https://images.petshop.local/users/054.webp', 'CUSTOMER', 'CUSTOMER', 1, 2, 1, 0),
(55, 'alex.ross', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.ross@gmail.com', 'Alex', 'Ross', 'Alex Ross', '403-730-0055', 'https://images.petshop.local/users/055.webp', 'CUSTOMER', 'CUSTOMER', 2, 7, 1, 0),
(56, 'alex.henderson', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.henderson@gmail.com', 'Alex', 'Henderson', 'Alex Henderson', '403-730-0056', 'https://images.petshop.local/users/056.webp', 'CUSTOMER', 'CUSTOMER', 3, 15, 1, 0),
(57, 'alex.coleman', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.coleman@gmail.com', 'Alex', 'Coleman', 'Alex Coleman', '403-730-0057', 'https://images.petshop.local/users/057.webp', 'CUSTOMER', 'CUSTOMER', 1, 2, 1, 0),
(58, 'alex.jenkins', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.jenkins@gmail.com', 'Alex', 'Jenkins', 'Alex Jenkins', '403-730-0058', 'https://images.petshop.local/users/058.webp', 'CUSTOMER', 'CUSTOMER', 2, 17, 1, 0),
(59, 'alex.perry', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.perry@gmail.com', 'Alex', 'Perry', 'Alex Perry', '403-730-0059', 'https://images.petshop.local/users/059.webp', 'CUSTOMER', 'CUSTOMER', 3, 15, 1, 0),
(60, 'alex.powell', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.powell@gmail.com', 'Alex', 'Powell', 'Alex Powell', '403-730-0060', 'https://images.petshop.local/users/060.webp', 'CUSTOMER', 'CUSTOMER', 1, 4, 1, 0),
(61, 'alex.long', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.long@gmail.com', 'Alex', 'Long', 'Alex Long', '403-730-0061', 'https://images.petshop.local/users/061.webp', 'CUSTOMER', 'CUSTOMER', 2, 13, 1, 0),
(62, 'alex.patterson', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.patterson@gmail.com', 'Alex', 'Patterson', 'Alex Patterson', '403-730-0062', 'https://images.petshop.local/users/062.webp', 'CUSTOMER', 'CUSTOMER', 3, 26, 1, 0),
(63, 'alex.hughes', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.hughes@gmail.com', 'Alex', 'Hughes', 'Alex Hughes', '403-730-0063', 'https://images.petshop.local/users/063.webp', 'CUSTOMER', 'CUSTOMER', 1, 5, 1, 0),
(64, 'alex.flores', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.flores@gmail.com', 'Alex', 'Flores', 'Alex Flores', '403-730-0064', 'https://images.petshop.local/users/064.webp', 'CUSTOMER', 'CUSTOMER', 2, 9, 1, 0),
(65, 'alex.washington', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.washington@gmail.com', 'Alex', 'Washington', 'Alex Washington', '403-730-0065', 'https://images.petshop.local/users/065.webp', 'CUSTOMER', 'CUSTOMER', 3, 22, 1, 0),
(66, 'alex.butler', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.butler@gmail.com', 'Alex', 'Butler', 'Alex Butler', '403-730-0066', 'https://images.petshop.local/users/066.webp', 'CUSTOMER', 'CUSTOMER', 1, 5, 1, 0),
(67, 'alex.simmons', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.simmons@gmail.com', 'Alex', 'Simmons', 'Alex Simmons', '403-730-0067', 'https://images.petshop.local/users/067.webp', 'CUSTOMER', 'CUSTOMER', 2, 5, 1, 0),
(68, 'alex.foster', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.foster@gmail.com', 'Alex', 'Foster', 'Alex Foster', '403-730-0068', 'https://images.petshop.local/users/068.webp', 'CUSTOMER', 'CUSTOMER', 3, 17, 1, 0),
(69, 'alex.gonzales', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.gonzales@gmail.com', 'Alex', 'Gonzales', 'Alex Gonzales', '403-730-0069', 'https://images.petshop.local/users/069.webp', 'CUSTOMER', 'CUSTOMER', 1, 15, 1, 0),
(70, 'alex.bryant', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.bryant@gmail.com', 'Alex', 'Bryant', 'Alex Bryant', '403-730-0070', 'https://images.petshop.local/users/070.webp', 'CUSTOMER', 'CUSTOMER', 2, 19, 1, 0),
(71, 'alex.alexander', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.alexander@gmail.com', 'Alex', 'Alexander', 'Alex Alexander', '403-730-0071', 'https://images.petshop.local/users/071.webp', 'CUSTOMER', 'CUSTOMER', 3, 13, 1, 0),
(72, 'alex.russell', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.russell@gmail.com', 'Alex', 'Russell', 'Alex Russell', '403-730-0072', 'https://images.petshop.local/users/072.webp', 'CUSTOMER', 'CUSTOMER', 1, 7, 1, 0),
(73, 'alex.griffin', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.griffin@gmail.com', 'Alex', 'Griffin', 'Alex Griffin', '403-730-0073', 'https://images.petshop.local/users/073.webp', 'CUSTOMER', 'CUSTOMER', 2, 2, 1, 0),
(74, 'alex.diaz', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.diaz@gmail.com', 'Alex', 'Diaz', 'Alex Diaz', '403-730-0074', 'https://images.petshop.local/users/074.webp', 'CUSTOMER', 'CUSTOMER', 3, 10, 1, 0),
(75, 'alex.hayes', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.hayes@gmail.com', 'Alex', 'Hayes', 'Alex Hayes', '403-730-0075', 'https://images.petshop.local/users/075.webp', 'CUSTOMER', 'CUSTOMER', 1, 7, 1, 0),
(76, 'alex.myers', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.myers@gmail.com', 'Alex', 'Myers', 'Alex Myers', '403-730-0076', 'https://images.petshop.local/users/076.webp', 'CUSTOMER', 'CUSTOMER', 2, 13, 1, 0),
(77, 'alex.ford', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.ford@gmail.com', 'Alex', 'Ford', 'Alex Ford', '403-730-0077', 'https://images.petshop.local/users/077.webp', 'CUSTOMER', 'CUSTOMER', 3, 13, 1, 0),
(78, 'alex.hamilton', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.hamilton@gmail.com', 'Alex', 'Hamilton', 'Alex Hamilton', '403-730-0078', 'https://images.petshop.local/users/078.webp', 'CUSTOMER', 'CUSTOMER', 1, 2, 1, 0),
(79, 'alex.graham', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.graham@gmail.com', 'Alex', 'Graham', 'Alex Graham', '403-730-0079', 'https://images.petshop.local/users/079.webp', 'CUSTOMER', 'CUSTOMER', 2, 5, 1, 0),
(80, 'alex.sullivan', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.sullivan@gmail.com', 'Alex', 'Sullivan', 'Alex Sullivan', '403-730-0080', 'https://images.petshop.local/users/080.webp', 'CUSTOMER', 'CUSTOMER', 3, 12, 1, 0),
(81, 'alex.wallace', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.wallace@gmail.com', 'Alex', 'Wallace', 'Alex Wallace', '403-730-0081', 'https://images.petshop.local/users/081.webp', 'CUSTOMER', 'CUSTOMER', 1, 11, 1, 0),
(82, 'alex.woods', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.woods@gmail.com', 'Alex', 'Woods', 'Alex Woods', '403-730-0082', 'https://images.petshop.local/users/082.webp', 'CUSTOMER', 'CUSTOMER', 2, 17, 1, 0),
(83, 'alex.cole', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.cole@gmail.com', 'Alex', 'Cole', 'Alex Cole', '403-730-0083', 'https://images.petshop.local/users/083.webp', 'CUSTOMER', 'CUSTOMER', 3, 36, 1, 0),
(84, 'alex.west', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.west@gmail.com', 'Alex', 'West', 'Alex West', '403-730-0084', 'https://images.petshop.local/users/084.webp', 'CUSTOMER', 'CUSTOMER', 1, 5, 1, 0),
(85, 'alex.jordan', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.jordan@gmail.com', 'Alex', 'Jordan', 'Alex Jordan', '403-730-0085', 'https://images.petshop.local/users/085.webp', 'CUSTOMER', 'CUSTOMER', 2, 9, 1, 0),
(86, 'alex.owens', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.owens@gmail.com', 'Alex', 'Owens', 'Alex Owens', '403-730-0086', 'https://images.petshop.local/users/086.webp', 'CUSTOMER', 'CUSTOMER', 3, 26, 1, 0),
(87, 'alex.reynolds', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.reynolds@gmail.com', 'Alex', 'Reynolds', 'Alex Reynolds', '403-730-0087', 'https://images.petshop.local/users/087.webp', 'CUSTOMER', 'CUSTOMER', 1, 3, 1, 0),
(88, 'alex.fisher', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.fisher@gmail.com', 'Alex', 'Fisher', 'Alex Fisher', '403-730-0088', 'https://images.petshop.local/users/088.webp', 'CUSTOMER', 'CUSTOMER', 2, 11, 1, 0),
(89, 'alex.ellis', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.ellis@gmail.com', 'Alex', 'Ellis', 'Alex Ellis', '403-730-0089', 'https://images.petshop.local/users/089.webp', 'CUSTOMER', 'CUSTOMER', 3, 30, 1, 0),
(90, 'alex.harrison', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.harrison@gmail.com', 'Alex', 'Harrison', 'Alex Harrison', '403-730-0090', 'https://images.petshop.local/users/090.webp', 'CUSTOMER', 'CUSTOMER', 1, 16, 1, 0),
(91, 'alex.gibson', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.gibson@gmail.com', 'Alex', 'Gibson', 'Alex Gibson', '403-730-0091', 'https://images.petshop.local/users/091.webp', 'CUSTOMER', 'CUSTOMER', 2, 9, 1, 0),
(92, 'alex.mcdonald', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.mcdonald@gmail.com', 'Alex', 'Mcdonald', 'Alex Mcdonald', '403-730-0092', 'https://images.petshop.local/users/092.webp', 'CUSTOMER', 'CUSTOMER', 3, 19, 1, 0),
(93, 'alex.cruz', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.cruz@gmail.com', 'Alex', 'Cruz', 'Alex Cruz', '403-730-0093', 'https://images.petshop.local/users/093.webp', 'CUSTOMER', 'CUSTOMER', 1, 2, 1, 0),
(94, 'alex.marshall', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.marshall@gmail.com', 'Alex', 'Marshall', 'Alex Marshall', '403-730-0094', 'https://images.petshop.local/users/094.webp', 'CUSTOMER', 'CUSTOMER', 2, 5, 1, 0),
(95, 'alex.ortiz', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.ortiz@gmail.com', 'Alex', 'Ortiz', 'Alex Ortiz', '403-730-0095', 'https://images.petshop.local/users/095.webp', 'CUSTOMER', 'CUSTOMER', 3, 30, 1, 0),
(96, 'alex.gomez', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.gomez@gmail.com', 'Alex', 'Gomez', 'Alex Gomez', '403-730-0096', 'https://images.petshop.local/users/096.webp', 'CUSTOMER', 'CUSTOMER', 1, 6, 1, 0),
(97, 'alex.murray', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.murray@gmail.com', 'Alex', 'Murray', 'Alex Murray', '403-730-0097', 'https://images.petshop.local/users/097.webp', 'CUSTOMER', 'CUSTOMER', 2, 4, 1, 0),
(98, 'alex.freeman', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.freeman@gmail.com', 'Alex', 'Freeman', 'Alex Freeman', '403-730-0098', 'https://images.petshop.local/users/098.webp', 'CUSTOMER', 'CUSTOMER', 3, 0, 1, 0),
(99, 'alex.wells', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.wells@gmail.com', 'Alex', 'Wells', 'Alex Wells', '403-730-0099', 'https://images.petshop.local/users/099.webp', 'CUSTOMER', 'CUSTOMER', 1, 0, 1, 0),
(100, 'alex.webb', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alex.webb@gmail.com', 'Alex', 'Webb', 'Alex Webb', '403-730-0100', 'https://images.petshop.local/users/100.webp', 'CUSTOMER', 'CUSTOMER', 2, 0, 1, 0),
(16, 'maya.brown', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'maya.brown@gmail.com', 'Maya', 'Brown', 'Maya Brown', '403-730-0016', 'https://images.petshop.local/users/016.webp', 'CUSTOMER', 'CUSTOMER', 2, 12, 1, 0),
(17, 'noah.clark', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'noah.clark@gmail.com', 'Noah', 'Clark', 'Noah Clark', '403-730-0017', 'https://images.petshop.local/users/017.webp', 'CUSTOMER', 'CUSTOMER', 3, 15, 1, 0),
(18, 'avery.wilson', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'avery.wilson@gmail.com', 'Avery', 'Wilson', 'Avery Wilson', '403-730-0018', 'https://images.petshop.local/users/018.webp', 'CUSTOMER', 'CUSTOMER', 1, 2, 1, 0),
(19, 'leah.martinez', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'leah.martinez@gmail.com', 'Leah', 'Martinez', 'Leah Martinez', '403-730-0019', 'https://images.petshop.local/users/019.webp', 'CUSTOMER', 'CUSTOMER', 2, 5, 1, 0),
(20, 'julian.anderson', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'julian.anderson@gmail.com', 'Julian', 'Anderson', 'Julian Anderson', '403-730-0020', 'https://images.petshop.local/users/020.webp', 'CUSTOMER', 'CUSTOMER', 3, 12, 1, 0),
(21, 'zoe.taylor', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'zoe.taylor@gmail.com', 'Zoe', 'Taylor', 'Zoe Taylor', '403-730-0021', 'https://images.petshop.local/users/021.webp', 'CUSTOMER', 'CUSTOMER', 1, 11, 1, 0),
(22, 'ethan.parker', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'ethan.parker@gmail.com', 'Ethan', 'Parker', 'Ethan Parker', '403-730-0022', 'https://images.petshop.local/users/022.webp', 'CUSTOMER', 'CUSTOMER', 2, 16, 1, 0),
(23, 'ruby.evans', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'ruby.evans@gmail.com', 'Ruby', 'Evans', 'Ruby Evans', '403-730-0023', 'https://images.petshop.local/users/023.webp', 'CUSTOMER', 'CUSTOMER', 3, 36, 1, 0),
(24, 'caleb.scott', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'caleb.scott@gmail.com', 'Caleb', 'Scott', 'Caleb Scott', '403-730-0024', 'https://images.petshop.local/users/024.webp', 'CUSTOMER', 'CUSTOMER', 1, 5, 1, 0),
(25, 'ivy.adams', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'ivy.adams@gmail.com', 'Ivy', 'Adams', 'Ivy Adams', '403-730-0025', 'https://images.petshop.local/users/025.webp', 'CUSTOMER', 'CUSTOMER', 2, 8, 1, 0),
(26, 'isaac.baker', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'isaac.baker@gmail.com', 'Isaac', 'Baker', 'Isaac Baker', '403-730-0026', 'https://images.petshop.local/users/026.webp', 'CUSTOMER', 'CUSTOMER', 3, 29, 1, 0),
(27, 'hannah.hall', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'hannah.hall@gmail.com', 'Hannah', 'Hall', 'Hannah Hall', '403-730-0027', 'https://images.petshop.local/users/027.webp', 'CUSTOMER', 'CUSTOMER', 1, 3, 1, 0),
(28, 'mason.rivera', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'mason.rivera@gmail.com', 'Mason', 'Rivera', 'Mason Rivera', '403-730-0028', 'https://images.petshop.local/users/028.webp', 'CUSTOMER', 'CUSTOMER', 2, 13, 1, 0),
(29, 'aria.mitchell', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'aria.mitchell@gmail.com', 'Aria', 'Mitchell', 'Aria Mitchell', '403-730-0029', 'https://images.petshop.local/users/029.webp', 'CUSTOMER', 'CUSTOMER', 3, 30, 1, 0),
(30, 'wyatt.collins', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'wyatt.collins@gmail.com', 'Wyatt', 'Collins', 'Wyatt Collins', '403-730-0030', 'https://images.petshop.local/users/030.webp', 'CUSTOMER', 'CUSTOMER', 1, 16, 1, 0),
(31, 'elena.morris', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'elena.morris@gmail.com', 'Elena', 'Morris', 'Elena Morris', '403-730-0031', 'https://images.petshop.local/users/031.webp', 'CUSTOMER', 'CUSTOMER', 2, 9, 1, 0),
(32, 'leo.cook', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'leo.cook@gmail.com', 'Leo', 'Cook', 'Leo Cook', '403-730-0032', 'https://images.petshop.local/users/032.webp', 'CUSTOMER', 'CUSTOMER', 3, 19, 1, 0),
(33, 'grace.bell', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'grace.bell@gmail.com', 'Grace', 'Bell', 'Grace Bell', '403-730-0033', 'https://images.petshop.local/users/033.webp', 'CUSTOMER', 'CUSTOMER', 1, 2, 1, 0),
(34, 'hudson.reed', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'hudson.reed@gmail.com', 'Hudson', 'Reed', 'Hudson Reed', '403-730-0034', 'https://images.petshop.local/users/034.webp', 'CUSTOMER', 'CUSTOMER', 2, 5, 1, 0),
(35, 'claire.murphy', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'claire.murphy@gmail.com', 'Claire', 'Murphy', 'Claire Murphy', '403-730-0035', 'https://images.petshop.local/users/035.webp', 'CUSTOMER', 'CUSTOMER', 3, 31, 1, 0),
(36, 'omar.bailey', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'omar.bailey@gmail.com', 'Omar', 'Bailey', 'Omar Bailey', '403-730-0036', 'https://images.petshop.local/users/036.webp', 'CUSTOMER', 'CUSTOMER', 1, 6, 1, 0),
(37, 'naomi.cooper', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'naomi.cooper@gmail.com', 'Naomi', 'Cooper', 'Naomi Cooper', '403-730-0037', 'https://images.petshop.local/users/037.webp', 'CUSTOMER', 'CUSTOMER', 2, 4, 1, 0),
(38, 'jasper.richardson', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'jasper.richardson@gmail.com', 'Jasper', 'Richardson', 'Jasper Richardson', '403-730-0038', 'https://images.petshop.local/users/038.webp', 'CUSTOMER', 'CUSTOMER', 3, 19, 1, 0),
(39, 'sofia.cox', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'sofia.cox@gmail.com', 'Sofia', 'Cox', 'Sofia Cox', '403-730-0039', 'https://images.petshop.local/users/039.webp', 'CUSTOMER', 'CUSTOMER', 1, 4, 1, 0),
(40, 'miles.howard', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'miles.howard@gmail.com', 'Miles', 'Howard', 'Miles Howard', '403-730-0040', 'https://images.petshop.local/users/040.webp', 'CUSTOMER', 'CUSTOMER', 2, 12, 1, 0),
(41, 'audrey.ward', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'audrey.ward@gmail.com', 'Audrey', 'Ward', 'Audrey Ward', '403-730-0041', 'https://images.petshop.local/users/041.webp', 'CUSTOMER', 'CUSTOMER', 3, 18, 1, 0),
(42, 'nathan.torres', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'nathan.torres@gmail.com', 'Nathan', 'Torres', 'Nathan Torres', '403-730-0042', 'https://images.petshop.local/users/042.webp', 'CUSTOMER', 'CUSTOMER', 1, 10, 1, 0),
(43, 'jade.peterson', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'jade.peterson@gmail.com', 'Jade', 'Peterson', 'Jade Peterson', '403-730-0043', 'https://images.petshop.local/users/043.webp', 'CUSTOMER', 'CUSTOMER', 2, 6, 1, 0),
(44, 'rowan.gray', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'rowan.gray@gmail.com', 'Rowan', 'Gray', 'Rowan Gray', '403-730-0044', 'https://images.petshop.local/users/044.webp', 'CUSTOMER', 'CUSTOMER', 3, 11, 1, 0),
(45, 'lila.ramirez', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'lila.ramirez@gmail.com', 'Lila', 'Ramirez', 'Lila Ramirez', '403-730-0045', 'https://images.petshop.local/users/045.webp', 'CUSTOMER', 'CUSTOMER', 1, 5, 1, 0),
(46, 'eli.james', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'eli.james@gmail.com', 'Eli', 'James', 'Eli James', '403-730-0046', 'https://images.petshop.local/users/046.webp', 'CUSTOMER', 'CUSTOMER', 2, 28, 1, 0),
(47, 'violet.watson', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'violet.watson@gmail.com', 'Violet', 'Watson', 'Violet Watson', '403-730-0047', 'https://images.petshop.local/users/047.webp', 'CUSTOMER', 'CUSTOMER', 3, 8, 1, 0),
(48, 'gavin.brooks', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'gavin.brooks@gmail.com', 'Gavin', 'Brooks', 'Gavin Brooks', '403-730-0048', 'https://images.petshop.local/users/048.webp', 'CUSTOMER', 'CUSTOMER', 1, 2, 1, 0),
(49, 'stella.kelly', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'stella.kelly@gmail.com', 'Stella', 'Kelly', 'Stella Kelly', '403-730-0049', 'https://images.petshop.local/users/049.webp', 'CUSTOMER', 'CUSTOMER', 2, 16, 1, 0),
(50, 'adrian.sanders', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'adrian.sanders@gmail.com', 'Adrian', 'Sanders', 'Adrian Sanders', '403-730-0050', 'https://images.petshop.local/users/050.webp', 'CUSTOMER', 'CUSTOMER', 3, 21, 1, 0),
(51, 'hazel.price', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'hazel.price@gmail.com', 'Hazel', 'Price', 'Hazel Price', '403-730-0051', 'https://images.petshop.local/users/051.webp', 'CUSTOMER', 'CUSTOMER', 1, 7, 1, 0),
(52, 'connor.bennett', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'connor.bennett@gmail.com', 'Connor', 'Bennett', 'Connor Bennett', '403-730-0052', 'https://images.petshop.local/users/052.webp', 'CUSTOMER', 'CUSTOMER', 2, 17, 1, 0),
(53, 'sadie.wood', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'sadie.wood@gmail.com', 'Sadie', 'Wood', 'Sadie Wood', '403-730-0053', 'https://images.petshop.local/users/053.webp', 'CUSTOMER', 'CUSTOMER', 3, 10, 1, 0),
(54, 'xavier.barnes', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'xavier.barnes@gmail.com', 'Xavier', 'Barnes', 'Xavier Barnes', '403-730-0054', 'https://images.petshop.local/users/054.webp', 'CUSTOMER', 'CUSTOMER', 1, 2, 1, 0),
(55, 'alice.ross', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'alice.ross@gmail.com', 'Alice', 'Ross', 'Alice Ross', '403-730-0055', 'https://images.petshop.local/users/055.webp', 'CUSTOMER', 'CUSTOMER', 2, 7, 1, 0),
(56, 'roman.henderson', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'roman.henderson@gmail.com', 'Roman', 'Henderson', 'Roman Henderson', '403-730-0056', 'https://images.petshop.local/users/056.webp', 'CUSTOMER', 'CUSTOMER', 3, 15, 1, 0),
(57, 'lucy.coleman', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'lucy.coleman@gmail.com', 'Lucy', 'Coleman', 'Lucy Coleman', '403-730-0057', 'https://images.petshop.local/users/057.webp', 'CUSTOMER', 'CUSTOMER', 1, 2, 1, 0),
(58, 'evan.jenkins', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'evan.jenkins@gmail.com', 'Evan', 'Jenkins', 'Evan Jenkins', '403-730-0058', 'https://images.petshop.local/users/058.webp', 'CUSTOMER', 'CUSTOMER', 2, 17, 1, 0),
(59, 'mila.perry', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'mila.perry@gmail.com', 'Mila', 'Perry', 'Mila Perry', '403-730-0059', 'https://images.petshop.local/users/059.webp', 'CUSTOMER', 'CUSTOMER', 3, 15, 1, 0),
(60, 'cole.powell', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'cole.powell@gmail.com', 'Cole', 'Powell', 'Cole Powell', '403-730-0060', 'https://images.petshop.local/users/060.webp', 'CUSTOMER', 'CUSTOMER', 1, 4, 1, 0),
(61, 'nora.long', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'nora.long@gmail.com', 'Nora', 'Long', 'Nora Long', '403-730-0061', 'https://images.petshop.local/users/061.webp', 'CUSTOMER', 'CUSTOMER', 2, 13, 1, 0),
(62, 'adam.patterson', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'adam.patterson@gmail.com', 'Adam', 'Patterson', 'Adam Patterson', '403-730-0062', 'https://images.petshop.local/users/062.webp', 'CUSTOMER', 'CUSTOMER', 3, 26, 1, 0),
(63, 'layla.hughes', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'layla.hughes@gmail.com', 'Layla', 'Hughes', 'Layla Hughes', '403-730-0063', 'https://images.petshop.local/users/063.webp', 'CUSTOMER', 'CUSTOMER', 1, 5, 1, 0),
(64, 'blake.flores', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'blake.flores@gmail.com', 'Blake', 'Flores', 'Blake Flores', '403-730-0064', 'https://images.petshop.local/users/064.webp', 'CUSTOMER', 'CUSTOMER', 2, 9, 1, 0),
(65, 'ellie.washington', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'ellie.washington@gmail.com', 'Ellie', 'Washington', 'Ellie Washington', '403-730-0065', 'https://images.petshop.local/users/065.webp', 'CUSTOMER', 'CUSTOMER', 3, 22, 1, 0),
(66, 'ryan.butler', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'ryan.butler@gmail.com', 'Ryan', 'Butler', 'Ryan Butler', '403-730-0066', 'https://images.petshop.local/users/066.webp', 'CUSTOMER', 'CUSTOMER', 1, 5, 1, 0),
(67, 'cora.simmons', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'cora.simmons@gmail.com', 'Cora', 'Simmons', 'Cora Simmons', '403-730-0067', 'https://images.petshop.local/users/067.webp', 'CUSTOMER', 'CUSTOMER', 2, 5, 1, 0),
(68, 'simon.foster', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'simon.foster@gmail.com', 'Simon', 'Foster', 'Simon Foster', '403-730-0068', 'https://images.petshop.local/users/068.webp', 'CUSTOMER', 'CUSTOMER', 3, 17, 1, 0),
(69, 'piper.gonzales', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'piper.gonzales@gmail.com', 'Piper', 'Gonzales', 'Piper Gonzales', '403-730-0069', 'https://images.petshop.local/users/069.webp', 'CUSTOMER', 'CUSTOMER', 1, 15, 1, 0),
(70, 'joel.bryant', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'joel.bryant@gmail.com', 'Joel', 'Bryant', 'Joel Bryant', '403-730-0070', 'https://images.petshop.local/users/070.webp', 'CUSTOMER', 'CUSTOMER', 2, 19, 1, 0),
(71, 'eva.alexander', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'eva.alexander@gmail.com', 'Eva', 'Alexander', 'Eva Alexander', '403-730-0071', 'https://images.petshop.local/users/071.webp', 'CUSTOMER', 'CUSTOMER', 3, 13, 1, 0),
(72, 'felix.russell', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'felix.russell@gmail.com', 'Felix', 'Russell', 'Felix Russell', '403-730-0072', 'https://images.petshop.local/users/072.webp', 'CUSTOMER', 'CUSTOMER', 1, 7, 1, 0),
(73, 'maeve.griffin', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'maeve.griffin@gmail.com', 'Maeve', 'Griffin', 'Maeve Griffin', '403-730-0073', 'https://images.petshop.local/users/073.webp', 'CUSTOMER', 'CUSTOMER', 2, 2, 1, 0),
(74, 'tristan.diaz', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'tristan.diaz@gmail.com', 'Tristan', 'Diaz', 'Tristan Diaz', '403-730-0074', 'https://images.petshop.local/users/074.webp', 'CUSTOMER', 'CUSTOMER', 3, 10, 1, 0),
(75, 'ariana.hayes', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'ariana.hayes@gmail.com', 'Ariana', 'Hayes', 'Ariana Hayes', '403-730-0075', 'https://images.petshop.local/users/075.webp', 'CUSTOMER', 'CUSTOMER', 1, 7, 1, 0),
(76, 'declan.myers', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'declan.myers@gmail.com', 'Declan', 'Myers', 'Declan Myers', '403-730-0076', 'https://images.petshop.local/users/076.webp', 'CUSTOMER', 'CUSTOMER', 2, 13, 1, 0),
(77, 'brooke.ford', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'brooke.ford@gmail.com', 'Brooke', 'Ford', 'Brooke Ford', '403-730-0077', 'https://images.petshop.local/users/077.webp', 'CUSTOMER', 'CUSTOMER', 3, 13, 1, 0),
(78, 'micah.hamilton', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'micah.hamilton@gmail.com', 'Micah', 'Hamilton', 'Micah Hamilton', '403-730-0078', 'https://images.petshop.local/users/078.webp', 'CUSTOMER', 'CUSTOMER', 1, 2, 1, 0),
(79, 'bianca.graham', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'bianca.graham@gmail.com', 'Bianca', 'Graham', 'Bianca Graham', '403-730-0079', 'https://images.petshop.local/users/079.webp', 'CUSTOMER', 'CUSTOMER', 2, 5, 1, 0),
(80, 'jonah.sullivan', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'jonah.sullivan@gmail.com', 'Jonah', 'Sullivan', 'Jonah Sullivan', '403-730-0080', 'https://images.petshop.local/users/080.webp', 'CUSTOMER', 'CUSTOMER', 3, 12, 1, 0),
(81, 'tessa.wallace', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'tessa.wallace@gmail.com', 'Tessa', 'Wallace', 'Tessa Wallace', '403-730-0081', 'https://images.petshop.local/users/081.webp', 'CUSTOMER', 'CUSTOMER', 1, 11, 1, 0),
(82, 'damian.woods', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'damian.woods@gmail.com', 'Damian', 'Woods', 'Damian Woods', '403-730-0082', 'https://images.petshop.local/users/082.webp', 'CUSTOMER', 'CUSTOMER', 2, 17, 1, 0),
(83, 'riley.cole', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'riley.cole@gmail.com', 'Riley', 'Cole', 'Riley Cole', '403-730-0083', 'https://images.petshop.local/users/083.webp', 'CUSTOMER', 'CUSTOMER', 3, 36, 1, 0),
(84, 'kieran.west', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'kieran.west@gmail.com', 'Kieran', 'West', 'Kieran West', '403-730-0084', 'https://images.petshop.local/users/084.webp', 'CUSTOMER', 'CUSTOMER', 1, 5, 1, 0),
(85, 'sienna.jordan', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'sienna.jordan@gmail.com', 'Sienna', 'Jordan', 'Sienna Jordan', '403-730-0085', 'https://images.petshop.local/users/085.webp', 'CUSTOMER', 'CUSTOMER', 2, 9, 1, 0),
(86, 'finley.owens', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'finley.owens@gmail.com', 'Finley', 'Owens', 'Finley Owens', '403-730-0086', 'https://images.petshop.local/users/086.webp', 'CUSTOMER', 'CUSTOMER', 3, 26, 1, 0),
(87, 'maren.reynolds', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'maren.reynolds@gmail.com', 'Maren', 'Reynolds', 'Maren Reynolds', '403-730-0087', 'https://images.petshop.local/users/087.webp', 'CUSTOMER', 'CUSTOMER', 1, 3, 1, 0),
(88, 'asher.fisher', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'asher.fisher@gmail.com', 'Asher', 'Fisher', 'Asher Fisher', '403-730-0088', 'https://images.petshop.local/users/088.webp', 'CUSTOMER', 'CUSTOMER', 2, 11, 1, 0),
(89, 'daphne.ellis', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'daphne.ellis@gmail.com', 'Daphne', 'Ellis', 'Daphne Ellis', '403-730-0089', 'https://images.petshop.local/users/089.webp', 'CUSTOMER', 'CUSTOMER', 3, 30, 1, 0),
(90, 'bennett.harrison', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'bennett.harrison@gmail.com', 'Bennett', 'Harrison', 'Bennett Harrison', '403-730-0090', 'https://images.petshop.local/users/090.webp', 'CUSTOMER', 'CUSTOMER', 1, 16, 1, 0),
(91, 'selena.gibson', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'selena.gibson@gmail.com', 'Selena', 'Gibson', 'Selena Gibson', '403-730-0091', 'https://images.petshop.local/users/091.webp', 'CUSTOMER', 'CUSTOMER', 2, 9, 1, 0),
(92, 'emmett.mcdonald', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'emmett.mcdonald@gmail.com', 'Emmett', 'Mcdonald', 'Emmett Mcdonald', '403-730-0092', 'https://images.petshop.local/users/092.webp', 'CUSTOMER', 'CUSTOMER', 3, 19, 1, 0),
(93, 'phoebe.cruz', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'phoebe.cruz@gmail.com', 'Phoebe', 'Cruz', 'Phoebe Cruz', '403-730-0093', 'https://images.petshop.local/users/093.webp', 'CUSTOMER', 'CUSTOMER', 1, 2, 1, 0),
(94, 'sawyer.marshall', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'sawyer.marshall@gmail.com', 'Sawyer', 'Marshall', 'Sawyer Marshall', '403-730-0094', 'https://images.petshop.local/users/094.webp', 'CUSTOMER', 'CUSTOMER', 2, 5, 1, 0),
(95, 'keira.ortiz', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'keira.ortiz@gmail.com', 'Keira', 'Ortiz', 'Keira Ortiz', '403-730-0095', 'https://images.petshop.local/users/095.webp', 'CUSTOMER', 'CUSTOMER', 3, 30, 1, 0),
(96, 'landon.gomez', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'landon.gomez@gmail.com', 'Landon', 'Gomez', 'Landon Gomez', '403-730-0096', 'https://images.petshop.local/users/096.webp', 'CUSTOMER', 'CUSTOMER', 1, 6, 1, 0),
(97, 'rosalie.murray', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'rosalie.murray@gmail.com', 'Rosalie', 'Murray', 'Rosalie Murray', '403-730-0097', 'https://images.petshop.local/users/097.webp', 'CUSTOMER', 'CUSTOMER', 2, 4, 1, 0),
(98, 'malik.freeman', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'malik.freeman@gmail.com', 'Malik', 'Freeman', 'Malik Freeman', '403-730-0098', 'https://images.petshop.local/users/098.webp', 'CUSTOMER', 'CUSTOMER', 3, 0, 1, 0),
(99, 'esme.wells', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'esme.wells@gmail.com', 'Esme', 'Wells', 'Esme Wells', '403-730-0099', 'https://images.petshop.local/users/099.webp', 'CUSTOMER', 'CUSTOMER', 1, 0, 1, 0),
(100, 'holden.webb', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'holden.webb@gmail.com', 'Holden', 'Webb', 'Holden Webb', '403-730-0100', 'https://images.petshop.local/users/100.webp', 'CUSTOMER', 'CUSTOMER', 2, 0, 1, 0),
(101, 'ai.bot', '$2a$10$mE0D/HrnCuqFeEqMy0NJwuy2jkoRYjQ7GrKcc/7QQ0r2AqnZTvyGq', 'bot@petshop.com', 'AI', 'Bot', 'AI Bot', '000-000-0000', 'https://images.petshop.local/users/bot.webp', 'STAFF', 'CUSTOMER_SERVICE', NULL, 0, 1, 0);
INSERT INTO supplier (supId, supCompany, supContactFirstName, supContactLastName, supEmail, supPhone) VALUES
@@ -1739,124 +1739,124 @@ INSERT INTO message (id, conversationId, senderId, content, attachmentUrl, attac
(119, 30, 45, 'Order #1030 is the one I meant, and the pet is Kiki.', NULL, NULL, NULL, NULL, '2026-03-02 09:10:00', 1),
(120, 30, 8, 'Thanks, the account and order are now updated on this conversation.', NULL, NULL, NULL, NULL, '2026-03-02 09:15:00', 0);
INSERT INTO activityLog (logId, userId, storeId, activity, logTimestamp) VALUES
(1, 1, 1, 'Reviewed store inventory adjustments.', '2026-01-03 08:00:00'),
(2, 2, 2, 'Approved a purchase transaction at the register.', '2026-01-03 17:00:00'),
(3, 3, 1, 'Updated a pet availability record.', '2026-01-04 02:00:00'),
(4, 4, 1, 'Completed a grooming appointment handoff.', '2026-01-04 11:00:00'),
(5, 5, 1, 'Checked a pending adoption record.', '2026-01-04 20:00:00'),
(6, 6, 1, 'Reviewed a refund request tied to an original sale.', '2026-01-05 05:00:00'),
(7, 7, 2, 'Answered a customer support conversation.', '2026-01-05 14:00:00'),
(8, 8, 2, 'Updated a product detail for the catalogue.', '2026-01-05 23:00:00'),
(9, 9, 2, 'Reviewed store inventory adjustments.', '2026-01-06 08:00:00'),
(10, 10, 2, 'Approved a purchase transaction at the register.', '2026-01-06 17:00:00'),
(11, 11, 3, 'Updated a pet availability record.', '2026-01-07 02:00:00'),
(12, 12, 3, 'Completed a grooming appointment handoff.', '2026-01-07 11:00:00'),
(13, 13, 3, 'Checked a pending adoption record.', '2026-01-07 20:00:00'),
(14, 14, 3, 'Reviewed a refund request tied to an original sale.', '2026-01-08 05:00:00'),
(15, 1, 1, 'Answered a customer support conversation.', '2026-01-08 14:00:00'),
(16, 2, 2, 'Updated a product detail for the catalogue.', '2026-01-08 23:00:00'),
(17, 3, 1, 'Reviewed store inventory adjustments.', '2026-01-09 08:00:00'),
(18, 4, 1, 'Approved a purchase transaction at the register.', '2026-01-09 17:00:00'),
(19, 5, 1, 'Updated a pet availability record.', '2026-01-10 02:00:00'),
(20, 6, 1, 'Completed a grooming appointment handoff.', '2026-01-10 11:00:00'),
(21, 7, 2, 'Checked a pending adoption record.', '2026-01-10 20:00:00'),
(22, 8, 2, 'Reviewed a refund request tied to an original sale.', '2026-01-11 05:00:00'),
(23, 9, 2, 'Answered a customer support conversation.', '2026-01-11 14:00:00'),
(24, 10, 2, 'Updated a product detail for the catalogue.', '2026-01-11 23:00:00'),
(25, 11, 3, 'Reviewed store inventory adjustments.', '2026-01-12 08:00:00'),
(26, 12, 3, 'Approved a purchase transaction at the register.', '2026-01-12 17:00:00'),
(27, 13, 3, 'Updated a pet availability record.', '2026-01-13 02:00:00'),
(28, 14, 3, 'Completed a grooming appointment handoff.', '2026-01-13 11:00:00'),
(29, 1, 1, 'Checked a pending adoption record.', '2026-01-13 20:00:00'),
(30, 2, 2, 'Reviewed a refund request tied to an original sale.', '2026-01-14 05:00:00'),
(31, 3, 1, 'Answered a customer support conversation.', '2026-01-14 14:00:00'),
(32, 4, 1, 'Updated a product detail for the catalogue.', '2026-01-14 23:00:00'),
(33, 5, 1, 'Reviewed store inventory adjustments.', '2026-01-15 08:00:00'),
(34, 6, 1, 'Approved a purchase transaction at the register.', '2026-01-15 17:00:00'),
(35, 7, 2, 'Updated a pet availability record.', '2026-01-16 02:00:00'),
(36, 8, 2, 'Completed a grooming appointment handoff.', '2026-01-16 11:00:00'),
(37, 9, 2, 'Checked a pending adoption record.', '2026-01-16 20:00:00'),
(38, 10, 2, 'Reviewed a refund request tied to an original sale.', '2026-01-17 05:00:00'),
(39, 11, 3, 'Answered a customer support conversation.', '2026-01-17 14:00:00'),
(40, 12, 3, 'Updated a product detail for the catalogue.', '2026-01-17 23:00:00'),
(41, 13, 3, 'Reviewed store inventory adjustments.', '2026-01-18 08:00:00'),
(42, 14, 3, 'Approved a purchase transaction at the register.', '2026-01-18 17:00:00'),
(43, 1, 1, 'Updated a pet availability record.', '2026-01-19 02:00:00'),
(44, 2, 2, 'Completed a grooming appointment handoff.', '2026-01-19 11:00:00'),
(45, 3, 1, 'Checked a pending adoption record.', '2026-01-19 20:00:00'),
(46, 4, 1, 'Reviewed a refund request tied to an original sale.', '2026-01-20 05:00:00'),
(47, 5, 1, 'Answered a customer support conversation.', '2026-01-20 14:00:00'),
(48, 6, 1, 'Updated a product detail for the catalogue.', '2026-01-20 23:00:00'),
(49, 7, 2, 'Reviewed store inventory adjustments.', '2026-01-21 08:00:00'),
(50, 8, 2, 'Approved a purchase transaction at the register.', '2026-01-21 17:00:00'),
(51, 9, 2, 'Updated a pet availability record.', '2026-01-22 02:00:00'),
(52, 10, 2, 'Completed a grooming appointment handoff.', '2026-01-22 11:00:00'),
(53, 11, 3, 'Checked a pending adoption record.', '2026-01-22 20:00:00'),
(54, 12, 3, 'Reviewed a refund request tied to an original sale.', '2026-01-23 05:00:00'),
(55, 13, 3, 'Answered a customer support conversation.', '2026-01-23 14:00:00'),
(56, 14, 3, 'Updated a product detail for the catalogue.', '2026-01-23 23:00:00'),
(57, 1, 1, 'Reviewed store inventory adjustments.', '2026-01-24 08:00:00'),
(58, 2, 2, 'Approved a purchase transaction at the register.', '2026-01-24 17:00:00'),
(59, 3, 1, 'Updated a pet availability record.', '2026-01-25 02:00:00'),
(60, 4, 1, 'Completed a grooming appointment handoff.', '2026-01-25 11:00:00'),
(61, 5, 1, 'Checked a pending adoption record.', '2026-01-25 20:00:00'),
(62, 6, 1, 'Reviewed a refund request tied to an original sale.', '2026-01-26 05:00:00'),
(63, 7, 2, 'Answered a customer support conversation.', '2026-01-26 14:00:00'),
(64, 8, 2, 'Updated a product detail for the catalogue.', '2026-01-26 23:00:00'),
(65, 9, 2, 'Reviewed store inventory adjustments.', '2026-01-27 08:00:00'),
(66, 10, 2, 'Approved a purchase transaction at the register.', '2026-01-27 17:00:00'),
(67, 11, 3, 'Updated a pet availability record.', '2026-01-28 02:00:00'),
(68, 12, 3, 'Completed a grooming appointment handoff.', '2026-01-28 11:00:00'),
(69, 13, 3, 'Checked a pending adoption record.', '2026-01-28 20:00:00'),
(70, 14, 3, 'Reviewed a refund request tied to an original sale.', '2026-01-29 05:00:00'),
(71, 1, 1, 'Answered a customer support conversation.', '2026-01-29 14:00:00'),
(72, 2, 2, 'Updated a product detail for the catalogue.', '2026-01-29 23:00:00'),
(73, 3, 1, 'Reviewed store inventory adjustments.', '2026-01-30 08:00:00'),
(74, 4, 1, 'Approved a purchase transaction at the register.', '2026-01-30 17:00:00'),
(75, 5, 1, 'Updated a pet availability record.', '2026-01-31 02:00:00'),
(76, 6, 1, 'Completed a grooming appointment handoff.', '2026-01-31 11:00:00'),
(77, 7, 2, 'Checked a pending adoption record.', '2026-01-31 20:00:00'),
(78, 8, 2, 'Reviewed a refund request tied to an original sale.', '2026-02-01 05:00:00'),
(79, 9, 2, 'Answered a customer support conversation.', '2026-02-01 14:00:00'),
(80, 10, 2, 'Updated a product detail for the catalogue.', '2026-02-01 23:00:00'),
(81, 11, 3, 'Reviewed store inventory adjustments.', '2026-02-02 08:00:00'),
(82, 12, 3, 'Approved a purchase transaction at the register.', '2026-02-02 17:00:00'),
(83, 13, 3, 'Updated a pet availability record.', '2026-02-03 02:00:00'),
(84, 14, 3, 'Completed a grooming appointment handoff.', '2026-02-03 11:00:00'),
(85, 1, 1, 'Checked a pending adoption record.', '2026-02-03 20:00:00'),
(86, 2, 2, 'Reviewed a refund request tied to an original sale.', '2026-02-04 05:00:00'),
(87, 3, 1, 'Answered a customer support conversation.', '2026-02-04 14:00:00'),
(88, 4, 1, 'Updated a product detail for the catalogue.', '2026-02-04 23:00:00'),
(89, 5, 1, 'Reviewed store inventory adjustments.', '2026-02-05 08:00:00'),
(90, 6, 1, 'Approved a purchase transaction at the register.', '2026-02-05 17:00:00'),
(91, 7, 2, 'Updated a pet availability record.', '2026-02-06 02:00:00'),
(92, 8, 2, 'Completed a grooming appointment handoff.', '2026-02-06 11:00:00'),
(93, 9, 2, 'Checked a pending adoption record.', '2026-02-06 20:00:00'),
(94, 10, 2, 'Reviewed a refund request tied to an original sale.', '2026-02-07 05:00:00'),
(95, 11, 3, 'Answered a customer support conversation.', '2026-02-07 14:00:00'),
(96, 12, 3, 'Updated a product detail for the catalogue.', '2026-02-07 23:00:00'),
(97, 13, 3, 'Reviewed store inventory adjustments.', '2026-02-08 08:00:00'),
(98, 14, 3, 'Approved a purchase transaction at the register.', '2026-02-08 17:00:00'),
(99, 1, 1, 'Updated a pet availability record.', '2026-02-09 02:00:00'),
(100, 2, 2, 'Completed a grooming appointment handoff.', '2026-02-09 11:00:00'),
(101, 3, 1, 'Checked a pending adoption record.', '2026-02-09 20:00:00'),
(102, 4, 1, 'Reviewed a refund request tied to an original sale.', '2026-02-10 05:00:00'),
(103, 5, 1, 'Answered a customer support conversation.', '2026-02-10 14:00:00'),
(104, 6, 1, 'Updated a product detail for the catalogue.', '2026-02-10 23:00:00'),
(105, 7, 2, 'Reviewed store inventory adjustments.', '2026-02-11 08:00:00'),
(106, 8, 2, 'Approved a purchase transaction at the register.', '2026-02-11 17:00:00'),
(107, 9, 2, 'Updated a pet availability record.', '2026-02-12 02:00:00'),
(108, 10, 2, 'Completed a grooming appointment handoff.', '2026-02-12 11:00:00'),
(109, 11, 3, 'Checked a pending adoption record.', '2026-02-12 20:00:00'),
(110, 12, 3, 'Reviewed a refund request tied to an original sale.', '2026-02-13 05:00:00'),
(111, 13, 3, 'Answered a customer support conversation.', '2026-02-13 14:00:00'),
(112, 14, 3, 'Updated a product detail for the catalogue.', '2026-02-13 23:00:00'),
(113, 1, 1, 'Reviewed store inventory adjustments.', '2026-02-14 08:00:00'),
(114, 2, 2, 'Approved a purchase transaction at the register.', '2026-02-14 17:00:00'),
(115, 3, 1, 'Updated a pet availability record.', '2026-02-15 02:00:00'),
(116, 4, 1, 'Completed a grooming appointment handoff.', '2026-02-15 11:00:00'),
(117, 5, 1, 'Checked a pending adoption record.', '2026-02-15 20:00:00'),
(118, 6, 1, 'Reviewed a refund request tied to an original sale.', '2026-02-16 05:00:00'),
(119, 7, 2, 'Answered a customer support conversation.', '2026-02-16 14:00:00'),
(120, 8, 2, 'Updated a product detail for the catalogue.', '2026-02-16 23:00:00');
INSERT INTO activityLog (logId, userId, storeId, usernameSnapshot, fullNameSnapshot, roleSnapshot, storeNameSnapshot, activity, logTimestamp) VALUES
(1, 1, 1, 'admin', 'Admin User', 'ADMIN', 'Downtown Branch', 'Reviewed store inventory adjustments.', '2026-01-03 08:00:00'),
(2, 2, 2, 'morgan.lee', 'Morgan Lee', 'ADMIN', 'North Branch', 'Approved a purchase transaction at the register.', '2026-01-03 17:00:00'),
(3, 3, 1, 'staff', 'Staff User', 'STAFF', 'Downtown Branch', 'Updated a pet availability record.', '2026-01-04 02:00:00'),
(4, 4, 1, 'sara.smith', 'Sara Smith', 'STAFF', 'Downtown Branch', 'Completed a grooming appointment handoff.', '2026-01-04 11:00:00'),
(5, 5, 1, 'david.brown', 'David Brown', 'STAFF', 'Downtown Branch', 'Checked a pending adoption record.', '2026-01-04 20:00:00'),
(6, 6, 1, 'priya.patel', 'Priya Patel', 'STAFF', 'Downtown Branch', 'Reviewed a refund request tied to an original sale.', '2026-01-05 05:00:00'),
(7, 7, 2, 'michael.johnson', 'Michael Johnson', 'STAFF', 'North Branch', 'Answered a customer support conversation.', '2026-01-05 14:00:00'),
(8, 8, 2, 'emma.davis', 'Emma Davis', 'STAFF', 'North Branch', 'Updated a product detail for the catalogue.', '2026-01-05 23:00:00'),
(9, 9, 2, 'lucas.turner', 'Lucas Turner', 'STAFF', 'North Branch', 'Reviewed store inventory adjustments.', '2026-01-06 08:00:00'),
(10, 10, 2, 'nina.green', 'Nina Green', 'STAFF', 'North Branch', 'Approved a purchase transaction at the register.', '2026-01-06 17:00:00'),
(11, 11, 3, 'lisa.williams', 'Lisa Williams', 'STAFF', 'West Side Store', 'Updated a pet availability record.', '2026-01-07 02:00:00'),
(12, 12, 3, 'daniel.moore', 'Daniel Moore', 'STAFF', 'West Side Store', 'Completed a grooming appointment handoff.', '2026-01-07 11:00:00'),
(13, 13, 3, 'chloe.martin', 'Chloe Martin', 'STAFF', 'West Side Store', 'Checked a pending adoption record.', '2026-01-07 20:00:00'),
(14, 14, 3, 'owen.baker', 'Owen Baker', 'STAFF', 'West Side Store', 'Reviewed a refund request tied to an original sale.', '2026-01-08 05:00:00'),
(15, 1, 1, 'admin', 'Admin User', 'ADMIN', 'Downtown Branch', 'Answered a customer support conversation.', '2026-01-08 14:00:00'),
(16, 2, 2, 'morgan.lee', 'Morgan Lee', 'ADMIN', 'North Branch', 'Updated a product detail for the catalogue.', '2026-01-08 23:00:00'),
(17, 3, 1, 'staff', 'Staff User', 'STAFF', 'Downtown Branch', 'Reviewed store inventory adjustments.', '2026-01-09 08:00:00'),
(18, 4, 1, 'sara.smith', 'Sara Smith', 'STAFF', 'Downtown Branch', 'Approved a purchase transaction at the register.', '2026-01-09 17:00:00'),
(19, 5, 1, 'david.brown', 'David Brown', 'STAFF', 'Downtown Branch', 'Updated a pet availability record.', '2026-01-10 02:00:00'),
(20, 6, 1, 'priya.patel', 'Priya Patel', 'STAFF', 'Downtown Branch', 'Completed a grooming appointment handoff.', '2026-01-10 11:00:00'),
(21, 7, 2, 'michael.johnson', 'Michael Johnson', 'STAFF', 'North Branch', 'Checked a pending adoption record.', '2026-01-10 20:00:00'),
(22, 8, 2, 'emma.davis', 'Emma Davis', 'STAFF', 'North Branch', 'Reviewed a refund request tied to an original sale.', '2026-01-11 05:00:00'),
(23, 9, 2, 'lucas.turner', 'Lucas Turner', 'STAFF', 'North Branch', 'Answered a customer support conversation.', '2026-01-11 14:00:00'),
(24, 10, 2, 'nina.green', 'Nina Green', 'STAFF', 'North Branch', 'Updated a product detail for the catalogue.', '2026-01-11 23:00:00'),
(25, 11, 3, 'lisa.williams', 'Lisa Williams', 'STAFF', 'West Side Store', 'Reviewed store inventory adjustments.', '2026-01-12 08:00:00'),
(26, 12, 3, 'daniel.moore', 'Daniel Moore', 'STAFF', 'West Side Store', 'Approved a purchase transaction at the register.', '2026-01-12 17:00:00'),
(27, 13, 3, 'chloe.martin', 'Chloe Martin', 'STAFF', 'West Side Store', 'Updated a pet availability record.', '2026-01-13 02:00:00'),
(28, 14, 3, 'owen.baker', 'Owen Baker', 'STAFF', 'West Side Store', 'Completed a grooming appointment handoff.', '2026-01-13 11:00:00'),
(29, 1, 1, 'admin', 'Admin User', 'ADMIN', 'Downtown Branch', 'Checked a pending adoption record.', '2026-01-13 20:00:00'),
(30, 2, 2, 'morgan.lee', 'Morgan Lee', 'ADMIN', 'North Branch', 'Reviewed a refund request tied to an original sale.', '2026-01-14 05:00:00'),
(31, 3, 1, 'staff', 'Staff User', 'STAFF', 'Downtown Branch', 'Answered a customer support conversation.', '2026-01-14 14:00:00'),
(32, 4, 1, 'sara.smith', 'Sara Smith', 'STAFF', 'Downtown Branch', 'Updated a product detail for the catalogue.', '2026-01-14 23:00:00'),
(33, 5, 1, 'david.brown', 'David Brown', 'STAFF', 'Downtown Branch', 'Reviewed store inventory adjustments.', '2026-01-15 08:00:00'),
(34, 6, 1, 'priya.patel', 'Priya Patel', 'STAFF', 'Downtown Branch', 'Approved a purchase transaction at the register.', '2026-01-15 17:00:00'),
(35, 7, 2, 'michael.johnson', 'Michael Johnson', 'STAFF', 'North Branch', 'Updated a pet availability record.', '2026-01-16 02:00:00'),
(36, 8, 2, 'emma.davis', 'Emma Davis', 'STAFF', 'North Branch', 'Completed a grooming appointment handoff.', '2026-01-16 11:00:00'),
(37, 9, 2, 'lucas.turner', 'Lucas Turner', 'STAFF', 'North Branch', 'Checked a pending adoption record.', '2026-01-16 20:00:00'),
(38, 10, 2, 'nina.green', 'Nina Green', 'STAFF', 'North Branch', 'Reviewed a refund request tied to an original sale.', '2026-01-17 05:00:00'),
(39, 11, 3, 'lisa.williams', 'Lisa Williams', 'STAFF', 'West Side Store', 'Answered a customer support conversation.', '2026-01-17 14:00:00'),
(40, 12, 3, 'daniel.moore', 'Daniel Moore', 'STAFF', 'West Side Store', 'Updated a product detail for the catalogue.', '2026-01-17 23:00:00'),
(41, 13, 3, 'chloe.martin', 'Chloe Martin', 'STAFF', 'West Side Store', 'Reviewed store inventory adjustments.', '2026-01-18 08:00:00'),
(42, 14, 3, 'owen.baker', 'Owen Baker', 'STAFF', 'West Side Store', 'Approved a purchase transaction at the register.', '2026-01-18 17:00:00'),
(43, 1, 1, 'admin', 'Admin User', 'ADMIN', 'Downtown Branch', 'Updated a pet availability record.', '2026-01-19 02:00:00'),
(44, 2, 2, 'morgan.lee', 'Morgan Lee', 'ADMIN', 'North Branch', 'Completed a grooming appointment handoff.', '2026-01-19 11:00:00'),
(45, 3, 1, 'staff', 'Staff User', 'STAFF', 'Downtown Branch', 'Checked a pending adoption record.', '2026-01-19 20:00:00'),
(46, 4, 1, 'sara.smith', 'Sara Smith', 'STAFF', 'Downtown Branch', 'Reviewed a refund request tied to an original sale.', '2026-01-20 05:00:00'),
(47, 5, 1, 'david.brown', 'David Brown', 'STAFF', 'Downtown Branch', 'Answered a customer support conversation.', '2026-01-20 14:00:00'),
(48, 6, 1, 'priya.patel', 'Priya Patel', 'STAFF', 'Downtown Branch', 'Updated a product detail for the catalogue.', '2026-01-20 23:00:00'),
(49, 7, 2, 'michael.johnson', 'Michael Johnson', 'STAFF', 'North Branch', 'Reviewed store inventory adjustments.', '2026-01-21 08:00:00'),
(50, 8, 2, 'emma.davis', 'Emma Davis', 'STAFF', 'North Branch', 'Approved a purchase transaction at the register.', '2026-01-21 17:00:00'),
(51, 9, 2, 'lucas.turner', 'Lucas Turner', 'STAFF', 'North Branch', 'Updated a pet availability record.', '2026-01-22 02:00:00'),
(52, 10, 2, 'nina.green', 'Nina Green', 'STAFF', 'North Branch', 'Completed a grooming appointment handoff.', '2026-01-22 11:00:00'),
(53, 11, 3, 'lisa.williams', 'Lisa Williams', 'STAFF', 'West Side Store', 'Checked a pending adoption record.', '2026-01-22 20:00:00'),
(54, 12, 3, 'daniel.moore', 'Daniel Moore', 'STAFF', 'West Side Store', 'Reviewed a refund request tied to an original sale.', '2026-01-23 05:00:00'),
(55, 13, 3, 'chloe.martin', 'Chloe Martin', 'STAFF', 'West Side Store', 'Answered a customer support conversation.', '2026-01-23 14:00:00'),
(56, 14, 3, 'owen.baker', 'Owen Baker', 'STAFF', 'West Side Store', 'Updated a product detail for the catalogue.', '2026-01-23 23:00:00'),
(57, 1, 1, 'admin', 'Admin User', 'ADMIN', 'Downtown Branch', 'Reviewed store inventory adjustments.', '2026-01-24 08:00:00'),
(58, 2, 2, 'morgan.lee', 'Morgan Lee', 'ADMIN', 'North Branch', 'Approved a purchase transaction at the register.', '2026-01-24 17:00:00'),
(59, 3, 1, 'staff', 'Staff User', 'STAFF', 'Downtown Branch', 'Updated a pet availability record.', '2026-01-25 02:00:00'),
(60, 4, 1, 'sara.smith', 'Sara Smith', 'STAFF', 'Downtown Branch', 'Completed a grooming appointment handoff.', '2026-01-25 11:00:00'),
(61, 5, 1, 'david.brown', 'David Brown', 'STAFF', 'Downtown Branch', 'Checked a pending adoption record.', '2026-01-25 20:00:00'),
(62, 6, 1, 'priya.patel', 'Priya Patel', 'STAFF', 'Downtown Branch', 'Reviewed a refund request tied to an original sale.', '2026-01-26 05:00:00'),
(63, 7, 2, 'michael.johnson', 'Michael Johnson', 'STAFF', 'North Branch', 'Answered a customer support conversation.', '2026-01-26 14:00:00'),
(64, 8, 2, 'emma.davis', 'Emma Davis', 'STAFF', 'North Branch', 'Updated a product detail for the catalogue.', '2026-01-26 23:00:00'),
(65, 9, 2, 'lucas.turner', 'Lucas Turner', 'STAFF', 'North Branch', 'Reviewed store inventory adjustments.', '2026-01-27 08:00:00'),
(66, 10, 2, 'nina.green', 'Nina Green', 'STAFF', 'North Branch', 'Approved a purchase transaction at the register.', '2026-01-27 17:00:00'),
(67, 11, 3, 'lisa.williams', 'Lisa Williams', 'STAFF', 'West Side Store', 'Updated a pet availability record.', '2026-01-28 02:00:00'),
(68, 12, 3, 'daniel.moore', 'Daniel Moore', 'STAFF', 'West Side Store', 'Completed a grooming appointment handoff.', '2026-01-28 11:00:00'),
(69, 13, 3, 'chloe.martin', 'Chloe Martin', 'STAFF', 'West Side Store', 'Checked a pending adoption record.', '2026-01-28 20:00:00'),
(70, 14, 3, 'owen.baker', 'Owen Baker', 'STAFF', 'West Side Store', 'Reviewed a refund request tied to an original sale.', '2026-01-29 05:00:00'),
(71, 1, 1, 'admin', 'Admin User', 'ADMIN', 'Downtown Branch', 'Answered a customer support conversation.', '2026-01-29 14:00:00'),
(72, 2, 2, 'morgan.lee', 'Morgan Lee', 'ADMIN', 'North Branch', 'Updated a product detail for the catalogue.', '2026-01-29 23:00:00'),
(73, 3, 1, 'staff', 'Staff User', 'STAFF', 'Downtown Branch', 'Reviewed store inventory adjustments.', '2026-01-30 08:00:00'),
(74, 4, 1, 'sara.smith', 'Sara Smith', 'STAFF', 'Downtown Branch', 'Approved a purchase transaction at the register.', '2026-01-30 17:00:00'),
(75, 5, 1, 'david.brown', 'David Brown', 'STAFF', 'Downtown Branch', 'Updated a pet availability record.', '2026-01-31 02:00:00'),
(76, 6, 1, 'priya.patel', 'Priya Patel', 'STAFF', 'Downtown Branch', 'Completed a grooming appointment handoff.', '2026-01-31 11:00:00'),
(77, 7, 2, 'michael.johnson', 'Michael Johnson', 'STAFF', 'North Branch', 'Checked a pending adoption record.', '2026-01-31 20:00:00'),
(78, 8, 2, 'emma.davis', 'Emma Davis', 'STAFF', 'North Branch', 'Reviewed a refund request tied to an original sale.', '2026-02-01 05:00:00'),
(79, 9, 2, 'lucas.turner', 'Lucas Turner', 'STAFF', 'North Branch', 'Answered a customer support conversation.', '2026-02-01 14:00:00'),
(80, 10, 2, 'nina.green', 'Nina Green', 'STAFF', 'North Branch', 'Updated a product detail for the catalogue.', '2026-02-01 23:00:00'),
(81, 11, 3, 'lisa.williams', 'Lisa Williams', 'STAFF', 'West Side Store', 'Reviewed store inventory adjustments.', '2026-02-02 08:00:00'),
(82, 12, 3, 'daniel.moore', 'Daniel Moore', 'STAFF', 'West Side Store', 'Approved a purchase transaction at the register.', '2026-02-02 17:00:00'),
(83, 13, 3, 'chloe.martin', 'Chloe Martin', 'STAFF', 'West Side Store', 'Updated a pet availability record.', '2026-02-03 02:00:00'),
(84, 14, 3, 'owen.baker', 'Owen Baker', 'STAFF', 'West Side Store', 'Completed a grooming appointment handoff.', '2026-02-03 11:00:00'),
(85, 1, 1, 'admin', 'Admin User', 'ADMIN', 'Downtown Branch', 'Checked a pending adoption record.', '2026-02-03 20:00:00'),
(86, 2, 2, 'morgan.lee', 'Morgan Lee', 'ADMIN', 'North Branch', 'Reviewed a refund request tied to an original sale.', '2026-02-04 05:00:00'),
(87, 3, 1, 'staff', 'Staff User', 'STAFF', 'Downtown Branch', 'Answered a customer support conversation.', '2026-02-04 14:00:00'),
(88, 4, 1, 'sara.smith', 'Sara Smith', 'STAFF', 'Downtown Branch', 'Updated a product detail for the catalogue.', '2026-02-04 23:00:00'),
(89, 5, 1, 'david.brown', 'David Brown', 'STAFF', 'Downtown Branch', 'Reviewed store inventory adjustments.', '2026-02-05 08:00:00'),
(90, 6, 1, 'priya.patel', 'Priya Patel', 'STAFF', 'Downtown Branch', 'Approved a purchase transaction at the register.', '2026-02-05 17:00:00'),
(91, 7, 2, 'michael.johnson', 'Michael Johnson', 'STAFF', 'North Branch', 'Updated a pet availability record.', '2026-02-06 02:00:00'),
(92, 8, 2, 'emma.davis', 'Emma Davis', 'STAFF', 'North Branch', 'Completed a grooming appointment handoff.', '2026-02-06 11:00:00'),
(93, 9, 2, 'lucas.turner', 'Lucas Turner', 'STAFF', 'North Branch', 'Checked a pending adoption record.', '2026-02-06 20:00:00'),
(94, 10, 2, 'nina.green', 'Nina Green', 'STAFF', 'North Branch', 'Reviewed a refund request tied to an original sale.', '2026-02-07 05:00:00'),
(95, 11, 3, 'lisa.williams', 'Lisa Williams', 'STAFF', 'West Side Store', 'Answered a customer support conversation.', '2026-02-07 14:00:00'),
(96, 12, 3, 'daniel.moore', 'Daniel Moore', 'STAFF', 'West Side Store', 'Updated a product detail for the catalogue.', '2026-02-07 23:00:00'),
(97, 13, 3, 'chloe.martin', 'Chloe Martin', 'STAFF', 'West Side Store', 'Reviewed store inventory adjustments.', '2026-02-08 08:00:00'),
(98, 14, 3, 'owen.baker', 'Owen Baker', 'STAFF', 'West Side Store', 'Approved a purchase transaction at the register.', '2026-02-08 17:00:00'),
(99, 1, 1, 'admin', 'Admin User', 'ADMIN', 'Downtown Branch', 'Updated a pet availability record.', '2026-02-09 02:00:00'),
(100, 2, 2, 'morgan.lee', 'Morgan Lee', 'ADMIN', 'North Branch', 'Completed a grooming appointment handoff.', '2026-02-09 11:00:00'),
(101, 3, 1, 'staff', 'Staff User', 'STAFF', 'Downtown Branch', 'Checked a pending adoption record.', '2026-02-09 20:00:00'),
(102, 4, 1, 'sara.smith', 'Sara Smith', 'STAFF', 'Downtown Branch', 'Reviewed a refund request tied to an original sale.', '2026-02-10 05:00:00'),
(103, 5, 1, 'david.brown', 'David Brown', 'STAFF', 'Downtown Branch', 'Answered a customer support conversation.', '2026-02-10 14:00:00'),
(104, 6, 1, 'priya.patel', 'Priya Patel', 'STAFF', 'Downtown Branch', 'Updated a product detail for the catalogue.', '2026-02-10 23:00:00'),
(105, 7, 2, 'michael.johnson', 'Michael Johnson', 'STAFF', 'North Branch', 'Reviewed store inventory adjustments.', '2026-02-11 08:00:00'),
(106, 8, 2, 'emma.davis', 'Emma Davis', 'STAFF', 'North Branch', 'Approved a purchase transaction at the register.', '2026-02-11 17:00:00'),
(107, 9, 2, 'lucas.turner', 'Lucas Turner', 'STAFF', 'North Branch', 'Updated a pet availability record.', '2026-02-12 02:00:00'),
(108, 10, 2, 'nina.green', 'Nina Green', 'STAFF', 'North Branch', 'Completed a grooming appointment handoff.', '2026-02-12 11:00:00'),
(109, 11, 3, 'lisa.williams', 'Lisa Williams', 'STAFF', 'West Side Store', 'Checked a pending adoption record.', '2026-02-12 20:00:00'),
(110, 12, 3, 'daniel.moore', 'Daniel Moore', 'STAFF', 'West Side Store', 'Reviewed a refund request tied to an original sale.', '2026-02-13 05:00:00'),
(111, 13, 3, 'chloe.martin', 'Chloe Martin', 'STAFF', 'West Side Store', 'Answered a customer support conversation.', '2026-02-13 14:00:00'),
(112, 14, 3, 'owen.baker', 'Owen Baker', 'STAFF', 'West Side Store', 'Updated a product detail for the catalogue.', '2026-02-13 23:00:00'),
(113, 1, 1, 'admin', 'Admin User', 'ADMIN', 'Downtown Branch', 'Reviewed store inventory adjustments.', '2026-02-14 08:00:00'),
(114, 2, 2, 'morgan.lee', 'Morgan Lee', 'ADMIN', 'North Branch', 'Approved a purchase transaction at the register.', '2026-02-14 17:00:00'),
(115, 3, 1, 'staff', 'Staff User', 'STAFF', 'Downtown Branch', 'Updated a pet availability record.', '2026-02-15 02:00:00'),
(116, 4, 1, 'sara.smith', 'Sara Smith', 'STAFF', 'Downtown Branch', 'Completed a grooming appointment handoff.', '2026-02-15 11:00:00'),
(117, 5, 1, 'david.brown', 'David Brown', 'STAFF', 'Downtown Branch', 'Checked a pending adoption record.', '2026-02-15 20:00:00'),
(118, 6, 1, 'priya.patel', 'Priya Patel', 'STAFF', 'Downtown Branch', 'Reviewed a refund request tied to an original sale.', '2026-02-16 05:00:00'),
(119, 7, 2, 'michael.johnson', 'Michael Johnson', 'STAFF', 'North Branch', 'Answered a customer support conversation.', '2026-02-16 14:00:00'),
(120, 8, 2, 'emma.davis', 'Emma Davis', 'STAFF', 'North Branch', 'Updated a product detail for the catalogue.', '2026-02-16 23:00:00');

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Some files were not shown because too many files have changed in this diff Show More