Merge branch 'main' into AttachmentsToChat
1
.gitignore
vendored
@@ -2,5 +2,4 @@
|
|||||||
.local/
|
.local/
|
||||||
commit-patches/
|
commit-patches/
|
||||||
temp_photos/
|
temp_photos/
|
||||||
uploads/
|
|
||||||
.env
|
.env
|
||||||
|
|||||||
5
backend/.gitignore
vendored
@@ -46,7 +46,10 @@ build/
|
|||||||
### Project Specific ###
|
### Project Specific ###
|
||||||
src/test/
|
src/test/
|
||||||
tmp/
|
tmp/
|
||||||
uploads/
|
uploads/*
|
||||||
|
!uploads/avatars/
|
||||||
|
!uploads/pets/
|
||||||
|
!uploads/products/
|
||||||
|
|
||||||
### Temp and backup files ###
|
### Temp and backup files ###
|
||||||
*.backup
|
*.backup
|
||||||
|
|||||||
@@ -9,5 +9,6 @@ RUN mvn -q -DskipTests package
|
|||||||
FROM eclipse-temurin:25-jre
|
FROM eclipse-temurin:25-jre
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY --from=build /app/target/*.jar app.jar
|
COPY --from=build /app/target/*.jar app.jar
|
||||||
|
COPY uploads ./uploads
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
ENTRYPOINT ["java","-jar","app.jar"]
|
ENTRYPOINT ["java","-jar","app.jar"]
|
||||||
|
|||||||
@@ -151,6 +151,12 @@
|
|||||||
<configuration>
|
<configuration>
|
||||||
<mainClass>com.petshop.backend.DevStackApplication</mainClass>
|
<mainClass>com.petshop.backend.DevStackApplication</mainClass>
|
||||||
<classpathScope>runtime</classpathScope>
|
<classpathScope>runtime</classpathScope>
|
||||||
|
<systemProperties>
|
||||||
|
<systemProperty>
|
||||||
|
<key>UPLOAD_BASE_DIR</key>
|
||||||
|
<value>${project.basedir}/uploads</value>
|
||||||
|
</systemProperty>
|
||||||
|
</systemProperties>
|
||||||
</configuration>
|
</configuration>
|
||||||
</execution>
|
</execution>
|
||||||
<execution>
|
<execution>
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
package com.petshop.backend.controller;
|
package com.petshop.backend.controller;
|
||||||
|
|
||||||
import com.petshop.backend.dto.auth.AvatarUploadResponse;
|
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.LoginRequest;
|
||||||
import com.petshop.backend.dto.auth.LoginResponse;
|
import com.petshop.backend.dto.auth.LoginResponse;
|
||||||
import com.petshop.backend.dto.auth.ProfileUpdateRequest;
|
import com.petshop.backend.dto.auth.ProfileUpdateRequest;
|
||||||
import com.petshop.backend.dto.auth.RegisterRequest;
|
import com.petshop.backend.dto.auth.RegisterRequest;
|
||||||
import com.petshop.backend.dto.auth.RegisterResponse;
|
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.dto.auth.UserInfoResponse;
|
||||||
import com.petshop.backend.entity.StoreLocation;
|
import com.petshop.backend.entity.StoreLocation;
|
||||||
import com.petshop.backend.entity.User;
|
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.security.JwtUtil;
|
||||||
import com.petshop.backend.service.ActivityLogService;
|
import com.petshop.backend.service.ActivityLogService;
|
||||||
import com.petshop.backend.service.AvatarStorageService;
|
import com.petshop.backend.service.AvatarStorageService;
|
||||||
|
import com.petshop.backend.service.PasswordResetService;
|
||||||
import com.petshop.backend.util.AuthenticationHelper;
|
import com.petshop.backend.util.AuthenticationHelper;
|
||||||
import com.petshop.backend.util.PhoneUtils;
|
import com.petshop.backend.util.PhoneUtils;
|
||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
@@ -49,14 +54,16 @@ public class AuthController {
|
|||||||
private final PasswordEncoder passwordEncoder;
|
private final PasswordEncoder passwordEncoder;
|
||||||
private final AvatarStorageService avatarStorageService;
|
private final AvatarStorageService avatarStorageService;
|
||||||
private final ActivityLogService activityLogService;
|
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.authenticationManager = authenticationManager;
|
||||||
this.userRepository = userRepository;
|
this.userRepository = userRepository;
|
||||||
this.jwtUtil = jwtUtil;
|
this.jwtUtil = jwtUtil;
|
||||||
this.passwordEncoder = passwordEncoder;
|
this.passwordEncoder = passwordEncoder;
|
||||||
this.avatarStorageService = avatarStorageService;
|
this.avatarStorageService = avatarStorageService;
|
||||||
this.activityLogService = activityLogService;
|
this.activityLogService = activityLogService;
|
||||||
|
this.passwordResetService = passwordResetService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/register")
|
@PostMapping("/register")
|
||||||
@@ -153,6 +160,16 @@ public class AuthController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("/forgot-password")
|
||||||
|
public ResponseEntity<ForgotPasswordResponse> forgotPassword(@Valid @RequestBody ForgotPasswordRequest request) {
|
||||||
|
return ResponseEntity.ok(passwordResetService.createResetToken(request.getUsernameOrEmail()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/reset-password")
|
||||||
|
public ResponseEntity<ResetPasswordResponse> resetPassword(@Valid @RequestBody ResetPasswordRequest request) {
|
||||||
|
return ResponseEntity.ok(passwordResetService.resetPassword(request.getToken(), request.getNewPassword()));
|
||||||
|
}
|
||||||
|
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
@GetMapping("/me")
|
@GetMapping("/me")
|
||||||
public ResponseEntity<UserInfoResponse> getCurrentUser() {
|
public ResponseEntity<UserInfoResponse> getCurrentUser() {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,8 +11,11 @@ public class CartResponse {
|
|||||||
private List<CartItemResponse> items;
|
private List<CartItemResponse> items;
|
||||||
private BigDecimal subtotalAmount;
|
private BigDecimal subtotalAmount;
|
||||||
private BigDecimal discountAmount;
|
private BigDecimal discountAmount;
|
||||||
|
private BigDecimal pointsDiscountAmount;
|
||||||
private BigDecimal totalAmount;
|
private BigDecimal totalAmount;
|
||||||
private String couponCode;
|
private String couponCode;
|
||||||
|
private Boolean pointsApplied;
|
||||||
|
private Integer availableLoyaltyPoints;
|
||||||
|
|
||||||
public CartResponse() {
|
public CartResponse() {
|
||||||
}
|
}
|
||||||
@@ -40,4 +43,14 @@ public class CartResponse {
|
|||||||
|
|
||||||
public String getCouponCode() { return couponCode; }
|
public String getCouponCode() { return couponCode; }
|
||||||
public void setCouponCode(String couponCode) { this.couponCode = 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; }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import jakarta.validation.constraints.NotEmpty;
|
|||||||
import jakarta.validation.constraints.NotNull;
|
import jakarta.validation.constraints.NotNull;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.math.BigDecimal;
|
|
||||||
|
|
||||||
public class SaleRequest {
|
public class SaleRequest {
|
||||||
@NotNull(message = "Store ID is required")
|
@NotNull(message = "Store ID is required")
|
||||||
@@ -29,9 +28,6 @@ public class SaleRequest {
|
|||||||
|
|
||||||
private Long cartId;
|
private Long cartId;
|
||||||
|
|
||||||
private Integer pointsUsed;
|
|
||||||
|
|
||||||
private BigDecimal pointsDiscountAmount;
|
|
||||||
|
|
||||||
public Long getStoreId() {
|
public Long getStoreId() {
|
||||||
return storeId;
|
return storeId;
|
||||||
@@ -105,21 +101,6 @@ public class SaleRequest {
|
|||||||
this.cartId = cartId;
|
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
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
|
|||||||
@@ -18,9 +18,8 @@ public class SaleResponse {
|
|||||||
private BigDecimal subtotalAmount;
|
private BigDecimal subtotalAmount;
|
||||||
private BigDecimal couponDiscountAmount;
|
private BigDecimal couponDiscountAmount;
|
||||||
private BigDecimal employeeDiscountAmount;
|
private BigDecimal employeeDiscountAmount;
|
||||||
|
private BigDecimal loyaltyDiscountAmount;
|
||||||
private Integer pointsEarned;
|
private Integer pointsEarned;
|
||||||
private Integer pointsUsed;
|
|
||||||
private BigDecimal pointsDiscountAmount;
|
|
||||||
private String channel;
|
private String channel;
|
||||||
private Long couponId;
|
private Long couponId;
|
||||||
private Long cartId;
|
private Long cartId;
|
||||||
@@ -129,6 +128,15 @@ public class SaleResponse {
|
|||||||
this.employeeDiscountAmount = employeeDiscountAmount;
|
this.employeeDiscountAmount = employeeDiscountAmount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public BigDecimal getLoyaltyDiscountAmount() {
|
||||||
|
return loyaltyDiscountAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLoyaltyDiscountAmount(BigDecimal loyaltyDiscountAmount) {
|
||||||
|
this.loyaltyDiscountAmount = loyaltyDiscountAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public Integer getPointsEarned() {
|
public Integer getPointsEarned() {
|
||||||
return pointsEarned;
|
return pointsEarned;
|
||||||
}
|
}
|
||||||
@@ -137,22 +145,6 @@ public class SaleResponse {
|
|||||||
this.pointsEarned = pointsEarned;
|
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() {
|
public String getChannel() {
|
||||||
return channel;
|
return channel;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
package com.petshop.backend.entity;
|
package com.petshop.backend.entity;
|
||||||
|
|
||||||
import jakarta.persistence.*;
|
import jakarta.persistence.*;
|
||||||
|
import org.hibernate.annotations.Immutable;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
|
@Immutable
|
||||||
@Table(name = "activityLog")
|
@Table(name = "activityLog")
|
||||||
public class ActivityLog {
|
public class ActivityLog {
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,24 @@ public class Cart {
|
|||||||
@Column(nullable = false, precision = 10, scale = 2)
|
@Column(nullable = false, precision = 10, scale = 2)
|
||||||
private BigDecimal totalAmount = BigDecimal.ZERO;
|
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
|
@CreationTimestamp
|
||||||
@Column(name = "created_at", updatable = false)
|
@Column(name = "created_at", updatable = false)
|
||||||
private LocalDateTime createdAt;
|
private LocalDateTime createdAt;
|
||||||
@@ -115,6 +133,54 @@ public class Cart {
|
|||||||
this.totalAmount = totalAmount;
|
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() {
|
public LocalDateTime getCreatedAt() {
|
||||||
return createdAt;
|
return createdAt;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -66,14 +66,12 @@ public class Sale {
|
|||||||
@Column(nullable = false, precision = 10, scale = 2)
|
@Column(nullable = false, precision = 10, scale = 2)
|
||||||
private BigDecimal employeeDiscountAmount = BigDecimal.ZERO;
|
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)
|
@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)
|
@OneToMany(mappedBy = "sale", cascade = CascadeType.ALL)
|
||||||
private List<SaleItem> items = new ArrayList<>();
|
private List<SaleItem> items = new ArrayList<>();
|
||||||
@@ -209,6 +207,15 @@ public class Sale {
|
|||||||
this.employeeDiscountAmount = employeeDiscountAmount;
|
this.employeeDiscountAmount = employeeDiscountAmount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public BigDecimal getLoyaltyDiscountAmount() {
|
||||||
|
return loyaltyDiscountAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLoyaltyDiscountAmount(BigDecimal loyaltyDiscountAmount) {
|
||||||
|
this.loyaltyDiscountAmount = loyaltyDiscountAmount;
|
||||||
|
}
|
||||||
|
|
||||||
public Integer getPointsEarned() {
|
public Integer getPointsEarned() {
|
||||||
return pointsEarned;
|
return pointsEarned;
|
||||||
}
|
}
|
||||||
@@ -217,22 +224,6 @@ public class Sale {
|
|||||||
this.pointsEarned = pointsEarned;
|
this.pointsEarned = pointsEarned;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Integer getPointsUsed() {
|
|
||||||
return pointsUsed;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPointsUsed(Integer pointsUsed) {
|
|
||||||
this.pointsUsed = pointsUsed;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BigDecimal getPointsDiscountAmount() {
|
|
||||||
return pointsDiscountAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPointsDiscountAmount(BigDecimal pointsDiscountAmount) {
|
|
||||||
this.pointsDiscountAmount = pointsDiscountAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<SaleItem> getItems() {
|
public List<SaleItem> getItems() {
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.petshop.backend.repository;
|
||||||
|
|
||||||
|
import com.petshop.backend.entity.PasswordResetToken;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface PasswordResetTokenRepository extends JpaRepository<PasswordResetToken, Long> {
|
||||||
|
|
||||||
|
List<PasswordResetToken> findByUser_IdAndUsedAtIsNull(Long userId);
|
||||||
|
|
||||||
|
Optional<PasswordResetToken> findByTokenHashAndUsedAtIsNullAndExpiresAtAfter(String tokenHash, LocalDateTime now);
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ import org.springframework.data.repository.query.Param;
|
|||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
public interface SaleRepository extends JpaRepository<Sale, Long> {
|
public interface SaleRepository extends JpaRepository<Sale, Long> {
|
||||||
@@ -25,4 +26,6 @@ public interface SaleRepository extends JpaRepository<Sale, Long> {
|
|||||||
Page<Sale> searchSales(@Param("q") String query, @Param("paymentMethod") String paymentMethod, @Param("storeId") Long storeId, @Param("isRefund") Boolean isRefund, Pageable pageable);
|
Page<Sale> searchSales(@Param("q") String query, @Param("paymentMethod") String paymentMethod, @Param("storeId") Long storeId, @Param("isRefund") Boolean isRefund, Pageable pageable);
|
||||||
|
|
||||||
List<Sale> findByOriginalSaleSaleId(Long originalSaleId);
|
List<Sale> findByOriginalSaleSaleId(Long originalSaleId);
|
||||||
|
|
||||||
|
Optional<Sale> findByCartCartId(Long cartId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,7 +50,12 @@ public class SecurityConfig {
|
|||||||
http.cors(cors -> cors.configurationSource(corsConfigurationSource()))
|
http.cors(cors -> cors.configurationSource(corsConfigurationSource()))
|
||||||
.csrf(AbstractHttpConfigurer::disable)
|
.csrf(AbstractHttpConfigurer::disable)
|
||||||
.authorizeHttpRequests(auth -> auth
|
.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("/api/v1/health").permitAll()
|
||||||
.requestMatchers("/ws/chat/**", "/ws/chat-sockjs/**").permitAll()
|
.requestMatchers("/ws/chat/**", "/ws/chat-sockjs/**").permitAll()
|
||||||
.requestMatchers("/swagger-ui/**", "/v3/api-docs/**", "/swagger-ui.html").permitAll()
|
.requestMatchers("/swagger-ui/**", "/v3/api-docs/**", "/swagger-ui.html").permitAll()
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.petshop.backend.service;
|
package com.petshop.backend.service;
|
||||||
|
|
||||||
import com.petshop.backend.entity.User;
|
import com.petshop.backend.entity.User;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.core.io.PathResource;
|
import org.springframework.core.io.PathResource;
|
||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
@@ -8,6 +9,7 @@ import org.springframework.http.MediaTypeFactory;
|
|||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import jakarta.annotation.PostConstruct;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
@@ -22,7 +24,15 @@ public class AvatarStorageService {
|
|||||||
private static final String STORED_PREFIX = "/uploads/avatars/";
|
private static final String STORED_PREFIX = "/uploads/avatars/";
|
||||||
private static final String OWNER_ENDPOINT = "/api/v1/auth/me/avatar/file";
|
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 {
|
public String storeAvatar(MultipartFile file) throws IOException {
|
||||||
Files.createDirectories(avatarDirectory);
|
Files.createDirectories(avatarDirectory);
|
||||||
@@ -101,7 +111,7 @@ public class AvatarStorageService {
|
|||||||
}
|
}
|
||||||
String extension = originalFilename.substring(extensionIndex).toLowerCase(Locale.ROOT);
|
String extension = originalFilename.substring(extensionIndex).toLowerCase(Locale.ROOT);
|
||||||
return switch (extension) {
|
return switch (extension) {
|
||||||
case ".jpg", ".jpeg", ".png", ".gif" -> extension;
|
case ".jpg", ".jpeg", ".png", ".gif", ".webp" -> extension;
|
||||||
default -> ".jpg";
|
default -> ".jpg";
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package com.petshop.backend.service;
|
package com.petshop.backend.service;
|
||||||
|
|
||||||
import com.petshop.backend.dto.cart.*;
|
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.entity.*;
|
||||||
import com.petshop.backend.exception.BusinessException;
|
import com.petshop.backend.exception.BusinessException;
|
||||||
import com.petshop.backend.exception.ResourceNotFoundException;
|
import com.petshop.backend.exception.ResourceNotFoundException;
|
||||||
@@ -22,28 +24,36 @@ import java.util.List;
|
|||||||
@Service
|
@Service
|
||||||
public class CartService {
|
public class CartService {
|
||||||
|
|
||||||
|
private static final int LOYALTY_POINTS_PER_DOLLAR = 20;
|
||||||
|
|
||||||
private final CartRepository cartRepository;
|
private final CartRepository cartRepository;
|
||||||
private final CartItemRepository cartItemRepository;
|
private final CartItemRepository cartItemRepository;
|
||||||
private final UserRepository userRepository;
|
private final UserRepository userRepository;
|
||||||
private final StoreRepository storeRepository;
|
private final StoreRepository storeRepository;
|
||||||
private final ProductRepository productRepository;
|
private final ProductRepository productRepository;
|
||||||
private final CouponRepository couponRepository;
|
private final CouponRepository couponRepository;
|
||||||
|
private final SaleRepository saleRepository;
|
||||||
|
private final SaleService saleService;
|
||||||
|
|
||||||
@Value("${stripe.secret-key:}")
|
@Value("${stripe.secret-key:}")
|
||||||
private String stripeSecretKey;
|
private String stripeSecretKey;
|
||||||
|
|
||||||
public CartService(CartRepository cartRepository,
|
public CartService(CartRepository cartRepository,
|
||||||
CartItemRepository cartItemRepository,
|
CartItemRepository cartItemRepository,
|
||||||
UserRepository userRepository,
|
UserRepository userRepository,
|
||||||
StoreRepository storeRepository,
|
StoreRepository storeRepository,
|
||||||
ProductRepository productRepository,
|
ProductRepository productRepository,
|
||||||
CouponRepository couponRepository) {
|
CouponRepository couponRepository,
|
||||||
|
SaleRepository saleRepository,
|
||||||
|
SaleService saleService) {
|
||||||
this.cartRepository = cartRepository;
|
this.cartRepository = cartRepository;
|
||||||
this.cartItemRepository = cartItemRepository;
|
this.cartItemRepository = cartItemRepository;
|
||||||
this.userRepository = userRepository;
|
this.userRepository = userRepository;
|
||||||
this.storeRepository = storeRepository;
|
this.storeRepository = storeRepository;
|
||||||
this.productRepository = productRepository;
|
this.productRepository = productRepository;
|
||||||
this.couponRepository = couponRepository;
|
this.couponRepository = couponRepository;
|
||||||
|
this.saleRepository = saleRepository;
|
||||||
|
this.saleService = saleService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
@@ -77,10 +87,14 @@ public class CartService {
|
|||||||
newCart.setUser(user);
|
newCart.setUser(user);
|
||||||
newCart.setStore(store);
|
newCart.setStore(store);
|
||||||
newCart.setCartStatus("ACTIVE");
|
newCart.setCartStatus("ACTIVE");
|
||||||
|
newCart.setPointsApplied(false);
|
||||||
|
newCart.setPointsDiscountAmount(BigDecimal.ZERO);
|
||||||
|
|
||||||
return cartRepository.save(newCart);
|
return cartRepository.save(newCart);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
requireNotCheckoutPending(cart);
|
||||||
|
|
||||||
cartItemRepository.findByCartCartIdAndProductProdId(cart.getCartId(), product.getProdId())
|
cartItemRepository.findByCartCartIdAndProductProdId(cart.getCartId(), product.getProdId())
|
||||||
.ifPresentOrElse(
|
.ifPresentOrElse(
|
||||||
existing -> existing.setQuantity(existing.getQuantity() + request.getQuantity()),
|
existing -> existing.setQuantity(existing.getQuantity() + request.getQuantity()),
|
||||||
@@ -112,6 +126,8 @@ public class CartService {
|
|||||||
throw new BusinessException("Cart is not active");
|
throw new BusinessException("Cart is not active");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
requireNotCheckoutPending(item.getCart());
|
||||||
|
|
||||||
if (request.getQuantity() < 1) {
|
if (request.getQuantity() < 1) {
|
||||||
throw new BusinessException("Quantity must be at least 1");
|
throw new BusinessException("Quantity must be at least 1");
|
||||||
}
|
}
|
||||||
@@ -136,6 +152,8 @@ public class CartService {
|
|||||||
throw new BusinessException("Cart is not active");
|
throw new BusinessException("Cart is not active");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
requireNotCheckoutPending(item.getCart());
|
||||||
|
|
||||||
Cart cart = item.getCart();
|
Cart cart = item.getCart();
|
||||||
cartItemRepository.delete(item);
|
cartItemRepository.delete(item);
|
||||||
recalculate(cart);
|
recalculate(cart);
|
||||||
@@ -147,11 +165,14 @@ public class CartService {
|
|||||||
public void clearCart(Long userId, Long storeId) {
|
public void clearCart(Long userId, Long storeId) {
|
||||||
cartRepository.findActiveCartByUserAndStore(userId, storeId, "ACTIVE")
|
cartRepository.findActiveCartByUserAndStore(userId, storeId, "ACTIVE")
|
||||||
.ifPresent(cart -> {
|
.ifPresent(cart -> {
|
||||||
|
requireNotCheckoutPending(cart);
|
||||||
cartItemRepository.deleteByCartCartId(cart.getCartId());
|
cartItemRepository.deleteByCartCartId(cart.getCartId());
|
||||||
cart.setSubtotalAmount(BigDecimal.ZERO);
|
cart.setSubtotalAmount(BigDecimal.ZERO);
|
||||||
cart.setDiscountAmount(BigDecimal.ZERO);
|
cart.setDiscountAmount(BigDecimal.ZERO);
|
||||||
|
cart.setPointsDiscountAmount(BigDecimal.ZERO);
|
||||||
cart.setTotalAmount(BigDecimal.ZERO);
|
cart.setTotalAmount(BigDecimal.ZERO);
|
||||||
cart.setCoupon(null);
|
cart.setCoupon(null);
|
||||||
|
cart.setPointsApplied(false);
|
||||||
cartRepository.save(cart);
|
cartRepository.save(cart);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -162,6 +183,8 @@ public class CartService {
|
|||||||
.findActiveCartByUserAndStore(userId, storeId, "ACTIVE")
|
.findActiveCartByUserAndStore(userId, storeId, "ACTIVE")
|
||||||
.orElseThrow(() -> new BusinessException("No active cart found"));
|
.orElseThrow(() -> new BusinessException("No active cart found"));
|
||||||
|
|
||||||
|
requireNotCheckoutPending(cart);
|
||||||
|
|
||||||
Coupon coupon = couponRepository.findByCouponCodeIgnoreCase(couponCode)
|
Coupon coupon = couponRepository.findByCouponCodeIgnoreCase(couponCode)
|
||||||
.orElseThrow(() -> new BusinessException("Invalid coupon code"));
|
.orElseThrow(() -> new BusinessException("Invalid coupon code"));
|
||||||
|
|
||||||
@@ -189,12 +212,28 @@ public class CartService {
|
|||||||
return toResponse(cart);
|
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
|
@Transactional
|
||||||
public CheckoutResponse checkout(Long userId, Long storeId) {
|
public CheckoutResponse checkout(Long userId, Long storeId) {
|
||||||
Cart cart = cartRepository
|
Cart cart = cartRepository
|
||||||
.findActiveCartByUserAndStore(userId, storeId, "ACTIVE")
|
.findActiveCartByUserAndStore(userId, storeId, "ACTIVE")
|
||||||
.orElseThrow(() -> new BusinessException("No active cart found"));
|
.orElseThrow(() -> new BusinessException("No active cart found"));
|
||||||
|
|
||||||
|
if (Boolean.TRUE.equals(cart.getCheckoutPending())) {
|
||||||
|
throw new BusinessException("Checkout already in progress for this cart");
|
||||||
|
}
|
||||||
|
|
||||||
List<CartItem> items = cartItemRepository.findByCartCartId(cart.getCartId());
|
List<CartItem> items = cartItemRepository.findByCartCartId(cart.getCartId());
|
||||||
if (items.isEmpty()) {
|
if (items.isEmpty()) {
|
||||||
throw new BusinessException("Cart is empty");
|
throw new BusinessException("Cart is empty");
|
||||||
@@ -219,6 +258,12 @@ public class CartService {
|
|||||||
|
|
||||||
PaymentIntent intent = PaymentIntent.create(params);
|
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(
|
return new CheckoutResponse(
|
||||||
cart.getCartId(),
|
cart.getCartId(),
|
||||||
intent.getClientSecret(),
|
intent.getClientSecret(),
|
||||||
@@ -242,7 +287,29 @@ public class CartService {
|
|||||||
throw new BusinessException("Payment has not been completed");
|
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)
|
Cart cart = cartRepository.findById(cartId)
|
||||||
.orElseThrow(() -> new BusinessException("Cart not found"));
|
.orElseThrow(() -> new BusinessException("Cart not found"));
|
||||||
|
|
||||||
@@ -250,7 +317,74 @@ public class CartService {
|
|||||||
throw new BusinessException("Unauthorized");
|
throw new BusinessException("Unauthorized");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!Boolean.TRUE.equals(cart.getCheckoutPending())) {
|
||||||
|
throw new BusinessException("Cart checkout was not initiated");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!paymentIntentId.equals(cart.getCheckoutPaymentIntentId())) {
|
||||||
|
throw new BusinessException("Payment intent mismatch");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cart.getCheckoutAmount() == null) {
|
||||||
|
throw new BusinessException("Checkout amount snapshot is missing");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!cart.getCartStatus().equals("ACTIVE")) {
|
||||||
|
throw new BusinessException("Cart is not in active state");
|
||||||
|
}
|
||||||
|
|
||||||
|
List<CartItem> items = cartItemRepository.findByCartCartId(cart.getCartId());
|
||||||
|
if (items.isEmpty()) {
|
||||||
|
throw new BusinessException("Cart items were removed during checkout");
|
||||||
|
}
|
||||||
|
|
||||||
|
BigDecimal recalculatedTotal = recalculateTotalAmount(cart);
|
||||||
|
if (recalculatedTotal.compareTo(cart.getCheckoutAmount()) != 0) {
|
||||||
|
throw new BusinessException("Cart total changed during checkout");
|
||||||
|
}
|
||||||
|
|
||||||
|
long storedAmountInCents = cart.getCheckoutAmount()
|
||||||
|
.multiply(BigDecimal.valueOf(100))
|
||||||
|
.setScale(0, RoundingMode.HALF_UP)
|
||||||
|
.longValue();
|
||||||
|
|
||||||
|
if (intent.getAmount() != storedAmountInCents) {
|
||||||
|
throw new BusinessException("Stripe charged amount does not match expected amount");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (saleRepository.findByCartCartId(cart.getCartId()).isPresent()) {
|
||||||
|
cart.setCartStatus("CHECKED_OUT");
|
||||||
|
cart.setCheckoutPending(false);
|
||||||
|
cart.setCheckoutAmount(null);
|
||||||
|
cart.setCheckoutStartedAt(null);
|
||||||
|
cart.setCheckoutPaymentIntentId(null);
|
||||||
|
cartRepository.save(cart);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SaleRequest saleRequest = new SaleRequest();
|
||||||
|
saleRequest.setStoreId(cart.getStore().getStoreId());
|
||||||
|
saleRequest.setCustomerId(cart.getUser().getId());
|
||||||
|
saleRequest.setCartId(cart.getCartId());
|
||||||
|
saleRequest.setCouponId(cart.getCoupon() != null ? cart.getCoupon().getCouponId() : null);
|
||||||
|
saleRequest.setPaymentMethod("Card");
|
||||||
|
saleRequest.setChannel("WEBSITE");
|
||||||
|
saleRequest.setItems(cartItemRepository.findByCartCartId(cart.getCartId()).stream()
|
||||||
|
.map(item -> {
|
||||||
|
SaleItemRequest saleItemRequest = new SaleItemRequest();
|
||||||
|
saleItemRequest.setProdId(item.getProduct().getProdId());
|
||||||
|
saleItemRequest.setQuantity(item.getQuantity());
|
||||||
|
return saleItemRequest;
|
||||||
|
})
|
||||||
|
.toList());
|
||||||
|
|
||||||
|
saleService.createSale(saleRequest);
|
||||||
|
|
||||||
cart.setCartStatus("CHECKED_OUT");
|
cart.setCartStatus("CHECKED_OUT");
|
||||||
|
cart.setCheckoutPending(false);
|
||||||
|
cart.setCheckoutAmount(null);
|
||||||
|
cart.setCheckoutStartedAt(null);
|
||||||
|
cart.setCheckoutPaymentIntentId(null);
|
||||||
cartRepository.save(cart);
|
cartRepository.save(cart);
|
||||||
|
|
||||||
} catch (StripeException e) {
|
} 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) {
|
private void recalculate(Cart cart) {
|
||||||
List<CartItem> items = cartItemRepository.findByCartCartId(cart.getCartId());
|
List<CartItem> items = cartItemRepository.findByCartCartId(cart.getCartId());
|
||||||
|
|
||||||
@@ -274,8 +414,8 @@ public class CartService {
|
|||||||
if ("PERCENTAGE".equalsIgnoreCase(coupon.getDiscountType())) {
|
if ("PERCENTAGE".equalsIgnoreCase(coupon.getDiscountType())) {
|
||||||
discount = subtotal.multiply(coupon.getDiscountValue())
|
discount = subtotal.multiply(coupon.getDiscountValue())
|
||||||
.divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP);
|
.divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP);
|
||||||
}
|
}
|
||||||
|
|
||||||
else if ("FIXED".equalsIgnoreCase(coupon.getDiscountType())) {
|
else if ("FIXED".equalsIgnoreCase(coupon.getDiscountType())) {
|
||||||
discount = coupon.getDiscountValue().min(subtotal);
|
discount = coupon.getDiscountValue().min(subtotal);
|
||||||
}
|
}
|
||||||
@@ -283,10 +423,61 @@ public class CartService {
|
|||||||
|
|
||||||
discount = discount.max(BigDecimal.ZERO).min(subtotal);
|
discount = discount.max(BigDecimal.ZERO).min(subtotal);
|
||||||
cart.setDiscountAmount(discount);
|
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);
|
cartRepository.save(cart);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private BigDecimal recalculateTotalAmount(Cart cart) {
|
||||||
|
List<CartItem> items = cartItemRepository.findByCartCartId(cart.getCartId());
|
||||||
|
|
||||||
|
BigDecimal subtotal = items.stream()
|
||||||
|
.map(i -> i.getUnitPrice().multiply(BigDecimal.valueOf(i.getQuantity())))
|
||||||
|
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||||
|
|
||||||
|
BigDecimal discount = BigDecimal.ZERO;
|
||||||
|
Coupon coupon = cart.getCoupon();
|
||||||
|
|
||||||
|
if (coupon != null) {
|
||||||
|
if ("PERCENTAGE".equalsIgnoreCase(coupon.getDiscountType())) {
|
||||||
|
discount = subtotal.multiply(coupon.getDiscountValue())
|
||||||
|
.divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP);
|
||||||
|
}
|
||||||
|
|
||||||
|
else if ("FIXED".equalsIgnoreCase(coupon.getDiscountType())) {
|
||||||
|
discount = coupon.getDiscountValue().min(subtotal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
discount = discount.max(BigDecimal.ZERO).min(subtotal);
|
||||||
|
|
||||||
|
BigDecimal remainingAfterCoupon = subtotal.subtract(discount).max(BigDecimal.ZERO);
|
||||||
|
BigDecimal pointsDiscount = calculatePointsDiscount(cart.getUser(), remainingAfterCoupon, Boolean.TRUE.equals(cart.getPointsApplied()));
|
||||||
|
|
||||||
|
return remainingAfterCoupon.subtract(pointsDiscount).max(BigDecimal.ZERO);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private BigDecimal calculatePointsDiscount(User user, BigDecimal remainingAmount, boolean pointsApplied) {
|
||||||
|
if (!pointsApplied || user == null || remainingAmount.compareTo(BigDecimal.ZERO) <= 0) {
|
||||||
|
return BigDecimal.ZERO;
|
||||||
|
}
|
||||||
|
|
||||||
|
int availablePoints = user.getLoyaltyPoints() != null ? user.getLoyaltyPoints() : 0;
|
||||||
|
int wholeDollars = availablePoints / LOYALTY_POINTS_PER_DOLLAR;
|
||||||
|
if (wholeDollars <= 0) {
|
||||||
|
return BigDecimal.ZERO;
|
||||||
|
}
|
||||||
|
|
||||||
|
BigDecimal maxRedeemable = remainingAmount.setScale(0, RoundingMode.DOWN);
|
||||||
|
return BigDecimal.valueOf(wholeDollars)
|
||||||
|
.min(maxRedeemable)
|
||||||
|
.setScale(2, RoundingMode.HALF_UP);
|
||||||
|
}
|
||||||
|
|
||||||
private CartResponse toResponse(Cart cart) {
|
private CartResponse toResponse(Cart cart) {
|
||||||
List<CartItemResponse> itemResponses = cartItemRepository
|
List<CartItemResponse> itemResponses = cartItemRepository
|
||||||
.findByCartCartId(cart.getCartId())
|
.findByCartCartId(cart.getCartId())
|
||||||
@@ -309,8 +500,11 @@ public class CartService {
|
|||||||
response.setItems(itemResponses);
|
response.setItems(itemResponses);
|
||||||
response.setSubtotalAmount(cart.getSubtotalAmount());
|
response.setSubtotalAmount(cart.getSubtotalAmount());
|
||||||
response.setDiscountAmount(cart.getDiscountAmount());
|
response.setDiscountAmount(cart.getDiscountAmount());
|
||||||
|
response.setPointsDiscountAmount(cart.getPointsDiscountAmount());
|
||||||
response.setTotalAmount(cart.getTotalAmount());
|
response.setTotalAmount(cart.getTotalAmount());
|
||||||
response.setCouponCode(cart.getCoupon() != null ? cart.getCoupon().getCouponCode() : null);
|
response.setCouponCode(cart.getCoupon() != null ? cart.getCoupon().getCouponCode() : null);
|
||||||
|
response.setPointsApplied(cart.getPointsApplied());
|
||||||
|
response.setAvailableLoyaltyPoints(cart.getUser() != null ? cart.getUser().getLoyaltyPoints() : null);
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.petshop.backend.service;
|
package com.petshop.backend.service;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.core.io.PathResource;
|
import org.springframework.core.io.PathResource;
|
||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
@@ -21,20 +22,31 @@ public class CatalogImageStorageService {
|
|||||||
private static final String PET_PREFIX = "/uploads/pets/";
|
private static final String PET_PREFIX = "/uploads/pets/";
|
||||||
private static final String PRODUCT_PREFIX = "/uploads/products/";
|
private static final String PRODUCT_PREFIX = "/uploads/products/";
|
||||||
|
|
||||||
|
@Value("${app.upload.base-dir:uploads}")
|
||||||
|
private String uploadBaseDir;
|
||||||
|
|
||||||
public String storePetImage(MultipartFile file) throws IOException {
|
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 {
|
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) {
|
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) {
|
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) {
|
public MediaType resolveMediaType(Resource resource) {
|
||||||
@@ -42,11 +54,11 @@ public class CatalogImageStorageService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void deletePetImage(String storedPath) throws IOException {
|
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 {
|
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 {
|
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);
|
String extension = originalFilename.substring(extensionIndex).toLowerCase(Locale.ROOT);
|
||||||
return switch (extension) {
|
return switch (extension) {
|
||||||
case ".jpg", ".jpeg", ".png", ".gif" -> extension;
|
case ".jpg", ".jpeg", ".png", ".gif", ".webp" -> extension;
|
||||||
default -> ".jpg";
|
default -> ".jpg";
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,136 @@
|
|||||||
|
package com.petshop.backend.service;
|
||||||
|
|
||||||
|
import com.petshop.backend.dto.auth.ForgotPasswordResponse;
|
||||||
|
import com.petshop.backend.dto.auth.ResetPasswordResponse;
|
||||||
|
import com.petshop.backend.entity.PasswordResetToken;
|
||||||
|
import com.petshop.backend.entity.User;
|
||||||
|
import com.petshop.backend.exception.BusinessException;
|
||||||
|
import com.petshop.backend.repository.PasswordResetTokenRepository;
|
||||||
|
import com.petshop.backend.repository.UserRepository;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class PasswordResetService {
|
||||||
|
|
||||||
|
private static final int RESET_TOKEN_BYTES = 32;
|
||||||
|
private static final long RESET_TOKEN_MINUTES = 30;
|
||||||
|
|
||||||
|
private final PasswordResetTokenRepository passwordResetTokenRepository;
|
||||||
|
private final UserRepository userRepository;
|
||||||
|
private final PasswordEncoder passwordEncoder;
|
||||||
|
private final SecureRandom secureRandom = new SecureRandom();
|
||||||
|
|
||||||
|
public PasswordResetService(PasswordResetTokenRepository passwordResetTokenRepository,
|
||||||
|
UserRepository userRepository,
|
||||||
|
PasswordEncoder passwordEncoder) {
|
||||||
|
this.passwordResetTokenRepository = passwordResetTokenRepository;
|
||||||
|
this.userRepository = userRepository;
|
||||||
|
this.passwordEncoder = passwordEncoder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public ForgotPasswordResponse createResetToken(String usernameOrEmail) {
|
||||||
|
String normalized = trimToNull(usernameOrEmail);
|
||||||
|
if (normalized == null) {
|
||||||
|
throw new BusinessException("Username or email is required");
|
||||||
|
}
|
||||||
|
|
||||||
|
Optional<User> user = userRepository.findByEmail(normalized)
|
||||||
|
.or(() -> userRepository.findByUsername(normalized));
|
||||||
|
|
||||||
|
if (user.isEmpty() || !Boolean.TRUE.equals(user.get().getActive())) {
|
||||||
|
return new ForgotPasswordResponse(
|
||||||
|
"If an account matches that username or email, a reset token has been generated.",
|
||||||
|
null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
User managedUser = user.get();
|
||||||
|
LocalDateTime now = LocalDateTime.now();
|
||||||
|
invalidateOutstandingTokens(managedUser.getId(), now);
|
||||||
|
|
||||||
|
String rawToken = generateRawToken();
|
||||||
|
PasswordResetToken resetToken = new PasswordResetToken();
|
||||||
|
resetToken.setUser(managedUser);
|
||||||
|
resetToken.setTokenHash(hashToken(rawToken));
|
||||||
|
resetToken.setExpiresAt(now.plusMinutes(RESET_TOKEN_MINUTES));
|
||||||
|
passwordResetTokenRepository.save(resetToken);
|
||||||
|
|
||||||
|
return new ForgotPasswordResponse(
|
||||||
|
"If an account matches that username or email, a reset token has been generated.",
|
||||||
|
rawToken
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public ResetPasswordResponse resetPassword(String rawToken, String newPassword) {
|
||||||
|
String normalizedToken = trimToNull(rawToken);
|
||||||
|
if (normalizedToken == null) {
|
||||||
|
throw new BusinessException("Reset token is required");
|
||||||
|
}
|
||||||
|
|
||||||
|
PasswordResetToken token = passwordResetTokenRepository
|
||||||
|
.findByTokenHashAndUsedAtIsNullAndExpiresAtAfter(hashToken(normalizedToken), LocalDateTime.now())
|
||||||
|
.orElseThrow(() -> new BusinessException("Reset token is invalid or has expired"));
|
||||||
|
|
||||||
|
User user = token.getUser();
|
||||||
|
if (!Boolean.TRUE.equals(user.getActive())) {
|
||||||
|
throw new BusinessException("User account is inactive");
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalDateTime now = LocalDateTime.now();
|
||||||
|
user.setPassword(passwordEncoder.encode(newPassword));
|
||||||
|
user.setTokenVersion(user.getTokenVersion() + 1);
|
||||||
|
userRepository.save(user);
|
||||||
|
|
||||||
|
token.setUsedAt(now);
|
||||||
|
passwordResetTokenRepository.save(token);
|
||||||
|
invalidateOutstandingTokens(user.getId(), now);
|
||||||
|
|
||||||
|
return new ResetPasswordResponse("Password reset successfully");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void invalidateOutstandingTokens(Long userId, LocalDateTime usedAt) {
|
||||||
|
for (PasswordResetToken token : passwordResetTokenRepository.findByUser_IdAndUsedAtIsNull(userId)) {
|
||||||
|
token.setUsedAt(usedAt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String generateRawToken() {
|
||||||
|
byte[] bytes = new byte[RESET_TOKEN_BYTES];
|
||||||
|
secureRandom.nextBytes(bytes);
|
||||||
|
return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String hashToken(String rawToken) {
|
||||||
|
try {
|
||||||
|
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||||
|
byte[] hashed = digest.digest(rawToken.getBytes(StandardCharsets.UTF_8));
|
||||||
|
StringBuilder builder = new StringBuilder(hashed.length * 2);
|
||||||
|
for (byte value : hashed) {
|
||||||
|
builder.append(String.format("%02x", value));
|
||||||
|
}
|
||||||
|
return builder.toString();
|
||||||
|
} catch (NoSuchAlgorithmException ex) {
|
||||||
|
throw new IllegalStateException("SHA-256 is not available", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String trimToNull(String value) {
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String trimmed = value.trim();
|
||||||
|
return trimmed.isEmpty() ? null : trimmed;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,6 +22,7 @@ import java.util.List;
|
|||||||
public class SaleService {
|
public class SaleService {
|
||||||
|
|
||||||
private static final BigDecimal EMPLOYEE_DISCOUNT_PERCENT = new BigDecimal("0.10");
|
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 SaleRepository saleRepository;
|
||||||
private final ProductRepository productRepository;
|
private final ProductRepository productRepository;
|
||||||
@@ -56,7 +57,9 @@ public class SaleService {
|
|||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public SaleResponse createSale(SaleRequest request) {
|
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())
|
StoreLocation store = storeRepository.findById(request.getStoreId())
|
||||||
.orElseThrow(() -> new ResourceNotFoundException("Store not found with id: " + request.getStoreId()));
|
.orElseThrow(() -> new ResourceNotFoundException("Store not found with id: " + request.getStoreId()));
|
||||||
@@ -96,6 +99,11 @@ public class SaleService {
|
|||||||
sale.setCustomer(customer);
|
sale.setCustomer(customer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (websiteSale && customer == null) {
|
||||||
|
customer = actor;
|
||||||
|
sale.setCustomer(customer);
|
||||||
|
}
|
||||||
|
|
||||||
if (sale.getIsRefund() && request.getOriginalSaleId() != null) {
|
if (sale.getIsRefund() && request.getOriginalSaleId() != null) {
|
||||||
Sale originalSale = saleRepository.findById(request.getOriginalSaleId())
|
Sale originalSale = saleRepository.findById(request.getOriginalSaleId())
|
||||||
.orElseThrow(() -> new ResourceNotFoundException("Original sale not found with id: " + request.getOriginalSaleId()));
|
.orElseThrow(() -> new ResourceNotFoundException("Original sale not found with id: " + request.getOriginalSaleId()));
|
||||||
@@ -152,34 +160,17 @@ public class SaleService {
|
|||||||
saleItems.add(saleItem);
|
saleItems.add(saleItem);
|
||||||
subtotalAmount = subtotalAmount.add(itemTotal);
|
subtotalAmount = subtotalAmount.add(itemTotal);
|
||||||
}
|
}
|
||||||
|
subtotalAmount = subtotalAmount.negate();
|
||||||
Sale originalSale = sale.getOriginalSale();
|
sale.setSubtotalAmount(subtotalAmount);
|
||||||
BigDecimal originalSubtotal = originalSale.getSubtotalAmount() != null
|
sale.setTotalAmount(subtotalAmount);
|
||||||
? originalSale.getSubtotalAmount()
|
sale.setCouponDiscountAmount(BigDecimal.ZERO);
|
||||||
: originalSale.getItems().stream()
|
sale.setEmployeeDiscountAmount(BigDecimal.ZERO);
|
||||||
.map(i -> i.getUnitPrice().multiply(BigDecimal.valueOf(Math.abs(i.getQuantity()))))
|
sale.setLoyaltyDiscountAmount(BigDecimal.ZERO);
|
||||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
sale.setPointsEarned(0);
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
|
if (request.getItems() == null || request.getItems().isEmpty()) {
|
||||||
|
throw new BusinessException("At least one item is required");
|
||||||
|
}
|
||||||
for (var itemRequest : request.getItems()) {
|
for (var itemRequest : request.getItems()) {
|
||||||
Product product = productRepository.findById(itemRequest.getProdId())
|
Product product = productRepository.findById(itemRequest.getProdId())
|
||||||
.orElseThrow(() -> new ResourceNotFoundException("Product not found with id: " + itemRequest.getProdId()));
|
.orElseThrow(() -> new ResourceNotFoundException("Product not found with id: " + itemRequest.getProdId()));
|
||||||
@@ -212,28 +203,22 @@ public class SaleService {
|
|||||||
BigDecimal couponDiscount = calculateCouponDiscount(sale.getCoupon(), subtotalAmount);
|
BigDecimal couponDiscount = calculateCouponDiscount(sale.getCoupon(), subtotalAmount);
|
||||||
sale.setCouponDiscountAmount(couponDiscount);
|
sale.setCouponDiscountAmount(couponDiscount);
|
||||||
|
|
||||||
BigDecimal pointsDiscount = BigDecimal.ZERO;
|
BigDecimal employeeDiscount = calculateEmployeeDiscount(customer, subtotalAmount.subtract(couponDiscount));
|
||||||
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));
|
|
||||||
sale.setEmployeeDiscountAmount(employeeDiscount);
|
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));
|
sale.setTotalAmount(finalTotal.max(BigDecimal.ZERO));
|
||||||
|
|
||||||
|
int pointsUsed = toPointsUsed(loyaltyDiscount);
|
||||||
sale.setPointsEarned(sale.getTotalAmount().setScale(0, RoundingMode.FLOOR).intValue());
|
sale.setPointsEarned(sale.getTotalAmount().setScale(0, RoundingMode.FLOOR).intValue());
|
||||||
if (customer != null) {
|
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);
|
userRepository.save(customer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -277,10 +262,6 @@ public class SaleService {
|
|||||||
return discount.min(subtotal).setScale(2, RoundingMode.HALF_UP);
|
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) {
|
private BigDecimal calculateEmployeeDiscount(User customer, BigDecimal remainingAmount) {
|
||||||
if (customer == null || remainingAmount.compareTo(BigDecimal.ZERO) <= 0) {
|
if (customer == null || remainingAmount.compareTo(BigDecimal.ZERO) <= 0) {
|
||||||
return BigDecimal.ZERO;
|
return BigDecimal.ZERO;
|
||||||
@@ -293,6 +274,36 @@ public class SaleService {
|
|||||||
return BigDecimal.ZERO;
|
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) {
|
private SaleResponse mapToResponse(Sale sale) {
|
||||||
SaleResponse response = new SaleResponse();
|
SaleResponse response = new SaleResponse();
|
||||||
response.setSaleId(sale.getSaleId());
|
response.setSaleId(sale.getSaleId());
|
||||||
@@ -314,9 +325,8 @@ public class SaleService {
|
|||||||
response.setSubtotalAmount(sale.getSubtotalAmount());
|
response.setSubtotalAmount(sale.getSubtotalAmount());
|
||||||
response.setCouponDiscountAmount(sale.getCouponDiscountAmount());
|
response.setCouponDiscountAmount(sale.getCouponDiscountAmount());
|
||||||
response.setEmployeeDiscountAmount(sale.getEmployeeDiscountAmount());
|
response.setEmployeeDiscountAmount(sale.getEmployeeDiscountAmount());
|
||||||
|
response.setLoyaltyDiscountAmount(sale.getLoyaltyDiscountAmount());
|
||||||
response.setPointsEarned(sale.getPointsEarned());
|
response.setPointsEarned(sale.getPointsEarned());
|
||||||
response.setPointsUsed(sale.getPointsUsed());
|
|
||||||
response.setPointsDiscountAmount(sale.getPointsDiscountAmount());
|
|
||||||
response.setChannel(sale.getChannel());
|
response.setChannel(sale.getChannel());
|
||||||
if (sale.getCoupon() != null) {
|
if (sale.getCoupon() != null) {
|
||||||
response.setCouponId(sale.getCoupon().getCouponId());
|
response.setCouponId(sale.getCoupon().getCouponId());
|
||||||
|
|||||||
@@ -47,6 +47,10 @@ springdoc:
|
|||||||
swagger-ui:
|
swagger-ui:
|
||||||
path: /swagger-ui
|
path: /swagger-ui
|
||||||
|
|
||||||
|
app:
|
||||||
|
upload:
|
||||||
|
base-dir: ${UPLOAD_BASE_DIR:uploads}
|
||||||
|
|
||||||
jwt:
|
jwt:
|
||||||
secret: ${JWT_SECRET}
|
secret: ${JWT_SECRET}
|
||||||
expiration: ${JWT_EXPIRATION:86400000}
|
expiration: ${JWT_EXPIRATION:86400000}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS storeLocation (
|
CREATE TABLE IF NOT EXISTS storeLocation (
|
||||||
storeId BIGINT AUTO_INCREMENT PRIMARY KEY,
|
storeId BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||||
storeName VARCHAR(100) NOT NULL,
|
storeName VARCHAR(100) NOT NULL,
|
||||||
@@ -192,6 +191,12 @@ CREATE TABLE IF NOT EXISTS cart (
|
|||||||
subtotalAmount DECIMAL(10, 2) NOT NULL DEFAULT 0.00,
|
subtotalAmount DECIMAL(10, 2) NOT NULL DEFAULT 0.00,
|
||||||
discountAmount 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,
|
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,
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
CONSTRAINT fk_cart_user FOREIGN KEY (userId) REFERENCES users(id),
|
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,
|
subtotalAmount DECIMAL(10, 2) NULL,
|
||||||
couponDiscountAmount DECIMAL(10, 2) NOT NULL DEFAULT 0.00,
|
couponDiscountAmount DECIMAL(10, 2) NOT NULL DEFAULT 0.00,
|
||||||
employeeDiscountAmount 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,
|
pointsEarned INT NOT NULL DEFAULT 0,
|
||||||
|
pointsDiscountAmount DECIMAL(10, 2) NOT NULL DEFAULT 0.00,
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE 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_employee FOREIGN KEY (employeeId) REFERENCES users(id),
|
||||||
CONSTRAINT fk_sale_store FOREIGN KEY (storeId) REFERENCES storeLocation(storeId),
|
CONSTRAINT fk_sale_store FOREIGN KEY (storeId) REFERENCES storeLocation(storeId),
|
||||||
CONSTRAINT fk_sale_customer FOREIGN KEY (customerId) REFERENCES users(id) ON DELETE SET NULL,
|
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)
|
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 (
|
CREATE TABLE IF NOT EXISTS conversation (
|
||||||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||||
customerId BIGINT NOT NULL,
|
customerId BIGINT NOT NULL,
|
||||||
@@ -307,6 +330,10 @@ CREATE TABLE IF NOT EXISTS activityLog (
|
|||||||
logId BIGINT AUTO_INCREMENT PRIMARY KEY,
|
logId BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||||
userId BIGINT NOT NULL,
|
userId BIGINT NOT NULL,
|
||||||
storeId BIGINT NULL,
|
storeId BIGINT NULL,
|
||||||
|
usernameSnapshot VARCHAR(50) NULL,
|
||||||
|
fullNameSnapshot VARCHAR(100) NULL,
|
||||||
|
roleSnapshot VARCHAR(20) NULL,
|
||||||
|
storeNameSnapshot VARCHAR(100) NULL,
|
||||||
activity TEXT NOT NULL,
|
activity TEXT NOT NULL,
|
||||||
logTimestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
logTimestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
CONSTRAINT fk_activity_log_user FOREIGN KEY (userId) REFERENCES users(id),
|
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_customer ON conversation(customerId);
|
||||||
CREATE INDEX idx_conversation_staff ON conversation(staffId);
|
CREATE INDEX idx_conversation_staff ON conversation(staffId);
|
||||||
CREATE INDEX idx_activity_log_store ON activityLog(storeId);
|
CREATE INDEX idx_activity_log_store ON activityLog(storeId);
|
||||||
|
CREATE INDEX idx_activity_log_timestamp_id ON activityLog(logTimestamp, logId);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ SET FOREIGN_KEY_CHECKS = 0;
|
|||||||
DELETE FROM activityLog;
|
DELETE FROM activityLog;
|
||||||
DELETE FROM message;
|
DELETE FROM message;
|
||||||
DELETE FROM conversation;
|
DELETE FROM conversation;
|
||||||
|
DELETE FROM passwordResetToken;
|
||||||
DELETE FROM refund_item;
|
DELETE FROM refund_item;
|
||||||
DELETE FROM refund;
|
DELETE FROM refund;
|
||||||
DELETE FROM saleItem;
|
DELETE FROM saleItem;
|
||||||
@@ -45,6 +46,7 @@ ALTER TABLE refund AUTO_INCREMENT = 1;
|
|||||||
ALTER TABLE refund_item AUTO_INCREMENT = 1;
|
ALTER TABLE refund_item AUTO_INCREMENT = 1;
|
||||||
ALTER TABLE conversation AUTO_INCREMENT = 1;
|
ALTER TABLE conversation AUTO_INCREMENT = 1;
|
||||||
ALTER TABLE message AUTO_INCREMENT = 1;
|
ALTER TABLE message AUTO_INCREMENT = 1;
|
||||||
|
ALTER TABLE passwordResetToken AUTO_INCREMENT = 1;
|
||||||
ALTER TABLE activityLog AUTO_INCREMENT = 1;
|
ALTER TABLE activityLog AUTO_INCREMENT = 1;
|
||||||
|
|
||||||
SET FOREIGN_KEY_CHECKS = 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),
|
(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),
|
(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);
|
(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/%';
|
||||||
|
|||||||
@@ -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'
|
|
||||||
);
|
|
||||||
@@ -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);
|
|
||||||
@@ -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;
|
|
||||||
@@ -311,6 +311,10 @@ CREATE TABLE IF NOT EXISTS activityLog (
|
|||||||
logId BIGINT AUTO_INCREMENT PRIMARY KEY,
|
logId BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||||||
userId BIGINT NOT NULL,
|
userId BIGINT NOT NULL,
|
||||||
storeId BIGINT NULL,
|
storeId BIGINT NULL,
|
||||||
|
usernameSnapshot VARCHAR(50) NULL,
|
||||||
|
fullNameSnapshot VARCHAR(100) NULL,
|
||||||
|
roleSnapshot VARCHAR(20) NULL,
|
||||||
|
storeNameSnapshot VARCHAR(100) NULL,
|
||||||
activity TEXT NOT NULL,
|
activity TEXT NOT NULL,
|
||||||
logTimestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
logTimestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
CONSTRAINT fk_activity_log_user FOREIGN KEY (userId) REFERENCES users(id),
|
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_customer ON conversation(customerId);
|
||||||
CREATE INDEX idx_conversation_staff ON conversation(staffId);
|
CREATE INDEX idx_conversation_staff ON conversation(staffId);
|
||||||
CREATE INDEX idx_activity_log_store ON activityLog(storeId);
|
CREATE INDEX idx_activity_log_store ON activityLog(storeId);
|
||||||
|
CREATE INDEX idx_activity_log_timestamp_id ON activityLog(logTimestamp, logId);
|
||||||
|
|||||||
@@ -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),
|
(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),
|
(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),
|
(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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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, '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),
|
(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);
|
(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
|
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),
|
(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);
|
(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
|
INSERT INTO activityLog (logId, userId, storeId, usernameSnapshot, fullNameSnapshot, roleSnapshot, storeNameSnapshot, activity, logTimestamp) VALUES
|
||||||
(1, 1, 1, 'Reviewed store inventory adjustments.', '2026-01-03 08:00:00'),
|
(1, 1, 1, 'admin', 'Admin User', 'ADMIN', 'Downtown Branch', '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'),
|
(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, 'Updated a pet availability record.', '2026-01-04 02: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, 'Completed a grooming appointment handoff.', '2026-01-04 11: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, 'Checked a pending adoption record.', '2026-01-04 20: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, 'Reviewed a refund request tied to an original sale.', '2026-01-05 05: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, 'Answered a customer support conversation.', '2026-01-05 14: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, 'Updated a product detail for the catalogue.', '2026-01-05 23: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, 'Reviewed store inventory adjustments.', '2026-01-06 08: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, 'Approved a purchase transaction at the register.', '2026-01-06 17: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, 'Updated a pet availability record.', '2026-01-07 02: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, 'Completed a grooming appointment handoff.', '2026-01-07 11: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, 'Checked a pending adoption record.', '2026-01-07 20: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, 'Reviewed a refund request tied to an original sale.', '2026-01-08 05: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, 'Answered a customer support conversation.', '2026-01-08 14: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, 'Updated a product detail for the catalogue.', '2026-01-08 23: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, 'Reviewed store inventory adjustments.', '2026-01-09 08:00:00'),
|
(17, 3, 1, 'staff', 'Staff User', 'STAFF', 'Downtown Branch', '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'),
|
(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, 'Updated a pet availability record.', '2026-01-10 02: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, 'Completed a grooming appointment handoff.', '2026-01-10 11: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, 'Checked a pending adoption record.', '2026-01-10 20: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, 'Reviewed a refund request tied to an original sale.', '2026-01-11 05: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, 'Answered a customer support conversation.', '2026-01-11 14: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, 'Updated a product detail for the catalogue.', '2026-01-11 23: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, 'Reviewed store inventory adjustments.', '2026-01-12 08: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, 'Approved a purchase transaction at the register.', '2026-01-12 17: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, 'Updated a pet availability record.', '2026-01-13 02: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, 'Completed a grooming appointment handoff.', '2026-01-13 11: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, 'Checked a pending adoption record.', '2026-01-13 20: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, 'Reviewed a refund request tied to an original sale.', '2026-01-14 05: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, 'Answered a customer support conversation.', '2026-01-14 14: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, 'Updated a product detail for the catalogue.', '2026-01-14 23: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, 'Reviewed store inventory adjustments.', '2026-01-15 08: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, 'Approved a purchase transaction at the register.', '2026-01-15 17: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, 'Updated a pet availability record.', '2026-01-16 02: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, 'Completed a grooming appointment handoff.', '2026-01-16 11: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, 'Checked a pending adoption record.', '2026-01-16 20: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, 'Reviewed a refund request tied to an original sale.', '2026-01-17 05: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, 'Answered a customer support conversation.', '2026-01-17 14: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, 'Updated a product detail for the catalogue.', '2026-01-17 23: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, 'Reviewed store inventory adjustments.', '2026-01-18 08: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, 'Approved a purchase transaction at the register.', '2026-01-18 17: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, 'Updated a pet availability record.', '2026-01-19 02: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, 'Completed a grooming appointment handoff.', '2026-01-19 11: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, 'Checked a pending adoption record.', '2026-01-19 20: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, 'Reviewed a refund request tied to an original sale.', '2026-01-20 05: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, 'Answered a customer support conversation.', '2026-01-20 14: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, 'Updated a product detail for the catalogue.', '2026-01-20 23: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, 'Reviewed store inventory adjustments.', '2026-01-21 08: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, 'Approved a purchase transaction at the register.', '2026-01-21 17: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, 'Updated a pet availability record.', '2026-01-22 02: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, 'Completed a grooming appointment handoff.', '2026-01-22 11: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, 'Checked a pending adoption record.', '2026-01-22 20: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, 'Reviewed a refund request tied to an original sale.', '2026-01-23 05: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, 'Answered a customer support conversation.', '2026-01-23 14: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, 'Updated a product detail for the catalogue.', '2026-01-23 23: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, 'Reviewed store inventory adjustments.', '2026-01-24 08:00:00'),
|
(57, 1, 1, 'admin', 'Admin User', 'ADMIN', 'Downtown Branch', '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'),
|
(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, 'Updated a pet availability record.', '2026-01-25 02: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, 'Completed a grooming appointment handoff.', '2026-01-25 11: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, 'Checked a pending adoption record.', '2026-01-25 20: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, 'Reviewed a refund request tied to an original sale.', '2026-01-26 05: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, 'Answered a customer support conversation.', '2026-01-26 14: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, 'Updated a product detail for the catalogue.', '2026-01-26 23: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, 'Reviewed store inventory adjustments.', '2026-01-27 08: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, 'Approved a purchase transaction at the register.', '2026-01-27 17: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, 'Updated a pet availability record.', '2026-01-28 02: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, 'Completed a grooming appointment handoff.', '2026-01-28 11: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, 'Checked a pending adoption record.', '2026-01-28 20: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, 'Reviewed a refund request tied to an original sale.', '2026-01-29 05: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, 'Answered a customer support conversation.', '2026-01-29 14: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, 'Updated a product detail for the catalogue.', '2026-01-29 23: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, 'Reviewed store inventory adjustments.', '2026-01-30 08:00:00'),
|
(73, 3, 1, 'staff', 'Staff User', 'STAFF', 'Downtown Branch', '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'),
|
(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, 'Updated a pet availability record.', '2026-01-31 02: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, 'Completed a grooming appointment handoff.', '2026-01-31 11: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, 'Checked a pending adoption record.', '2026-01-31 20: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, 'Reviewed a refund request tied to an original sale.', '2026-02-01 05: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, 'Answered a customer support conversation.', '2026-02-01 14: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, 'Updated a product detail for the catalogue.', '2026-02-01 23: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, 'Reviewed store inventory adjustments.', '2026-02-02 08: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, 'Approved a purchase transaction at the register.', '2026-02-02 17: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, 'Updated a pet availability record.', '2026-02-03 02: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, 'Completed a grooming appointment handoff.', '2026-02-03 11: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, 'Checked a pending adoption record.', '2026-02-03 20: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, 'Reviewed a refund request tied to an original sale.', '2026-02-04 05: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, 'Answered a customer support conversation.', '2026-02-04 14: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, 'Updated a product detail for the catalogue.', '2026-02-04 23: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, 'Reviewed store inventory adjustments.', '2026-02-05 08: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, 'Approved a purchase transaction at the register.', '2026-02-05 17: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, 'Updated a pet availability record.', '2026-02-06 02: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, 'Completed a grooming appointment handoff.', '2026-02-06 11: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, 'Checked a pending adoption record.', '2026-02-06 20: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, 'Reviewed a refund request tied to an original sale.', '2026-02-07 05: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, 'Answered a customer support conversation.', '2026-02-07 14: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, 'Updated a product detail for the catalogue.', '2026-02-07 23: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, 'Reviewed store inventory adjustments.', '2026-02-08 08: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, 'Approved a purchase transaction at the register.', '2026-02-08 17: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, 'Updated a pet availability record.', '2026-02-09 02: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, 'Completed a grooming appointment handoff.', '2026-02-09 11: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, 'Checked a pending adoption record.', '2026-02-09 20: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, 'Reviewed a refund request tied to an original sale.', '2026-02-10 05: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, 'Answered a customer support conversation.', '2026-02-10 14: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, 'Updated a product detail for the catalogue.', '2026-02-10 23: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, 'Reviewed store inventory adjustments.', '2026-02-11 08: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, 'Approved a purchase transaction at the register.', '2026-02-11 17: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, 'Updated a pet availability record.', '2026-02-12 02: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, 'Completed a grooming appointment handoff.', '2026-02-12 11: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, 'Checked a pending adoption record.', '2026-02-12 20: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, 'Reviewed a refund request tied to an original sale.', '2026-02-13 05: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, 'Answered a customer support conversation.', '2026-02-13 14: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, 'Updated a product detail for the catalogue.', '2026-02-13 23: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, 'Reviewed store inventory adjustments.', '2026-02-14 08:00:00'),
|
(113, 1, 1, 'admin', 'Admin User', 'ADMIN', 'Downtown Branch', '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'),
|
(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, 'Updated a pet availability record.', '2026-02-15 02: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, 'Completed a grooming appointment handoff.', '2026-02-15 11: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, 'Checked a pending adoption record.', '2026-02-15 20: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, 'Reviewed a refund request tied to an original sale.', '2026-02-16 05: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, 'Answered a customer support conversation.', '2026-02-16 14: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, 'Updated a product detail for the catalogue.', '2026-02-16 23: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');
|
||||||
BIN
backend/uploads/avatars/001.webp
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
backend/uploads/avatars/002.webp
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
backend/uploads/avatars/003.webp
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
backend/uploads/avatars/004.webp
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
backend/uploads/avatars/005.webp
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
backend/uploads/avatars/006.webp
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
backend/uploads/avatars/007.webp
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
backend/uploads/avatars/008.webp
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
backend/uploads/avatars/009.webp
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
backend/uploads/avatars/010.webp
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
backend/uploads/avatars/011.webp
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
backend/uploads/avatars/012.webp
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
backend/uploads/avatars/013.webp
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
backend/uploads/avatars/014.webp
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
backend/uploads/avatars/015.webp
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
backend/uploads/avatars/016.webp
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
backend/uploads/avatars/017.webp
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
backend/uploads/avatars/018.webp
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
backend/uploads/avatars/019.webp
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
backend/uploads/avatars/020.webp
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
backend/uploads/avatars/021.webp
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
backend/uploads/avatars/022.webp
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
backend/uploads/avatars/023.webp
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
backend/uploads/avatars/024.webp
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
backend/uploads/avatars/025.webp
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
backend/uploads/avatars/026.webp
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
backend/uploads/avatars/027.webp
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
backend/uploads/avatars/028.webp
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
backend/uploads/avatars/029.webp
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
backend/uploads/avatars/030.webp
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
backend/uploads/avatars/031.webp
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
backend/uploads/avatars/032.webp
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
backend/uploads/avatars/033.webp
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
backend/uploads/avatars/034.webp
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
backend/uploads/avatars/035.webp
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
backend/uploads/avatars/036.webp
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
backend/uploads/avatars/037.webp
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
backend/uploads/avatars/038.webp
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
backend/uploads/avatars/039.webp
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
backend/uploads/avatars/040.webp
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
backend/uploads/avatars/041.webp
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
backend/uploads/avatars/042.webp
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
backend/uploads/avatars/043.webp
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
backend/uploads/avatars/044.webp
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
backend/uploads/avatars/045.webp
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
backend/uploads/avatars/046.webp
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
backend/uploads/avatars/047.webp
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
backend/uploads/avatars/048.webp
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
backend/uploads/avatars/049.webp
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
backend/uploads/avatars/050.webp
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
backend/uploads/avatars/051.webp
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
backend/uploads/avatars/052.webp
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
backend/uploads/avatars/053.webp
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
backend/uploads/avatars/054.webp
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
backend/uploads/avatars/055.webp
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
backend/uploads/avatars/056.webp
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
backend/uploads/avatars/057.webp
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
backend/uploads/avatars/058.webp
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
backend/uploads/avatars/059.webp
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
backend/uploads/avatars/060.webp
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
backend/uploads/avatars/061.webp
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
backend/uploads/avatars/062.webp
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
backend/uploads/avatars/063.webp
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
backend/uploads/avatars/064.webp
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
backend/uploads/avatars/065.webp
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
backend/uploads/avatars/066.webp
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
backend/uploads/avatars/067.webp
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
backend/uploads/avatars/068.webp
Normal file
|
After Width: | Height: | Size: 12 KiB |