diff --git a/.gitignore b/.gitignore index bc434064..54f866f6 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,4 @@ .local/ commit-patches/ temp_photos/ -uploads/ .env diff --git a/backend/.gitignore b/backend/.gitignore index 242ba1f0..0989bc79 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -46,7 +46,10 @@ build/ ### Project Specific ### src/test/ tmp/ -uploads/ +uploads/* +!uploads/avatars/ +!uploads/pets/ +!uploads/products/ ### Temp and backup files ### *.backup diff --git a/backend/Dockerfile b/backend/Dockerfile index 08a06773..c3479c8e 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -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"] \ No newline at end of file +ENTRYPOINT ["java","-jar","app.jar"] diff --git a/backend/pom.xml b/backend/pom.xml index 9f2157b4..206ae451 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -151,6 +151,12 @@ com.petshop.backend.DevStackApplication runtime + + + UPLOAD_BASE_DIR + ${project.basedir}/uploads + + diff --git a/backend/src/main/java/com/petshop/backend/controller/AuthController.java b/backend/src/main/java/com/petshop/backend/controller/AuthController.java index c0eff22e..33281560 100644 --- a/backend/src/main/java/com/petshop/backend/controller/AuthController.java +++ b/backend/src/main/java/com/petshop/backend/controller/AuthController.java @@ -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 forgotPassword(@Valid @RequestBody ForgotPasswordRequest request) { + return ResponseEntity.ok(passwordResetService.createResetToken(request.getUsernameOrEmail())); + } + + @PostMapping("/reset-password") + public ResponseEntity resetPassword(@Valid @RequestBody ResetPasswordRequest request) { + return ResponseEntity.ok(passwordResetService.resetPassword(request.getToken(), request.getNewPassword())); + } + @Transactional(readOnly = true) @GetMapping("/me") public ResponseEntity getCurrentUser() { diff --git a/backend/src/main/java/com/petshop/backend/dto/auth/ForgotPasswordRequest.java b/backend/src/main/java/com/petshop/backend/dto/auth/ForgotPasswordRequest.java new file mode 100644 index 00000000..529581cc --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/dto/auth/ForgotPasswordRequest.java @@ -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; + } +} diff --git a/backend/src/main/java/com/petshop/backend/dto/auth/ForgotPasswordResponse.java b/backend/src/main/java/com/petshop/backend/dto/auth/ForgotPasswordResponse.java new file mode 100644 index 00000000..6062a196 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/dto/auth/ForgotPasswordResponse.java @@ -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; + } +} diff --git a/backend/src/main/java/com/petshop/backend/dto/auth/ResetPasswordRequest.java b/backend/src/main/java/com/petshop/backend/dto/auth/ResetPasswordRequest.java new file mode 100644 index 00000000..3f20f7f7 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/dto/auth/ResetPasswordRequest.java @@ -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; + } +} diff --git a/backend/src/main/java/com/petshop/backend/dto/auth/ResetPasswordResponse.java b/backend/src/main/java/com/petshop/backend/dto/auth/ResetPasswordResponse.java new file mode 100644 index 00000000..0edf18a7 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/dto/auth/ResetPasswordResponse.java @@ -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; + } +} diff --git a/backend/src/main/java/com/petshop/backend/dto/cart/CartResponse.java b/backend/src/main/java/com/petshop/backend/dto/cart/CartResponse.java index 4e2442f6..d5fc9faf 100644 --- a/backend/src/main/java/com/petshop/backend/dto/cart/CartResponse.java +++ b/backend/src/main/java/com/petshop/backend/dto/cart/CartResponse.java @@ -11,8 +11,11 @@ public class CartResponse { private List 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; } + } diff --git a/backend/src/main/java/com/petshop/backend/dto/sale/SaleRequest.java b/backend/src/main/java/com/petshop/backend/dto/sale/SaleRequest.java index 97248688..9faba27f 100644 --- a/backend/src/main/java/com/petshop/backend/dto/sale/SaleRequest.java +++ b/backend/src/main/java/com/petshop/backend/dto/sale/SaleRequest.java @@ -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) { diff --git a/backend/src/main/java/com/petshop/backend/dto/sale/SaleResponse.java b/backend/src/main/java/com/petshop/backend/dto/sale/SaleResponse.java index eaebd4e8..8f2b4fe5 100644 --- a/backend/src/main/java/com/petshop/backend/dto/sale/SaleResponse.java +++ b/backend/src/main/java/com/petshop/backend/dto/sale/SaleResponse.java @@ -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; } diff --git a/backend/src/main/java/com/petshop/backend/entity/ActivityLog.java b/backend/src/main/java/com/petshop/backend/entity/ActivityLog.java index 04dc79ec..b445b7c4 100644 --- a/backend/src/main/java/com/petshop/backend/entity/ActivityLog.java +++ b/backend/src/main/java/com/petshop/backend/entity/ActivityLog.java @@ -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 { diff --git a/backend/src/main/java/com/petshop/backend/entity/Cart.java b/backend/src/main/java/com/petshop/backend/entity/Cart.java index ba0566f8..61066af6 100644 --- a/backend/src/main/java/com/petshop/backend/entity/Cart.java +++ b/backend/src/main/java/com/petshop/backend/entity/Cart.java @@ -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; } diff --git a/backend/src/main/java/com/petshop/backend/entity/PasswordResetToken.java b/backend/src/main/java/com/petshop/backend/entity/PasswordResetToken.java new file mode 100644 index 00000000..bd6c5494 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/entity/PasswordResetToken.java @@ -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); + } +} diff --git a/backend/src/main/java/com/petshop/backend/entity/Sale.java b/backend/src/main/java/com/petshop/backend/entity/Sale.java index f994a855..e61204fd 100644 --- a/backend/src/main/java/com/petshop/backend/entity/Sale.java +++ b/backend/src/main/java/com/petshop/backend/entity/Sale.java @@ -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 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 getItems() { return items; } diff --git a/backend/src/main/java/com/petshop/backend/repository/PasswordResetTokenRepository.java b/backend/src/main/java/com/petshop/backend/repository/PasswordResetTokenRepository.java new file mode 100644 index 00000000..2d34d381 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/repository/PasswordResetTokenRepository.java @@ -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 { + + List findByUser_IdAndUsedAtIsNull(Long userId); + + Optional findByTokenHashAndUsedAtIsNullAndExpiresAtAfter(String tokenHash, LocalDateTime now); +} diff --git a/backend/src/main/java/com/petshop/backend/repository/SaleRepository.java b/backend/src/main/java/com/petshop/backend/repository/SaleRepository.java index c648886e..5a7798c2 100644 --- a/backend/src/main/java/com/petshop/backend/repository/SaleRepository.java +++ b/backend/src/main/java/com/petshop/backend/repository/SaleRepository.java @@ -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 { @@ -25,4 +26,6 @@ public interface SaleRepository extends JpaRepository { Page searchSales(@Param("q") String query, @Param("paymentMethod") String paymentMethod, @Param("storeId") Long storeId, @Param("isRefund") Boolean isRefund, Pageable pageable); List findByOriginalSaleSaleId(Long originalSaleId); + + Optional findByCartCartId(Long cartId); } diff --git a/backend/src/main/java/com/petshop/backend/security/SecurityConfig.java b/backend/src/main/java/com/petshop/backend/security/SecurityConfig.java index 1a6cedce..b15d4a96 100644 --- a/backend/src/main/java/com/petshop/backend/security/SecurityConfig.java +++ b/backend/src/main/java/com/petshop/backend/security/SecurityConfig.java @@ -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() diff --git a/backend/src/main/java/com/petshop/backend/service/AvatarStorageService.java b/backend/src/main/java/com/petshop/backend/service/AvatarStorageService.java index dff64508..2229c3d4 100644 --- a/backend/src/main/java/com/petshop/backend/service/AvatarStorageService.java +++ b/backend/src/main/java/com/petshop/backend/service/AvatarStorageService.java @@ -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"; }; } diff --git a/backend/src/main/java/com/petshop/backend/service/CartService.java b/backend/src/main/java/com/petshop/backend/service/CartService.java index d406a44d..1714f518 100644 --- a/backend/src/main/java/com/petshop/backend/service/CartService.java +++ b/backend/src/main/java/com/petshop/backend/service/CartService.java @@ -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,28 +24,36 @@ 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; public CartService(CartRepository cartRepository, CartItemRepository cartItemRepository, - UserRepository userRepository, - StoreRepository storeRepository, - ProductRepository productRepository, - CouponRepository couponRepository) { + UserRepository userRepository, + StoreRepository storeRepository, + ProductRepository productRepository, + 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 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 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 items = cartItemRepository.findByCartCartId(cart.getCartId()); @@ -274,8 +414,8 @@ public class CartService { 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); } @@ -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 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 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; } diff --git a/backend/src/main/java/com/petshop/backend/service/CatalogImageStorageService.java b/backend/src/main/java/com/petshop/backend/service/CatalogImageStorageService.java index 34a92ff0..1068e094 100644 --- a/backend/src/main/java/com/petshop/backend/service/CatalogImageStorageService.java +++ b/backend/src/main/java/com/petshop/backend/service/CatalogImageStorageService.java @@ -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"; }; } diff --git a/backend/src/main/java/com/petshop/backend/service/PasswordResetService.java b/backend/src/main/java/com/petshop/backend/service/PasswordResetService.java new file mode 100644 index 00000000..77a14d18 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/service/PasswordResetService.java @@ -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 = 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; + } +} diff --git a/backend/src/main/java/com/petshop/backend/service/SaleService.java b/backend/src/main/java/com/petshop/backend/service/SaleService.java index 526f5a57..b7c9c498 100644 --- a/backend/src/main/java/com/petshop/backend/service/SaleService.java +++ b/backend/src/main/java/com/petshop/backend/service/SaleService.java @@ -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()); diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml index d16835fb..e3c3e0df 100644 --- a/backend/src/main/resources/application.yml +++ b/backend/src/main/resources/application.yml @@ -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} diff --git a/backend/src/main/resources/db/migration/V1__target_baseline.sql b/backend/src/main/resources/db/migration/V1__target_baseline.sql index d287c8e1..bc064913 100644 --- a/backend/src/main/resources/db/migration/V1__target_baseline.sql +++ b/backend/src/main/resources/db/migration/V1__target_baseline.sql @@ -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); diff --git a/backend/src/main/resources/db/migration/V2__seed_data.sql b/backend/src/main/resources/db/migration/V2__seed_data.sql index 970c8b43..88d3835b 100644 --- a/backend/src/main/resources/db/migration/V2__seed_data.sql +++ b/backend/src/main/resources/db/migration/V2__seed_data.sql @@ -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/%'; diff --git a/backend/src/main/resources/db/migration/V3__seed_openrouter_bot.sql b/backend/src/main/resources/db/migration/V3__seed_openrouter_bot.sql deleted file mode 100644 index cf32a653..00000000 --- a/backend/src/main/resources/db/migration/V3__seed_openrouter_bot.sql +++ /dev/null @@ -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' -); diff --git a/backend/src/main/resources/db/migration/V4__activity_log_updates.sql b/backend/src/main/resources/db/migration/V4__activity_log_updates.sql deleted file mode 100644 index 03d00660..00000000 --- a/backend/src/main/resources/db/migration/V4__activity_log_updates.sql +++ /dev/null @@ -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); diff --git a/backend/src/main/resources/db/migration/V5__add_points_columns_to_sale.sql b/backend/src/main/resources/db/migration/V5__add_points_columns_to_sale.sql deleted file mode 100644 index 288561ef..00000000 --- a/backend/src/main/resources/db/migration/V5__add_points_columns_to_sale.sql +++ /dev/null @@ -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; diff --git a/backend/src/main/resources/dev/final-target/final_target_schema.sql b/backend/src/main/resources/dev/final-target/final_target_schema.sql index f515888e..8abf1beb 100644 --- a/backend/src/main/resources/dev/final-target/final_target_schema.sql +++ b/backend/src/main/resources/dev/final-target/final_target_schema.sql @@ -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); diff --git a/backend/src/main/resources/dev/final-target/final_target_seed.sql b/backend/src/main/resources/dev/final-target/final_target_seed.sql index 447f1dad..dbd7296a 100644 --- a/backend/src/main/resources/dev/final-target/final_target_seed.sql +++ b/backend/src/main/resources/dev/final-target/final_target_seed.sql @@ -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'); \ No newline at end of file diff --git a/backend/uploads/avatars/001.webp b/backend/uploads/avatars/001.webp new file mode 100644 index 00000000..c7f1714f Binary files /dev/null and b/backend/uploads/avatars/001.webp differ diff --git a/backend/uploads/avatars/002.webp b/backend/uploads/avatars/002.webp new file mode 100644 index 00000000..0415e705 Binary files /dev/null and b/backend/uploads/avatars/002.webp differ diff --git a/backend/uploads/avatars/003.webp b/backend/uploads/avatars/003.webp new file mode 100644 index 00000000..999af5e0 Binary files /dev/null and b/backend/uploads/avatars/003.webp differ diff --git a/backend/uploads/avatars/004.webp b/backend/uploads/avatars/004.webp new file mode 100644 index 00000000..e739f364 Binary files /dev/null and b/backend/uploads/avatars/004.webp differ diff --git a/backend/uploads/avatars/005.webp b/backend/uploads/avatars/005.webp new file mode 100644 index 00000000..cf53b9cc Binary files /dev/null and b/backend/uploads/avatars/005.webp differ diff --git a/backend/uploads/avatars/006.webp b/backend/uploads/avatars/006.webp new file mode 100644 index 00000000..9bbfb611 Binary files /dev/null and b/backend/uploads/avatars/006.webp differ diff --git a/backend/uploads/avatars/007.webp b/backend/uploads/avatars/007.webp new file mode 100644 index 00000000..43fe6076 Binary files /dev/null and b/backend/uploads/avatars/007.webp differ diff --git a/backend/uploads/avatars/008.webp b/backend/uploads/avatars/008.webp new file mode 100644 index 00000000..b47718dd Binary files /dev/null and b/backend/uploads/avatars/008.webp differ diff --git a/backend/uploads/avatars/009.webp b/backend/uploads/avatars/009.webp new file mode 100644 index 00000000..cda172e5 Binary files /dev/null and b/backend/uploads/avatars/009.webp differ diff --git a/backend/uploads/avatars/010.webp b/backend/uploads/avatars/010.webp new file mode 100644 index 00000000..8bb1f2b4 Binary files /dev/null and b/backend/uploads/avatars/010.webp differ diff --git a/backend/uploads/avatars/011.webp b/backend/uploads/avatars/011.webp new file mode 100644 index 00000000..8050e761 Binary files /dev/null and b/backend/uploads/avatars/011.webp differ diff --git a/backend/uploads/avatars/012.webp b/backend/uploads/avatars/012.webp new file mode 100644 index 00000000..3f7972fb Binary files /dev/null and b/backend/uploads/avatars/012.webp differ diff --git a/backend/uploads/avatars/013.webp b/backend/uploads/avatars/013.webp new file mode 100644 index 00000000..ae652e3e Binary files /dev/null and b/backend/uploads/avatars/013.webp differ diff --git a/backend/uploads/avatars/014.webp b/backend/uploads/avatars/014.webp new file mode 100644 index 00000000..bb6f3cc8 Binary files /dev/null and b/backend/uploads/avatars/014.webp differ diff --git a/backend/uploads/avatars/015.webp b/backend/uploads/avatars/015.webp new file mode 100644 index 00000000..7a89ff39 Binary files /dev/null and b/backend/uploads/avatars/015.webp differ diff --git a/backend/uploads/avatars/016.webp b/backend/uploads/avatars/016.webp new file mode 100644 index 00000000..7b3c61ba Binary files /dev/null and b/backend/uploads/avatars/016.webp differ diff --git a/backend/uploads/avatars/017.webp b/backend/uploads/avatars/017.webp new file mode 100644 index 00000000..dd561abf Binary files /dev/null and b/backend/uploads/avatars/017.webp differ diff --git a/backend/uploads/avatars/018.webp b/backend/uploads/avatars/018.webp new file mode 100644 index 00000000..ab48bf12 Binary files /dev/null and b/backend/uploads/avatars/018.webp differ diff --git a/backend/uploads/avatars/019.webp b/backend/uploads/avatars/019.webp new file mode 100644 index 00000000..052ba76a Binary files /dev/null and b/backend/uploads/avatars/019.webp differ diff --git a/backend/uploads/avatars/020.webp b/backend/uploads/avatars/020.webp new file mode 100644 index 00000000..0399188a Binary files /dev/null and b/backend/uploads/avatars/020.webp differ diff --git a/backend/uploads/avatars/021.webp b/backend/uploads/avatars/021.webp new file mode 100644 index 00000000..eb6409fe Binary files /dev/null and b/backend/uploads/avatars/021.webp differ diff --git a/backend/uploads/avatars/022.webp b/backend/uploads/avatars/022.webp new file mode 100644 index 00000000..a8823f17 Binary files /dev/null and b/backend/uploads/avatars/022.webp differ diff --git a/backend/uploads/avatars/023.webp b/backend/uploads/avatars/023.webp new file mode 100644 index 00000000..284214c0 Binary files /dev/null and b/backend/uploads/avatars/023.webp differ diff --git a/backend/uploads/avatars/024.webp b/backend/uploads/avatars/024.webp new file mode 100644 index 00000000..8c9ad2d0 Binary files /dev/null and b/backend/uploads/avatars/024.webp differ diff --git a/backend/uploads/avatars/025.webp b/backend/uploads/avatars/025.webp new file mode 100644 index 00000000..2f196a7e Binary files /dev/null and b/backend/uploads/avatars/025.webp differ diff --git a/backend/uploads/avatars/026.webp b/backend/uploads/avatars/026.webp new file mode 100644 index 00000000..a33cdaf9 Binary files /dev/null and b/backend/uploads/avatars/026.webp differ diff --git a/backend/uploads/avatars/027.webp b/backend/uploads/avatars/027.webp new file mode 100644 index 00000000..e08c3563 Binary files /dev/null and b/backend/uploads/avatars/027.webp differ diff --git a/backend/uploads/avatars/028.webp b/backend/uploads/avatars/028.webp new file mode 100644 index 00000000..7b47cb25 Binary files /dev/null and b/backend/uploads/avatars/028.webp differ diff --git a/backend/uploads/avatars/029.webp b/backend/uploads/avatars/029.webp new file mode 100644 index 00000000..f7a68ee4 Binary files /dev/null and b/backend/uploads/avatars/029.webp differ diff --git a/backend/uploads/avatars/030.webp b/backend/uploads/avatars/030.webp new file mode 100644 index 00000000..fbbe64c2 Binary files /dev/null and b/backend/uploads/avatars/030.webp differ diff --git a/backend/uploads/avatars/031.webp b/backend/uploads/avatars/031.webp new file mode 100644 index 00000000..8111ca66 Binary files /dev/null and b/backend/uploads/avatars/031.webp differ diff --git a/backend/uploads/avatars/032.webp b/backend/uploads/avatars/032.webp new file mode 100644 index 00000000..f2e3826b Binary files /dev/null and b/backend/uploads/avatars/032.webp differ diff --git a/backend/uploads/avatars/033.webp b/backend/uploads/avatars/033.webp new file mode 100644 index 00000000..a38e4d53 Binary files /dev/null and b/backend/uploads/avatars/033.webp differ diff --git a/backend/uploads/avatars/034.webp b/backend/uploads/avatars/034.webp new file mode 100644 index 00000000..4e69c5f5 Binary files /dev/null and b/backend/uploads/avatars/034.webp differ diff --git a/backend/uploads/avatars/035.webp b/backend/uploads/avatars/035.webp new file mode 100644 index 00000000..fd75c7bc Binary files /dev/null and b/backend/uploads/avatars/035.webp differ diff --git a/backend/uploads/avatars/036.webp b/backend/uploads/avatars/036.webp new file mode 100644 index 00000000..9e07d612 Binary files /dev/null and b/backend/uploads/avatars/036.webp differ diff --git a/backend/uploads/avatars/037.webp b/backend/uploads/avatars/037.webp new file mode 100644 index 00000000..003ce914 Binary files /dev/null and b/backend/uploads/avatars/037.webp differ diff --git a/backend/uploads/avatars/038.webp b/backend/uploads/avatars/038.webp new file mode 100644 index 00000000..b50c35d4 Binary files /dev/null and b/backend/uploads/avatars/038.webp differ diff --git a/backend/uploads/avatars/039.webp b/backend/uploads/avatars/039.webp new file mode 100644 index 00000000..62f94011 Binary files /dev/null and b/backend/uploads/avatars/039.webp differ diff --git a/backend/uploads/avatars/040.webp b/backend/uploads/avatars/040.webp new file mode 100644 index 00000000..db5e6d97 Binary files /dev/null and b/backend/uploads/avatars/040.webp differ diff --git a/backend/uploads/avatars/041.webp b/backend/uploads/avatars/041.webp new file mode 100644 index 00000000..03be9480 Binary files /dev/null and b/backend/uploads/avatars/041.webp differ diff --git a/backend/uploads/avatars/042.webp b/backend/uploads/avatars/042.webp new file mode 100644 index 00000000..eb6ecaed Binary files /dev/null and b/backend/uploads/avatars/042.webp differ diff --git a/backend/uploads/avatars/043.webp b/backend/uploads/avatars/043.webp new file mode 100644 index 00000000..3aa7677f Binary files /dev/null and b/backend/uploads/avatars/043.webp differ diff --git a/backend/uploads/avatars/044.webp b/backend/uploads/avatars/044.webp new file mode 100644 index 00000000..619bbabc Binary files /dev/null and b/backend/uploads/avatars/044.webp differ diff --git a/backend/uploads/avatars/045.webp b/backend/uploads/avatars/045.webp new file mode 100644 index 00000000..0a2519fb Binary files /dev/null and b/backend/uploads/avatars/045.webp differ diff --git a/backend/uploads/avatars/046.webp b/backend/uploads/avatars/046.webp new file mode 100644 index 00000000..f30b5a8c Binary files /dev/null and b/backend/uploads/avatars/046.webp differ diff --git a/backend/uploads/avatars/047.webp b/backend/uploads/avatars/047.webp new file mode 100644 index 00000000..854d8498 Binary files /dev/null and b/backend/uploads/avatars/047.webp differ diff --git a/backend/uploads/avatars/048.webp b/backend/uploads/avatars/048.webp new file mode 100644 index 00000000..f25181b8 Binary files /dev/null and b/backend/uploads/avatars/048.webp differ diff --git a/backend/uploads/avatars/049.webp b/backend/uploads/avatars/049.webp new file mode 100644 index 00000000..9d51aa1f Binary files /dev/null and b/backend/uploads/avatars/049.webp differ diff --git a/backend/uploads/avatars/050.webp b/backend/uploads/avatars/050.webp new file mode 100644 index 00000000..6b8124a5 Binary files /dev/null and b/backend/uploads/avatars/050.webp differ diff --git a/backend/uploads/avatars/051.webp b/backend/uploads/avatars/051.webp new file mode 100644 index 00000000..863026d4 Binary files /dev/null and b/backend/uploads/avatars/051.webp differ diff --git a/backend/uploads/avatars/052.webp b/backend/uploads/avatars/052.webp new file mode 100644 index 00000000..81b8367f Binary files /dev/null and b/backend/uploads/avatars/052.webp differ diff --git a/backend/uploads/avatars/053.webp b/backend/uploads/avatars/053.webp new file mode 100644 index 00000000..6f5c9e8d Binary files /dev/null and b/backend/uploads/avatars/053.webp differ diff --git a/backend/uploads/avatars/054.webp b/backend/uploads/avatars/054.webp new file mode 100644 index 00000000..0bf4afdb Binary files /dev/null and b/backend/uploads/avatars/054.webp differ diff --git a/backend/uploads/avatars/055.webp b/backend/uploads/avatars/055.webp new file mode 100644 index 00000000..46ca8b02 Binary files /dev/null and b/backend/uploads/avatars/055.webp differ diff --git a/backend/uploads/avatars/056.webp b/backend/uploads/avatars/056.webp new file mode 100644 index 00000000..a39c8017 Binary files /dev/null and b/backend/uploads/avatars/056.webp differ diff --git a/backend/uploads/avatars/057.webp b/backend/uploads/avatars/057.webp new file mode 100644 index 00000000..d49167d1 Binary files /dev/null and b/backend/uploads/avatars/057.webp differ diff --git a/backend/uploads/avatars/058.webp b/backend/uploads/avatars/058.webp new file mode 100644 index 00000000..676981ab Binary files /dev/null and b/backend/uploads/avatars/058.webp differ diff --git a/backend/uploads/avatars/059.webp b/backend/uploads/avatars/059.webp new file mode 100644 index 00000000..19a44217 Binary files /dev/null and b/backend/uploads/avatars/059.webp differ diff --git a/backend/uploads/avatars/060.webp b/backend/uploads/avatars/060.webp new file mode 100644 index 00000000..bfbff000 Binary files /dev/null and b/backend/uploads/avatars/060.webp differ diff --git a/backend/uploads/avatars/061.webp b/backend/uploads/avatars/061.webp new file mode 100644 index 00000000..2444ea30 Binary files /dev/null and b/backend/uploads/avatars/061.webp differ diff --git a/backend/uploads/avatars/062.webp b/backend/uploads/avatars/062.webp new file mode 100644 index 00000000..b88275d1 Binary files /dev/null and b/backend/uploads/avatars/062.webp differ diff --git a/backend/uploads/avatars/063.webp b/backend/uploads/avatars/063.webp new file mode 100644 index 00000000..2d177a41 Binary files /dev/null and b/backend/uploads/avatars/063.webp differ diff --git a/backend/uploads/avatars/064.webp b/backend/uploads/avatars/064.webp new file mode 100644 index 00000000..7d3e350c Binary files /dev/null and b/backend/uploads/avatars/064.webp differ diff --git a/backend/uploads/avatars/065.webp b/backend/uploads/avatars/065.webp new file mode 100644 index 00000000..aa1e955c Binary files /dev/null and b/backend/uploads/avatars/065.webp differ diff --git a/backend/uploads/avatars/066.webp b/backend/uploads/avatars/066.webp new file mode 100644 index 00000000..ae4eea9f Binary files /dev/null and b/backend/uploads/avatars/066.webp differ diff --git a/backend/uploads/avatars/067.webp b/backend/uploads/avatars/067.webp new file mode 100644 index 00000000..22419497 Binary files /dev/null and b/backend/uploads/avatars/067.webp differ diff --git a/backend/uploads/avatars/068.webp b/backend/uploads/avatars/068.webp new file mode 100644 index 00000000..60b6193a Binary files /dev/null and b/backend/uploads/avatars/068.webp differ diff --git a/backend/uploads/avatars/069.webp b/backend/uploads/avatars/069.webp new file mode 100644 index 00000000..8930f5ff Binary files /dev/null and b/backend/uploads/avatars/069.webp differ diff --git a/backend/uploads/avatars/070.webp b/backend/uploads/avatars/070.webp new file mode 100644 index 00000000..28e0089b Binary files /dev/null and b/backend/uploads/avatars/070.webp differ diff --git a/backend/uploads/avatars/071.webp b/backend/uploads/avatars/071.webp new file mode 100644 index 00000000..231c3ba7 Binary files /dev/null and b/backend/uploads/avatars/071.webp differ diff --git a/backend/uploads/avatars/072.webp b/backend/uploads/avatars/072.webp new file mode 100644 index 00000000..c948d59c Binary files /dev/null and b/backend/uploads/avatars/072.webp differ diff --git a/backend/uploads/avatars/073.webp b/backend/uploads/avatars/073.webp new file mode 100644 index 00000000..f2f213c1 Binary files /dev/null and b/backend/uploads/avatars/073.webp differ diff --git a/backend/uploads/avatars/074.webp b/backend/uploads/avatars/074.webp new file mode 100644 index 00000000..4948d52a Binary files /dev/null and b/backend/uploads/avatars/074.webp differ diff --git a/backend/uploads/avatars/075.webp b/backend/uploads/avatars/075.webp new file mode 100644 index 00000000..3262c091 Binary files /dev/null and b/backend/uploads/avatars/075.webp differ diff --git a/backend/uploads/avatars/076.webp b/backend/uploads/avatars/076.webp new file mode 100644 index 00000000..110d6971 Binary files /dev/null and b/backend/uploads/avatars/076.webp differ diff --git a/backend/uploads/avatars/077.webp b/backend/uploads/avatars/077.webp new file mode 100644 index 00000000..c19fa09a Binary files /dev/null and b/backend/uploads/avatars/077.webp differ diff --git a/backend/uploads/avatars/078.webp b/backend/uploads/avatars/078.webp new file mode 100644 index 00000000..849e4339 Binary files /dev/null and b/backend/uploads/avatars/078.webp differ diff --git a/backend/uploads/avatars/079.webp b/backend/uploads/avatars/079.webp new file mode 100644 index 00000000..11385a2b Binary files /dev/null and b/backend/uploads/avatars/079.webp differ diff --git a/backend/uploads/avatars/080.webp b/backend/uploads/avatars/080.webp new file mode 100644 index 00000000..1a3299ff Binary files /dev/null and b/backend/uploads/avatars/080.webp differ diff --git a/backend/uploads/avatars/081.webp b/backend/uploads/avatars/081.webp new file mode 100644 index 00000000..5d5066e4 Binary files /dev/null and b/backend/uploads/avatars/081.webp differ diff --git a/backend/uploads/avatars/082.webp b/backend/uploads/avatars/082.webp new file mode 100644 index 00000000..ba1c6233 Binary files /dev/null and b/backend/uploads/avatars/082.webp differ diff --git a/backend/uploads/avatars/083.webp b/backend/uploads/avatars/083.webp new file mode 100644 index 00000000..f4f8346f Binary files /dev/null and b/backend/uploads/avatars/083.webp differ diff --git a/backend/uploads/avatars/084.webp b/backend/uploads/avatars/084.webp new file mode 100644 index 00000000..fcdf8a0b Binary files /dev/null and b/backend/uploads/avatars/084.webp differ diff --git a/backend/uploads/avatars/085.webp b/backend/uploads/avatars/085.webp new file mode 100644 index 00000000..d1ef747e Binary files /dev/null and b/backend/uploads/avatars/085.webp differ diff --git a/backend/uploads/avatars/086.webp b/backend/uploads/avatars/086.webp new file mode 100644 index 00000000..9dedd650 Binary files /dev/null and b/backend/uploads/avatars/086.webp differ diff --git a/backend/uploads/avatars/087.webp b/backend/uploads/avatars/087.webp new file mode 100644 index 00000000..b1717ba5 Binary files /dev/null and b/backend/uploads/avatars/087.webp differ diff --git a/backend/uploads/avatars/088.webp b/backend/uploads/avatars/088.webp new file mode 100644 index 00000000..f3ceca9d Binary files /dev/null and b/backend/uploads/avatars/088.webp differ diff --git a/backend/uploads/avatars/089.webp b/backend/uploads/avatars/089.webp new file mode 100644 index 00000000..ba03d78d Binary files /dev/null and b/backend/uploads/avatars/089.webp differ diff --git a/backend/uploads/avatars/090.webp b/backend/uploads/avatars/090.webp new file mode 100644 index 00000000..cc609ba6 Binary files /dev/null and b/backend/uploads/avatars/090.webp differ diff --git a/backend/uploads/avatars/091.webp b/backend/uploads/avatars/091.webp new file mode 100644 index 00000000..aefb3482 Binary files /dev/null and b/backend/uploads/avatars/091.webp differ diff --git a/backend/uploads/avatars/092.webp b/backend/uploads/avatars/092.webp new file mode 100644 index 00000000..c207cfd8 Binary files /dev/null and b/backend/uploads/avatars/092.webp differ diff --git a/backend/uploads/avatars/093.webp b/backend/uploads/avatars/093.webp new file mode 100644 index 00000000..f604e882 Binary files /dev/null and b/backend/uploads/avatars/093.webp differ diff --git a/backend/uploads/avatars/094.webp b/backend/uploads/avatars/094.webp new file mode 100644 index 00000000..4338d215 Binary files /dev/null and b/backend/uploads/avatars/094.webp differ diff --git a/backend/uploads/avatars/095.webp b/backend/uploads/avatars/095.webp new file mode 100644 index 00000000..5241bfb6 Binary files /dev/null and b/backend/uploads/avatars/095.webp differ diff --git a/backend/uploads/avatars/096.webp b/backend/uploads/avatars/096.webp new file mode 100644 index 00000000..03d95d1e Binary files /dev/null and b/backend/uploads/avatars/096.webp differ diff --git a/backend/uploads/avatars/097.webp b/backend/uploads/avatars/097.webp new file mode 100644 index 00000000..6baf7962 Binary files /dev/null and b/backend/uploads/avatars/097.webp differ diff --git a/backend/uploads/avatars/098.webp b/backend/uploads/avatars/098.webp new file mode 100644 index 00000000..5c7af65a Binary files /dev/null and b/backend/uploads/avatars/098.webp differ diff --git a/backend/uploads/avatars/099.webp b/backend/uploads/avatars/099.webp new file mode 100644 index 00000000..cf5d6829 Binary files /dev/null and b/backend/uploads/avatars/099.webp differ diff --git a/backend/uploads/avatars/100.webp b/backend/uploads/avatars/100.webp new file mode 100644 index 00000000..49da3d4e Binary files /dev/null and b/backend/uploads/avatars/100.webp differ diff --git a/backend/uploads/avatars/bot.webp b/backend/uploads/avatars/bot.webp new file mode 100644 index 00000000..a92f6868 Binary files /dev/null and b/backend/uploads/avatars/bot.webp differ diff --git a/backend/uploads/pets/001.webp b/backend/uploads/pets/001.webp new file mode 100644 index 00000000..ccd88582 Binary files /dev/null and b/backend/uploads/pets/001.webp differ diff --git a/backend/uploads/pets/002.webp b/backend/uploads/pets/002.webp new file mode 100644 index 00000000..be1f7bc3 Binary files /dev/null and b/backend/uploads/pets/002.webp differ diff --git a/backend/uploads/pets/003.webp b/backend/uploads/pets/003.webp new file mode 100644 index 00000000..359974e6 Binary files /dev/null and b/backend/uploads/pets/003.webp differ diff --git a/backend/uploads/pets/004.webp b/backend/uploads/pets/004.webp new file mode 100644 index 00000000..39d34b31 Binary files /dev/null and b/backend/uploads/pets/004.webp differ diff --git a/backend/uploads/pets/005.webp b/backend/uploads/pets/005.webp new file mode 100644 index 00000000..34624a76 Binary files /dev/null and b/backend/uploads/pets/005.webp differ diff --git a/backend/uploads/pets/006.webp b/backend/uploads/pets/006.webp new file mode 100644 index 00000000..6b79b9aa Binary files /dev/null and b/backend/uploads/pets/006.webp differ diff --git a/backend/uploads/pets/007.webp b/backend/uploads/pets/007.webp new file mode 100644 index 00000000..b678783e Binary files /dev/null and b/backend/uploads/pets/007.webp differ diff --git a/backend/uploads/pets/008.webp b/backend/uploads/pets/008.webp new file mode 100644 index 00000000..cbd70aff Binary files /dev/null and b/backend/uploads/pets/008.webp differ diff --git a/backend/uploads/pets/009.webp b/backend/uploads/pets/009.webp new file mode 100644 index 00000000..ed0e9f5f Binary files /dev/null and b/backend/uploads/pets/009.webp differ diff --git a/backend/uploads/pets/010.webp b/backend/uploads/pets/010.webp new file mode 100644 index 00000000..fc11c05f Binary files /dev/null and b/backend/uploads/pets/010.webp differ diff --git a/backend/uploads/pets/011.webp b/backend/uploads/pets/011.webp new file mode 100644 index 00000000..14c0a91a Binary files /dev/null and b/backend/uploads/pets/011.webp differ diff --git a/backend/uploads/pets/012.webp b/backend/uploads/pets/012.webp new file mode 100644 index 00000000..f7f407d2 Binary files /dev/null and b/backend/uploads/pets/012.webp differ diff --git a/backend/uploads/pets/013.webp b/backend/uploads/pets/013.webp new file mode 100644 index 00000000..daf369db Binary files /dev/null and b/backend/uploads/pets/013.webp differ diff --git a/backend/uploads/pets/014.webp b/backend/uploads/pets/014.webp new file mode 100644 index 00000000..33b9ac2c Binary files /dev/null and b/backend/uploads/pets/014.webp differ diff --git a/backend/uploads/pets/015.webp b/backend/uploads/pets/015.webp new file mode 100644 index 00000000..b3354d63 Binary files /dev/null and b/backend/uploads/pets/015.webp differ diff --git a/backend/uploads/pets/016.webp b/backend/uploads/pets/016.webp new file mode 100644 index 00000000..2c21cea6 Binary files /dev/null and b/backend/uploads/pets/016.webp differ diff --git a/backend/uploads/pets/017.webp b/backend/uploads/pets/017.webp new file mode 100644 index 00000000..b04d98d3 Binary files /dev/null and b/backend/uploads/pets/017.webp differ diff --git a/backend/uploads/pets/018.webp b/backend/uploads/pets/018.webp new file mode 100644 index 00000000..e378006e Binary files /dev/null and b/backend/uploads/pets/018.webp differ diff --git a/backend/uploads/pets/019.webp b/backend/uploads/pets/019.webp new file mode 100644 index 00000000..d9335c9f Binary files /dev/null and b/backend/uploads/pets/019.webp differ diff --git a/backend/uploads/pets/020.webp b/backend/uploads/pets/020.webp new file mode 100644 index 00000000..df4f9c3f Binary files /dev/null and b/backend/uploads/pets/020.webp differ diff --git a/backend/uploads/pets/021.webp b/backend/uploads/pets/021.webp new file mode 100644 index 00000000..f9d97399 Binary files /dev/null and b/backend/uploads/pets/021.webp differ diff --git a/backend/uploads/pets/022.webp b/backend/uploads/pets/022.webp new file mode 100644 index 00000000..07695381 Binary files /dev/null and b/backend/uploads/pets/022.webp differ diff --git a/backend/uploads/pets/023.webp b/backend/uploads/pets/023.webp new file mode 100644 index 00000000..400117d4 Binary files /dev/null and b/backend/uploads/pets/023.webp differ diff --git a/backend/uploads/pets/024.webp b/backend/uploads/pets/024.webp new file mode 100644 index 00000000..b65df01e Binary files /dev/null and b/backend/uploads/pets/024.webp differ diff --git a/backend/uploads/pets/025.webp b/backend/uploads/pets/025.webp new file mode 100644 index 00000000..654875cd Binary files /dev/null and b/backend/uploads/pets/025.webp differ diff --git a/backend/uploads/pets/026.webp b/backend/uploads/pets/026.webp new file mode 100644 index 00000000..f38a3fc5 Binary files /dev/null and b/backend/uploads/pets/026.webp differ diff --git a/backend/uploads/pets/027.webp b/backend/uploads/pets/027.webp new file mode 100644 index 00000000..c124b1e5 Binary files /dev/null and b/backend/uploads/pets/027.webp differ diff --git a/backend/uploads/pets/028.webp b/backend/uploads/pets/028.webp new file mode 100644 index 00000000..4d7abc21 Binary files /dev/null and b/backend/uploads/pets/028.webp differ diff --git a/backend/uploads/pets/029.webp b/backend/uploads/pets/029.webp new file mode 100644 index 00000000..89370b50 Binary files /dev/null and b/backend/uploads/pets/029.webp differ diff --git a/backend/uploads/pets/030.webp b/backend/uploads/pets/030.webp new file mode 100644 index 00000000..fd4fe5ee Binary files /dev/null and b/backend/uploads/pets/030.webp differ diff --git a/backend/uploads/pets/031.webp b/backend/uploads/pets/031.webp new file mode 100644 index 00000000..b95424a3 Binary files /dev/null and b/backend/uploads/pets/031.webp differ diff --git a/backend/uploads/pets/032.webp b/backend/uploads/pets/032.webp new file mode 100644 index 00000000..d83942d8 Binary files /dev/null and b/backend/uploads/pets/032.webp differ diff --git a/backend/uploads/pets/033.webp b/backend/uploads/pets/033.webp new file mode 100644 index 00000000..b1b0da10 Binary files /dev/null and b/backend/uploads/pets/033.webp differ diff --git a/backend/uploads/pets/034.webp b/backend/uploads/pets/034.webp new file mode 100644 index 00000000..98011b7a Binary files /dev/null and b/backend/uploads/pets/034.webp differ diff --git a/backend/uploads/pets/035.webp b/backend/uploads/pets/035.webp new file mode 100644 index 00000000..b05934fc Binary files /dev/null and b/backend/uploads/pets/035.webp differ diff --git a/backend/uploads/pets/036.webp b/backend/uploads/pets/036.webp new file mode 100644 index 00000000..692707f7 Binary files /dev/null and b/backend/uploads/pets/036.webp differ diff --git a/backend/uploads/pets/037.webp b/backend/uploads/pets/037.webp new file mode 100644 index 00000000..5443ca8d Binary files /dev/null and b/backend/uploads/pets/037.webp differ diff --git a/backend/uploads/pets/038.webp b/backend/uploads/pets/038.webp new file mode 100644 index 00000000..d396c70f Binary files /dev/null and b/backend/uploads/pets/038.webp differ diff --git a/backend/uploads/pets/039.webp b/backend/uploads/pets/039.webp new file mode 100644 index 00000000..74b4b14f Binary files /dev/null and b/backend/uploads/pets/039.webp differ diff --git a/backend/uploads/pets/040.webp b/backend/uploads/pets/040.webp new file mode 100644 index 00000000..5d304262 Binary files /dev/null and b/backend/uploads/pets/040.webp differ diff --git a/backend/uploads/pets/041.webp b/backend/uploads/pets/041.webp new file mode 100644 index 00000000..3ccb008e Binary files /dev/null and b/backend/uploads/pets/041.webp differ diff --git a/backend/uploads/pets/042.webp b/backend/uploads/pets/042.webp new file mode 100644 index 00000000..1d76285a Binary files /dev/null and b/backend/uploads/pets/042.webp differ diff --git a/backend/uploads/pets/043.webp b/backend/uploads/pets/043.webp new file mode 100644 index 00000000..413e44f9 Binary files /dev/null and b/backend/uploads/pets/043.webp differ diff --git a/backend/uploads/pets/044.webp b/backend/uploads/pets/044.webp new file mode 100644 index 00000000..b99a611f Binary files /dev/null and b/backend/uploads/pets/044.webp differ diff --git a/backend/uploads/pets/045.webp b/backend/uploads/pets/045.webp new file mode 100644 index 00000000..3cad5e6e Binary files /dev/null and b/backend/uploads/pets/045.webp differ diff --git a/backend/uploads/pets/046.webp b/backend/uploads/pets/046.webp new file mode 100644 index 00000000..e39d1f14 Binary files /dev/null and b/backend/uploads/pets/046.webp differ diff --git a/backend/uploads/pets/047.webp b/backend/uploads/pets/047.webp new file mode 100644 index 00000000..f929a0ce Binary files /dev/null and b/backend/uploads/pets/047.webp differ diff --git a/backend/uploads/pets/048.webp b/backend/uploads/pets/048.webp new file mode 100644 index 00000000..01cc52f8 Binary files /dev/null and b/backend/uploads/pets/048.webp differ diff --git a/backend/uploads/pets/049.webp b/backend/uploads/pets/049.webp new file mode 100644 index 00000000..3cea07b5 Binary files /dev/null and b/backend/uploads/pets/049.webp differ diff --git a/backend/uploads/pets/050.webp b/backend/uploads/pets/050.webp new file mode 100644 index 00000000..079886db Binary files /dev/null and b/backend/uploads/pets/050.webp differ diff --git a/backend/uploads/pets/051.webp b/backend/uploads/pets/051.webp new file mode 100644 index 00000000..5e683607 Binary files /dev/null and b/backend/uploads/pets/051.webp differ diff --git a/backend/uploads/pets/052.webp b/backend/uploads/pets/052.webp new file mode 100644 index 00000000..6c334394 Binary files /dev/null and b/backend/uploads/pets/052.webp differ diff --git a/backend/uploads/pets/053.webp b/backend/uploads/pets/053.webp new file mode 100644 index 00000000..b9d61bbd Binary files /dev/null and b/backend/uploads/pets/053.webp differ diff --git a/backend/uploads/pets/054.webp b/backend/uploads/pets/054.webp new file mode 100644 index 00000000..ee98ced1 Binary files /dev/null and b/backend/uploads/pets/054.webp differ diff --git a/backend/uploads/pets/055.webp b/backend/uploads/pets/055.webp new file mode 100644 index 00000000..084af9a1 Binary files /dev/null and b/backend/uploads/pets/055.webp differ diff --git a/backend/uploads/pets/056.webp b/backend/uploads/pets/056.webp new file mode 100644 index 00000000..66facf07 Binary files /dev/null and b/backend/uploads/pets/056.webp differ diff --git a/backend/uploads/pets/057.webp b/backend/uploads/pets/057.webp new file mode 100644 index 00000000..4f6b3b51 Binary files /dev/null and b/backend/uploads/pets/057.webp differ diff --git a/backend/uploads/pets/058.webp b/backend/uploads/pets/058.webp new file mode 100644 index 00000000..430f5c4a Binary files /dev/null and b/backend/uploads/pets/058.webp differ diff --git a/backend/uploads/pets/059.webp b/backend/uploads/pets/059.webp new file mode 100644 index 00000000..23b0f85a Binary files /dev/null and b/backend/uploads/pets/059.webp differ diff --git a/backend/uploads/pets/060.webp b/backend/uploads/pets/060.webp new file mode 100644 index 00000000..5123f7ae Binary files /dev/null and b/backend/uploads/pets/060.webp differ diff --git a/backend/uploads/pets/061.webp b/backend/uploads/pets/061.webp new file mode 100644 index 00000000..a04e7446 Binary files /dev/null and b/backend/uploads/pets/061.webp differ diff --git a/backend/uploads/pets/062.webp b/backend/uploads/pets/062.webp new file mode 100644 index 00000000..1652a575 Binary files /dev/null and b/backend/uploads/pets/062.webp differ diff --git a/backend/uploads/pets/063.webp b/backend/uploads/pets/063.webp new file mode 100644 index 00000000..99e93a86 Binary files /dev/null and b/backend/uploads/pets/063.webp differ diff --git a/backend/uploads/pets/064.webp b/backend/uploads/pets/064.webp new file mode 100644 index 00000000..ce7e185c Binary files /dev/null and b/backend/uploads/pets/064.webp differ diff --git a/backend/uploads/pets/065.webp b/backend/uploads/pets/065.webp new file mode 100644 index 00000000..5864ca98 Binary files /dev/null and b/backend/uploads/pets/065.webp differ diff --git a/backend/uploads/pets/066.webp b/backend/uploads/pets/066.webp new file mode 100644 index 00000000..3771ccd4 Binary files /dev/null and b/backend/uploads/pets/066.webp differ diff --git a/backend/uploads/pets/067.webp b/backend/uploads/pets/067.webp new file mode 100644 index 00000000..21b2292a Binary files /dev/null and b/backend/uploads/pets/067.webp differ diff --git a/backend/uploads/pets/068.webp b/backend/uploads/pets/068.webp new file mode 100644 index 00000000..6f262352 Binary files /dev/null and b/backend/uploads/pets/068.webp differ diff --git a/backend/uploads/pets/069.webp b/backend/uploads/pets/069.webp new file mode 100644 index 00000000..107c3f49 Binary files /dev/null and b/backend/uploads/pets/069.webp differ diff --git a/backend/uploads/pets/070.webp b/backend/uploads/pets/070.webp new file mode 100644 index 00000000..21699aa4 Binary files /dev/null and b/backend/uploads/pets/070.webp differ diff --git a/backend/uploads/pets/071.webp b/backend/uploads/pets/071.webp new file mode 100644 index 00000000..ff14840a Binary files /dev/null and b/backend/uploads/pets/071.webp differ diff --git a/backend/uploads/pets/072.webp b/backend/uploads/pets/072.webp new file mode 100644 index 00000000..c730848c Binary files /dev/null and b/backend/uploads/pets/072.webp differ diff --git a/backend/uploads/pets/073.webp b/backend/uploads/pets/073.webp new file mode 100644 index 00000000..daaa276c Binary files /dev/null and b/backend/uploads/pets/073.webp differ diff --git a/backend/uploads/pets/074.webp b/backend/uploads/pets/074.webp new file mode 100644 index 00000000..23b76888 Binary files /dev/null and b/backend/uploads/pets/074.webp differ diff --git a/backend/uploads/pets/075.webp b/backend/uploads/pets/075.webp new file mode 100644 index 00000000..4262db1a Binary files /dev/null and b/backend/uploads/pets/075.webp differ diff --git a/backend/uploads/pets/076.webp b/backend/uploads/pets/076.webp new file mode 100644 index 00000000..08d5b420 Binary files /dev/null and b/backend/uploads/pets/076.webp differ diff --git a/backend/uploads/pets/077.webp b/backend/uploads/pets/077.webp new file mode 100644 index 00000000..ad03239d Binary files /dev/null and b/backend/uploads/pets/077.webp differ diff --git a/backend/uploads/pets/078.webp b/backend/uploads/pets/078.webp new file mode 100644 index 00000000..1001add1 Binary files /dev/null and b/backend/uploads/pets/078.webp differ diff --git a/backend/uploads/pets/079.webp b/backend/uploads/pets/079.webp new file mode 100644 index 00000000..e92085e1 Binary files /dev/null and b/backend/uploads/pets/079.webp differ diff --git a/backend/uploads/pets/080.webp b/backend/uploads/pets/080.webp new file mode 100644 index 00000000..bcaef257 Binary files /dev/null and b/backend/uploads/pets/080.webp differ diff --git a/backend/uploads/pets/081.webp b/backend/uploads/pets/081.webp new file mode 100644 index 00000000..6f0e0642 Binary files /dev/null and b/backend/uploads/pets/081.webp differ diff --git a/backend/uploads/pets/082.webp b/backend/uploads/pets/082.webp new file mode 100644 index 00000000..eab2b19a Binary files /dev/null and b/backend/uploads/pets/082.webp differ diff --git a/backend/uploads/pets/083.webp b/backend/uploads/pets/083.webp new file mode 100644 index 00000000..684eb290 Binary files /dev/null and b/backend/uploads/pets/083.webp differ diff --git a/backend/uploads/pets/084.webp b/backend/uploads/pets/084.webp new file mode 100644 index 00000000..4b7ff963 Binary files /dev/null and b/backend/uploads/pets/084.webp differ diff --git a/backend/uploads/pets/085.webp b/backend/uploads/pets/085.webp new file mode 100644 index 00000000..1ac48995 Binary files /dev/null and b/backend/uploads/pets/085.webp differ diff --git a/backend/uploads/pets/086.webp b/backend/uploads/pets/086.webp new file mode 100644 index 00000000..ff288d54 Binary files /dev/null and b/backend/uploads/pets/086.webp differ diff --git a/backend/uploads/pets/087.webp b/backend/uploads/pets/087.webp new file mode 100644 index 00000000..d46a1dcb Binary files /dev/null and b/backend/uploads/pets/087.webp differ diff --git a/backend/uploads/pets/088.webp b/backend/uploads/pets/088.webp new file mode 100644 index 00000000..2008bf53 Binary files /dev/null and b/backend/uploads/pets/088.webp differ diff --git a/backend/uploads/pets/089.webp b/backend/uploads/pets/089.webp new file mode 100644 index 00000000..245e35e1 Binary files /dev/null and b/backend/uploads/pets/089.webp differ diff --git a/backend/uploads/pets/090.webp b/backend/uploads/pets/090.webp new file mode 100644 index 00000000..7ad13f23 Binary files /dev/null and b/backend/uploads/pets/090.webp differ diff --git a/backend/uploads/pets/091.webp b/backend/uploads/pets/091.webp new file mode 100644 index 00000000..d81bbe09 Binary files /dev/null and b/backend/uploads/pets/091.webp differ diff --git a/backend/uploads/pets/092.webp b/backend/uploads/pets/092.webp new file mode 100644 index 00000000..14ea5315 Binary files /dev/null and b/backend/uploads/pets/092.webp differ diff --git a/backend/uploads/pets/093.webp b/backend/uploads/pets/093.webp new file mode 100644 index 00000000..c8ed2ee6 Binary files /dev/null and b/backend/uploads/pets/093.webp differ diff --git a/backend/uploads/pets/094.webp b/backend/uploads/pets/094.webp new file mode 100644 index 00000000..4a36d164 Binary files /dev/null and b/backend/uploads/pets/094.webp differ diff --git a/backend/uploads/pets/095.webp b/backend/uploads/pets/095.webp new file mode 100644 index 00000000..07abe07a Binary files /dev/null and b/backend/uploads/pets/095.webp differ diff --git a/backend/uploads/pets/096.webp b/backend/uploads/pets/096.webp new file mode 100644 index 00000000..87f17555 Binary files /dev/null and b/backend/uploads/pets/096.webp differ diff --git a/backend/uploads/pets/097.webp b/backend/uploads/pets/097.webp new file mode 100644 index 00000000..9a70552b Binary files /dev/null and b/backend/uploads/pets/097.webp differ diff --git a/backend/uploads/pets/098.webp b/backend/uploads/pets/098.webp new file mode 100644 index 00000000..12e68fb0 Binary files /dev/null and b/backend/uploads/pets/098.webp differ diff --git a/backend/uploads/pets/099.webp b/backend/uploads/pets/099.webp new file mode 100644 index 00000000..320b19a3 Binary files /dev/null and b/backend/uploads/pets/099.webp differ diff --git a/backend/uploads/pets/100.webp b/backend/uploads/pets/100.webp new file mode 100644 index 00000000..7363d204 Binary files /dev/null and b/backend/uploads/pets/100.webp differ diff --git a/backend/uploads/products/001.webp b/backend/uploads/products/001.webp new file mode 100644 index 00000000..a77c1a7f Binary files /dev/null and b/backend/uploads/products/001.webp differ diff --git a/backend/uploads/products/002.webp b/backend/uploads/products/002.webp new file mode 100644 index 00000000..e83cb558 Binary files /dev/null and b/backend/uploads/products/002.webp differ diff --git a/backend/uploads/products/003.webp b/backend/uploads/products/003.webp new file mode 100644 index 00000000..6775cd42 Binary files /dev/null and b/backend/uploads/products/003.webp differ diff --git a/backend/uploads/products/004.webp b/backend/uploads/products/004.webp new file mode 100644 index 00000000..bf82fce3 Binary files /dev/null and b/backend/uploads/products/004.webp differ diff --git a/backend/uploads/products/005.webp b/backend/uploads/products/005.webp new file mode 100644 index 00000000..15788d23 Binary files /dev/null and b/backend/uploads/products/005.webp differ diff --git a/backend/uploads/products/006.webp b/backend/uploads/products/006.webp new file mode 100644 index 00000000..3ee12fb8 Binary files /dev/null and b/backend/uploads/products/006.webp differ diff --git a/backend/uploads/products/007.webp b/backend/uploads/products/007.webp new file mode 100644 index 00000000..c6fe28b5 Binary files /dev/null and b/backend/uploads/products/007.webp differ diff --git a/backend/uploads/products/008.webp b/backend/uploads/products/008.webp new file mode 100644 index 00000000..8535d69d Binary files /dev/null and b/backend/uploads/products/008.webp differ diff --git a/backend/uploads/products/009.webp b/backend/uploads/products/009.webp new file mode 100644 index 00000000..acf2c1af Binary files /dev/null and b/backend/uploads/products/009.webp differ diff --git a/backend/uploads/products/010.webp b/backend/uploads/products/010.webp new file mode 100644 index 00000000..bb0e238b Binary files /dev/null and b/backend/uploads/products/010.webp differ diff --git a/backend/uploads/products/011.webp b/backend/uploads/products/011.webp new file mode 100644 index 00000000..ec8122c4 Binary files /dev/null and b/backend/uploads/products/011.webp differ diff --git a/backend/uploads/products/012.webp b/backend/uploads/products/012.webp new file mode 100644 index 00000000..ff2641d2 Binary files /dev/null and b/backend/uploads/products/012.webp differ diff --git a/backend/uploads/products/013.webp b/backend/uploads/products/013.webp new file mode 100644 index 00000000..5e70b2c1 Binary files /dev/null and b/backend/uploads/products/013.webp differ diff --git a/backend/uploads/products/014.webp b/backend/uploads/products/014.webp new file mode 100644 index 00000000..06ab3918 Binary files /dev/null and b/backend/uploads/products/014.webp differ diff --git a/backend/uploads/products/015.webp b/backend/uploads/products/015.webp new file mode 100644 index 00000000..c966feb9 Binary files /dev/null and b/backend/uploads/products/015.webp differ diff --git a/backend/uploads/products/016.webp b/backend/uploads/products/016.webp new file mode 100644 index 00000000..ba84a79e Binary files /dev/null and b/backend/uploads/products/016.webp differ diff --git a/backend/uploads/products/017.webp b/backend/uploads/products/017.webp new file mode 100644 index 00000000..95c20edb Binary files /dev/null and b/backend/uploads/products/017.webp differ diff --git a/backend/uploads/products/018.webp b/backend/uploads/products/018.webp new file mode 100644 index 00000000..4d80ee5a Binary files /dev/null and b/backend/uploads/products/018.webp differ diff --git a/backend/uploads/products/019.webp b/backend/uploads/products/019.webp new file mode 100644 index 00000000..0ec3b2a4 Binary files /dev/null and b/backend/uploads/products/019.webp differ diff --git a/backend/uploads/products/020.webp b/backend/uploads/products/020.webp new file mode 100644 index 00000000..3f33062d Binary files /dev/null and b/backend/uploads/products/020.webp differ diff --git a/backend/uploads/products/021.webp b/backend/uploads/products/021.webp new file mode 100644 index 00000000..9ae973ca Binary files /dev/null and b/backend/uploads/products/021.webp differ diff --git a/backend/uploads/products/022.webp b/backend/uploads/products/022.webp new file mode 100644 index 00000000..d4b076bd Binary files /dev/null and b/backend/uploads/products/022.webp differ diff --git a/backend/uploads/products/023.webp b/backend/uploads/products/023.webp new file mode 100644 index 00000000..b25ff3ec Binary files /dev/null and b/backend/uploads/products/023.webp differ diff --git a/backend/uploads/products/024.webp b/backend/uploads/products/024.webp new file mode 100644 index 00000000..75988867 Binary files /dev/null and b/backend/uploads/products/024.webp differ diff --git a/backend/uploads/products/025.webp b/backend/uploads/products/025.webp new file mode 100644 index 00000000..7aa4b93e Binary files /dev/null and b/backend/uploads/products/025.webp differ diff --git a/backend/uploads/products/026.webp b/backend/uploads/products/026.webp new file mode 100644 index 00000000..b401bbe3 Binary files /dev/null and b/backend/uploads/products/026.webp differ diff --git a/backend/uploads/products/027.webp b/backend/uploads/products/027.webp new file mode 100644 index 00000000..d5d2a797 Binary files /dev/null and b/backend/uploads/products/027.webp differ diff --git a/backend/uploads/products/028.webp b/backend/uploads/products/028.webp new file mode 100644 index 00000000..7c2d739e Binary files /dev/null and b/backend/uploads/products/028.webp differ diff --git a/backend/uploads/products/029.webp b/backend/uploads/products/029.webp new file mode 100644 index 00000000..6956e5ae Binary files /dev/null and b/backend/uploads/products/029.webp differ diff --git a/backend/uploads/products/030.webp b/backend/uploads/products/030.webp new file mode 100644 index 00000000..e25dc6ac Binary files /dev/null and b/backend/uploads/products/030.webp differ diff --git a/backend/uploads/products/031.webp b/backend/uploads/products/031.webp new file mode 100644 index 00000000..cc8e80e9 Binary files /dev/null and b/backend/uploads/products/031.webp differ diff --git a/backend/uploads/products/032.webp b/backend/uploads/products/032.webp new file mode 100644 index 00000000..ae44bf67 Binary files /dev/null and b/backend/uploads/products/032.webp differ diff --git a/backend/uploads/products/033.webp b/backend/uploads/products/033.webp new file mode 100644 index 00000000..3bbd6036 Binary files /dev/null and b/backend/uploads/products/033.webp differ diff --git a/backend/uploads/products/034.webp b/backend/uploads/products/034.webp new file mode 100644 index 00000000..9858dd89 Binary files /dev/null and b/backend/uploads/products/034.webp differ diff --git a/backend/uploads/products/035.webp b/backend/uploads/products/035.webp new file mode 100644 index 00000000..d9444061 Binary files /dev/null and b/backend/uploads/products/035.webp differ diff --git a/backend/uploads/products/036.webp b/backend/uploads/products/036.webp new file mode 100644 index 00000000..5bd3632d Binary files /dev/null and b/backend/uploads/products/036.webp differ diff --git a/backend/uploads/products/037.webp b/backend/uploads/products/037.webp new file mode 100644 index 00000000..99035bd8 Binary files /dev/null and b/backend/uploads/products/037.webp differ diff --git a/backend/uploads/products/038.webp b/backend/uploads/products/038.webp new file mode 100644 index 00000000..8cfb8230 Binary files /dev/null and b/backend/uploads/products/038.webp differ diff --git a/backend/uploads/products/039.webp b/backend/uploads/products/039.webp new file mode 100644 index 00000000..1c0ddf56 Binary files /dev/null and b/backend/uploads/products/039.webp differ diff --git a/backend/uploads/products/040.webp b/backend/uploads/products/040.webp new file mode 100644 index 00000000..117595f8 Binary files /dev/null and b/backend/uploads/products/040.webp differ diff --git a/backend/uploads/products/041.webp b/backend/uploads/products/041.webp new file mode 100644 index 00000000..57eba1fa Binary files /dev/null and b/backend/uploads/products/041.webp differ diff --git a/backend/uploads/products/042.webp b/backend/uploads/products/042.webp new file mode 100644 index 00000000..bfa2aba1 Binary files /dev/null and b/backend/uploads/products/042.webp differ diff --git a/backend/uploads/products/043.webp b/backend/uploads/products/043.webp new file mode 100644 index 00000000..226358f0 Binary files /dev/null and b/backend/uploads/products/043.webp differ diff --git a/backend/uploads/products/044.webp b/backend/uploads/products/044.webp new file mode 100644 index 00000000..f46f13b4 Binary files /dev/null and b/backend/uploads/products/044.webp differ diff --git a/backend/uploads/products/045.webp b/backend/uploads/products/045.webp new file mode 100644 index 00000000..92b348b0 Binary files /dev/null and b/backend/uploads/products/045.webp differ diff --git a/backend/uploads/products/046.webp b/backend/uploads/products/046.webp new file mode 100644 index 00000000..4f797232 Binary files /dev/null and b/backend/uploads/products/046.webp differ diff --git a/backend/uploads/products/047.webp b/backend/uploads/products/047.webp new file mode 100644 index 00000000..4b04eba5 Binary files /dev/null and b/backend/uploads/products/047.webp differ diff --git a/backend/uploads/products/048.webp b/backend/uploads/products/048.webp new file mode 100644 index 00000000..20e60743 Binary files /dev/null and b/backend/uploads/products/048.webp differ diff --git a/backend/uploads/products/049.webp b/backend/uploads/products/049.webp new file mode 100644 index 00000000..7517f9b0 Binary files /dev/null and b/backend/uploads/products/049.webp differ diff --git a/backend/uploads/products/050.webp b/backend/uploads/products/050.webp new file mode 100644 index 00000000..f3e04000 Binary files /dev/null and b/backend/uploads/products/050.webp differ diff --git a/backend/uploads/products/051.webp b/backend/uploads/products/051.webp new file mode 100644 index 00000000..59843fe7 Binary files /dev/null and b/backend/uploads/products/051.webp differ diff --git a/backend/uploads/products/052.webp b/backend/uploads/products/052.webp new file mode 100644 index 00000000..358bfdb6 Binary files /dev/null and b/backend/uploads/products/052.webp differ diff --git a/backend/uploads/products/053.webp b/backend/uploads/products/053.webp new file mode 100644 index 00000000..0be3d445 Binary files /dev/null and b/backend/uploads/products/053.webp differ diff --git a/backend/uploads/products/054.webp b/backend/uploads/products/054.webp new file mode 100644 index 00000000..d23032f5 Binary files /dev/null and b/backend/uploads/products/054.webp differ diff --git a/backend/uploads/products/055.webp b/backend/uploads/products/055.webp new file mode 100644 index 00000000..a4ceba73 Binary files /dev/null and b/backend/uploads/products/055.webp differ diff --git a/backend/uploads/products/056.webp b/backend/uploads/products/056.webp new file mode 100644 index 00000000..c33eca4a Binary files /dev/null and b/backend/uploads/products/056.webp differ diff --git a/backend/uploads/products/057.webp b/backend/uploads/products/057.webp new file mode 100644 index 00000000..57d7da86 Binary files /dev/null and b/backend/uploads/products/057.webp differ diff --git a/backend/uploads/products/058.webp b/backend/uploads/products/058.webp new file mode 100644 index 00000000..6b110093 Binary files /dev/null and b/backend/uploads/products/058.webp differ diff --git a/backend/uploads/products/059.webp b/backend/uploads/products/059.webp new file mode 100644 index 00000000..d6b84e2e Binary files /dev/null and b/backend/uploads/products/059.webp differ diff --git a/backend/uploads/products/060.webp b/backend/uploads/products/060.webp new file mode 100644 index 00000000..1fc756da Binary files /dev/null and b/backend/uploads/products/060.webp differ diff --git a/backend/uploads/products/061.webp b/backend/uploads/products/061.webp new file mode 100644 index 00000000..e9d44375 Binary files /dev/null and b/backend/uploads/products/061.webp differ diff --git a/backend/uploads/products/062.webp b/backend/uploads/products/062.webp new file mode 100644 index 00000000..3c62c0c7 Binary files /dev/null and b/backend/uploads/products/062.webp differ diff --git a/backend/uploads/products/063.webp b/backend/uploads/products/063.webp new file mode 100644 index 00000000..4ba1522f Binary files /dev/null and b/backend/uploads/products/063.webp differ diff --git a/backend/uploads/products/064.webp b/backend/uploads/products/064.webp new file mode 100644 index 00000000..b90beba7 Binary files /dev/null and b/backend/uploads/products/064.webp differ diff --git a/backend/uploads/products/065.webp b/backend/uploads/products/065.webp new file mode 100644 index 00000000..8bf0b426 Binary files /dev/null and b/backend/uploads/products/065.webp differ diff --git a/backend/uploads/products/066.webp b/backend/uploads/products/066.webp new file mode 100644 index 00000000..b7e1ddfe Binary files /dev/null and b/backend/uploads/products/066.webp differ diff --git a/backend/uploads/products/067.webp b/backend/uploads/products/067.webp new file mode 100644 index 00000000..bb361f2f Binary files /dev/null and b/backend/uploads/products/067.webp differ diff --git a/backend/uploads/products/068.webp b/backend/uploads/products/068.webp new file mode 100644 index 00000000..06064410 Binary files /dev/null and b/backend/uploads/products/068.webp differ diff --git a/backend/uploads/products/069.webp b/backend/uploads/products/069.webp new file mode 100644 index 00000000..22cceb73 Binary files /dev/null and b/backend/uploads/products/069.webp differ diff --git a/backend/uploads/products/070.webp b/backend/uploads/products/070.webp new file mode 100644 index 00000000..62054903 Binary files /dev/null and b/backend/uploads/products/070.webp differ diff --git a/backend/uploads/products/071.webp b/backend/uploads/products/071.webp new file mode 100644 index 00000000..640da88c Binary files /dev/null and b/backend/uploads/products/071.webp differ diff --git a/backend/uploads/products/072.webp b/backend/uploads/products/072.webp new file mode 100644 index 00000000..51f7fa89 Binary files /dev/null and b/backend/uploads/products/072.webp differ diff --git a/backend/uploads/products/073.webp b/backend/uploads/products/073.webp new file mode 100644 index 00000000..f07e631e Binary files /dev/null and b/backend/uploads/products/073.webp differ diff --git a/backend/uploads/products/074.webp b/backend/uploads/products/074.webp new file mode 100644 index 00000000..9a1f9009 Binary files /dev/null and b/backend/uploads/products/074.webp differ diff --git a/backend/uploads/products/075.webp b/backend/uploads/products/075.webp new file mode 100644 index 00000000..9e8c5f4b Binary files /dev/null and b/backend/uploads/products/075.webp differ diff --git a/backend/uploads/products/076.webp b/backend/uploads/products/076.webp new file mode 100644 index 00000000..f6a7ef03 Binary files /dev/null and b/backend/uploads/products/076.webp differ diff --git a/backend/uploads/products/077.webp b/backend/uploads/products/077.webp new file mode 100644 index 00000000..0aa81b00 Binary files /dev/null and b/backend/uploads/products/077.webp differ diff --git a/backend/uploads/products/078.webp b/backend/uploads/products/078.webp new file mode 100644 index 00000000..a0f729a2 Binary files /dev/null and b/backend/uploads/products/078.webp differ diff --git a/backend/uploads/products/079.webp b/backend/uploads/products/079.webp new file mode 100644 index 00000000..d0027a5f Binary files /dev/null and b/backend/uploads/products/079.webp differ diff --git a/backend/uploads/products/080.webp b/backend/uploads/products/080.webp new file mode 100644 index 00000000..3725f3d4 Binary files /dev/null and b/backend/uploads/products/080.webp differ diff --git a/backend/uploads/products/081.webp b/backend/uploads/products/081.webp new file mode 100644 index 00000000..7d4bb52f Binary files /dev/null and b/backend/uploads/products/081.webp differ diff --git a/backend/uploads/products/082.webp b/backend/uploads/products/082.webp new file mode 100644 index 00000000..39f6d0ea Binary files /dev/null and b/backend/uploads/products/082.webp differ diff --git a/backend/uploads/products/083.webp b/backend/uploads/products/083.webp new file mode 100644 index 00000000..d600462f Binary files /dev/null and b/backend/uploads/products/083.webp differ diff --git a/backend/uploads/products/084.webp b/backend/uploads/products/084.webp new file mode 100644 index 00000000..d00c2b26 Binary files /dev/null and b/backend/uploads/products/084.webp differ diff --git a/backend/uploads/products/085.webp b/backend/uploads/products/085.webp new file mode 100644 index 00000000..d9489ee0 Binary files /dev/null and b/backend/uploads/products/085.webp differ diff --git a/backend/uploads/products/086.webp b/backend/uploads/products/086.webp new file mode 100644 index 00000000..079312ab Binary files /dev/null and b/backend/uploads/products/086.webp differ diff --git a/backend/uploads/products/087.webp b/backend/uploads/products/087.webp new file mode 100644 index 00000000..4a822786 Binary files /dev/null and b/backend/uploads/products/087.webp differ diff --git a/backend/uploads/products/088.webp b/backend/uploads/products/088.webp new file mode 100644 index 00000000..d7d5553e Binary files /dev/null and b/backend/uploads/products/088.webp differ diff --git a/backend/uploads/products/089.webp b/backend/uploads/products/089.webp new file mode 100644 index 00000000..21274a49 Binary files /dev/null and b/backend/uploads/products/089.webp differ diff --git a/backend/uploads/products/090.webp b/backend/uploads/products/090.webp new file mode 100644 index 00000000..ab301a06 Binary files /dev/null and b/backend/uploads/products/090.webp differ diff --git a/backend/uploads/products/091.webp b/backend/uploads/products/091.webp new file mode 100644 index 00000000..b336a344 Binary files /dev/null and b/backend/uploads/products/091.webp differ diff --git a/backend/uploads/products/092.webp b/backend/uploads/products/092.webp new file mode 100644 index 00000000..6ef28af2 Binary files /dev/null and b/backend/uploads/products/092.webp differ diff --git a/backend/uploads/products/093.webp b/backend/uploads/products/093.webp new file mode 100644 index 00000000..73ea1e9a Binary files /dev/null and b/backend/uploads/products/093.webp differ diff --git a/backend/uploads/products/094.webp b/backend/uploads/products/094.webp new file mode 100644 index 00000000..c0a5b588 Binary files /dev/null and b/backend/uploads/products/094.webp differ diff --git a/backend/uploads/products/095.webp b/backend/uploads/products/095.webp new file mode 100644 index 00000000..81d343cb Binary files /dev/null and b/backend/uploads/products/095.webp differ diff --git a/backend/uploads/products/096.webp b/backend/uploads/products/096.webp new file mode 100644 index 00000000..50cf062a Binary files /dev/null and b/backend/uploads/products/096.webp differ diff --git a/backend/uploads/products/097.webp b/backend/uploads/products/097.webp new file mode 100644 index 00000000..574f9610 Binary files /dev/null and b/backend/uploads/products/097.webp differ diff --git a/backend/uploads/products/098.webp b/backend/uploads/products/098.webp new file mode 100644 index 00000000..a5c5cbb0 Binary files /dev/null and b/backend/uploads/products/098.webp differ diff --git a/backend/uploads/products/099.webp b/backend/uploads/products/099.webp new file mode 100644 index 00000000..8ac94f20 Binary files /dev/null and b/backend/uploads/products/099.webp differ diff --git a/backend/uploads/products/100.webp b/backend/uploads/products/100.webp new file mode 100644 index 00000000..485919c3 Binary files /dev/null and b/backend/uploads/products/100.webp differ