From 8612835c82ede93fecc1ace357ae27a34a5fbbe0 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Wed, 4 Mar 2026 16:59:10 -0700 Subject: [PATCH 01/84] Initialize Spring Boot project --- backend/.gitignore | 35 ++++++ backend/pom.xml | 116 ++++++++++++++++++ .../petshop/backend/BackendApplication.java | 11 ++ backend/src/main/resources/application.yml | 43 +++++++ 4 files changed, 205 insertions(+) create mode 100644 backend/.gitignore create mode 100644 backend/pom.xml create mode 100644 backend/src/main/java/com/petshop/backend/BackendApplication.java create mode 100644 backend/src/main/resources/application.yml diff --git a/backend/.gitignore b/backend/.gitignore new file mode 100644 index 00000000..16d47a38 --- /dev/null +++ b/backend/.gitignore @@ -0,0 +1,35 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac ### +.DS_Store diff --git a/backend/pom.xml b/backend/pom.xml new file mode 100644 index 00000000..9b11bf68 --- /dev/null +++ b/backend/pom.xml @@ -0,0 +1,116 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 3.2.2 + + + + com.petshop + backend + 1.0.0 + PetShop Backend + Spring Boot backend for PetShop desktop application + + + 17 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + org.springframework.boot + spring-boot-starter-security + + + + org.springframework.boot + spring-boot-starter-validation + + + + com.mysql + mysql-connector-j + runtime + + + + org.flywaydb + flyway-core + + + + org.flywaydb + flyway-mysql + + + + io.jsonwebtoken + jjwt-api + 0.12.3 + + + + io.jsonwebtoken + jjwt-impl + 0.12.3 + runtime + + + + io.jsonwebtoken + jjwt-jackson + 0.12.3 + runtime + + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + 2.3.0 + + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.springframework.security + spring-security-test + test + + + + com.h2database + h2 + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + diff --git a/backend/src/main/java/com/petshop/backend/BackendApplication.java b/backend/src/main/java/com/petshop/backend/BackendApplication.java new file mode 100644 index 00000000..05cede40 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/BackendApplication.java @@ -0,0 +1,11 @@ +package com.petshop.backend; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class BackendApplication { + public static void main(String[] args) { + SpringApplication.run(BackendApplication.class, args); + } +} diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml new file mode 100644 index 00000000..07c5bff0 --- /dev/null +++ b/backend/src/main/resources/application.yml @@ -0,0 +1,43 @@ +spring: + application: + name: petshop-backend + + datasource: + url: ${DB_URL:jdbc:mysql://localhost:3306/petshop?createDatabaseIfNotExist=true} + username: ${DB_USERNAME:root} + password: ${DB_PASSWORD:password} + driver-class-name: com.mysql.cj.jdbc.Driver + + jpa: + hibernate: + ddl-auto: validate + show-sql: ${JPA_SHOW_SQL:false} + properties: + hibernate: + format_sql: true + dialect: org.hibernate.dialect.MySQLDialect + + flyway: + enabled: true + baseline-on-migrate: true + locations: classpath:db/migration + +server: + port: ${SERVER_PORT:8080} + servlet: + context-path: / + +springdoc: + api-docs: + path: /v3/api-docs + swagger-ui: + path: /swagger-ui + +jwt: + secret: ${JWT_SECRET:your-256-bit-secret-key-change-this-in-production-min-32-chars} + expiration: ${JWT_EXPIRATION:86400000} + +logging: + level: + com.petshop: ${LOG_LEVEL:INFO} + org.springframework.security: ${LOG_LEVEL_SECURITY:WARN} From 77dcfa3122e88b5d6e47067ee79be378776a5665 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Wed, 4 Mar 2026 17:01:54 -0700 Subject: [PATCH 02/84] Implement JWT authentication --- .../backend/controller/AuthController.java | 89 +++++++++++++++++++ .../backend/dto/auth/LoginRequest.java | 13 +++ .../backend/dto/auth/LoginResponse.java | 13 +++ .../backend/dto/auth/UserInfoResponse.java | 14 +++ .../java/com/petshop/backend/entity/User.java | 53 +++++++++++ .../backend/repository/UserRepository.java | 13 +++ .../security/JwtAuthenticationFilter.java | 58 ++++++++++++ .../com/petshop/backend/security/JwtUtil.java | 74 +++++++++++++++ .../backend/security/SecurityConfig.java | 71 +++++++++++++++ .../security/UserDetailsServiceImpl.java | 35 ++++++++ 10 files changed, 433 insertions(+) create mode 100644 backend/src/main/java/com/petshop/backend/controller/AuthController.java create mode 100644 backend/src/main/java/com/petshop/backend/dto/auth/LoginRequest.java create mode 100644 backend/src/main/java/com/petshop/backend/dto/auth/LoginResponse.java create mode 100644 backend/src/main/java/com/petshop/backend/dto/auth/UserInfoResponse.java create mode 100644 backend/src/main/java/com/petshop/backend/entity/User.java create mode 100644 backend/src/main/java/com/petshop/backend/repository/UserRepository.java create mode 100644 backend/src/main/java/com/petshop/backend/security/JwtAuthenticationFilter.java create mode 100644 backend/src/main/java/com/petshop/backend/security/JwtUtil.java create mode 100644 backend/src/main/java/com/petshop/backend/security/SecurityConfig.java create mode 100644 backend/src/main/java/com/petshop/backend/security/UserDetailsServiceImpl.java diff --git a/backend/src/main/java/com/petshop/backend/controller/AuthController.java b/backend/src/main/java/com/petshop/backend/controller/AuthController.java new file mode 100644 index 00000000..4d0dea61 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/controller/AuthController.java @@ -0,0 +1,89 @@ +package com.petshop.backend.controller; + +import com.petshop.backend.dto.auth.LoginRequest; +import com.petshop.backend.dto.auth.LoginResponse; +import com.petshop.backend.dto.auth.UserInfoResponse; +import com.petshop.backend.entity.User; +import com.petshop.backend.repository.UserRepository; +import com.petshop.backend.security.JwtUtil; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.web.bind.annotation.*; + +import java.util.HashMap; +import java.util.Map; + +@RestController +@RequestMapping("/api/v1/auth") +@RequiredArgsConstructor +public class AuthController { + + private final AuthenticationManager authenticationManager; + private final UserRepository userRepository; + private final JwtUtil jwtUtil; + + @PostMapping("/login") + public ResponseEntity login(@Valid @RequestBody LoginRequest request) { + try { + authenticationManager.authenticate( + new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword()) + ); + + User user = userRepository.findByUsername(request.getUsername()) + .orElseThrow(() -> new UsernameNotFoundException("User not found")); + + UserDetails userDetails = new org.springframework.security.core.userdetails.User( + user.getUsername(), + user.getPassword(), + java.util.Collections.emptyList() + ); + + String token = jwtUtil.generateToken(userDetails); + + return ResponseEntity.ok(new LoginResponse( + token, + user.getUsername(), + user.getFullName(), + user.getRole().name() + )); + + } catch (BadCredentialsException e) { + Map error = new HashMap<>(); + error.put("message", "Invalid username or password"); + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(error); + } + } + + @GetMapping("/me") + public ResponseEntity getCurrentUser() { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String username = authentication.getName(); + + User user = userRepository.findByUsername(username) + .orElseThrow(() -> new UsernameNotFoundException("User not found")); + + return ResponseEntity.ok(new UserInfoResponse( + user.getId(), + user.getUsername(), + user.getFullName(), + user.getEmail(), + user.getRole().name() + )); + } + + @PostMapping("/logout") + public ResponseEntity logout() { + Map response = new HashMap<>(); + response.put("message", "Logged out successfully"); + return ResponseEntity.ok(response); + } +} diff --git a/backend/src/main/java/com/petshop/backend/dto/auth/LoginRequest.java b/backend/src/main/java/com/petshop/backend/dto/auth/LoginRequest.java new file mode 100644 index 00000000..c80971c3 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/dto/auth/LoginRequest.java @@ -0,0 +1,13 @@ +package com.petshop.backend.dto.auth; + +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +@Data +public class LoginRequest { + @NotBlank(message = "Username is required") + private String username; + + @NotBlank(message = "Password is required") + private String password; +} diff --git a/backend/src/main/java/com/petshop/backend/dto/auth/LoginResponse.java b/backend/src/main/java/com/petshop/backend/dto/auth/LoginResponse.java new file mode 100644 index 00000000..d65b69f0 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/dto/auth/LoginResponse.java @@ -0,0 +1,13 @@ +package com.petshop.backend.dto.auth; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class LoginResponse { + private String token; + private String username; + private String fullName; + private String role; +} diff --git a/backend/src/main/java/com/petshop/backend/dto/auth/UserInfoResponse.java b/backend/src/main/java/com/petshop/backend/dto/auth/UserInfoResponse.java new file mode 100644 index 00000000..b96a01b1 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/dto/auth/UserInfoResponse.java @@ -0,0 +1,14 @@ +package com.petshop.backend.dto.auth; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class UserInfoResponse { + private Long id; + private String username; + private String fullName; + private String email; + private String role; +} diff --git a/backend/src/main/java/com/petshop/backend/entity/User.java b/backend/src/main/java/com/petshop/backend/entity/User.java new file mode 100644 index 00000000..5ac817f6 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/entity/User.java @@ -0,0 +1,53 @@ +package com.petshop.backend.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; + +import java.time.LocalDateTime; + +@Entity +@Table(name = "users") +@Data +@NoArgsConstructor +@AllArgsConstructor +public class User { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, unique = true, length = 50) + private String username; + + @Column(nullable = false) + private String password; + + @Column(name = "full_name", nullable = false, length = 100) + private String fullName; + + @Column(length = 100) + private String email; + + @Enumerated(EnumType.STRING) + @Column(nullable = false) + private Role role; + + @Column(nullable = false) + private Boolean active = true; + + @CreationTimestamp + @Column(name = "created_at", updatable = false) + private LocalDateTime createdAt; + + @UpdateTimestamp + @Column(name = "updated_at") + private LocalDateTime updatedAt; + + public enum Role { + STAFF, ADMIN + } +} diff --git a/backend/src/main/java/com/petshop/backend/repository/UserRepository.java b/backend/src/main/java/com/petshop/backend/repository/UserRepository.java new file mode 100644 index 00000000..95b79227 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/repository/UserRepository.java @@ -0,0 +1,13 @@ +package com.petshop.backend.repository; + +import com.petshop.backend.entity.User; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface UserRepository extends JpaRepository { + Optional findByUsername(String username); + boolean existsByUsername(String username); +} diff --git a/backend/src/main/java/com/petshop/backend/security/JwtAuthenticationFilter.java b/backend/src/main/java/com/petshop/backend/security/JwtAuthenticationFilter.java new file mode 100644 index 00000000..f08d4b5b --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/security/JwtAuthenticationFilter.java @@ -0,0 +1,58 @@ +package com.petshop.backend.security; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.lang.NonNull; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; + +@Component +@RequiredArgsConstructor +public class JwtAuthenticationFilter extends OncePerRequestFilter { + + private final JwtUtil jwtUtil; + private final UserDetailsService userDetailsService; + + @Override + protected void doFilterInternal( + @NonNull HttpServletRequest request, + @NonNull HttpServletResponse response, + @NonNull FilterChain filterChain + ) throws ServletException, IOException { + final String authHeader = request.getHeader("Authorization"); + final String jwt; + final String username; + + if (authHeader == null || !authHeader.startsWith("Bearer ")) { + filterChain.doFilter(request, response); + return; + } + + jwt = authHeader.substring(7); + username = jwtUtil.extractUsername(jwt); + + if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { + UserDetails userDetails = userDetailsService.loadUserByUsername(username); + if (jwtUtil.validateToken(jwt, userDetails)) { + UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken( + userDetails, + null, + userDetails.getAuthorities() + ); + authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + SecurityContextHolder.getContext().setAuthentication(authToken); + } + } + filterChain.doFilter(request, response); + } +} diff --git a/backend/src/main/java/com/petshop/backend/security/JwtUtil.java b/backend/src/main/java/com/petshop/backend/security/JwtUtil.java new file mode 100644 index 00000000..9381369b --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/security/JwtUtil.java @@ -0,0 +1,74 @@ +package com.petshop.backend.security; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.security.Keys; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Component; + +import javax.crypto.SecretKey; +import java.nio.charset.StandardCharsets; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; + +@Component +public class JwtUtil { + + @Value("${jwt.secret}") + private String secret; + + @Value("${jwt.expiration}") + private Long expiration; + + private SecretKey getSigningKey() { + return Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8)); + } + + public String extractUsername(String token) { + return extractClaim(token, Claims::getSubject); + } + + public Date extractExpiration(String token) { + return extractClaim(token, Claims::getExpiration); + } + + public T extractClaim(String token, Function claimsResolver) { + final Claims claims = extractAllClaims(token); + return claimsResolver.apply(claims); + } + + private Claims extractAllClaims(String token) { + return Jwts.parser() + .verifyWith(getSigningKey()) + .build() + .parseSignedClaims(token) + .getPayload(); + } + + private Boolean isTokenExpired(String token) { + return extractExpiration(token).before(new Date()); + } + + public String generateToken(UserDetails userDetails) { + Map claims = new HashMap<>(); + return createToken(claims, userDetails.getUsername()); + } + + private String createToken(Map claims, String subject) { + return Jwts.builder() + .claims(claims) + .subject(subject) + .issuedAt(new Date(System.currentTimeMillis())) + .expiration(new Date(System.currentTimeMillis() + expiration)) + .signWith(getSigningKey()) + .compact(); + } + + public Boolean validateToken(String token, UserDetails userDetails) { + final String username = extractUsername(token); + return (username.equals(userDetails.getUsername()) && !isTokenExpired(token)); + } +} diff --git a/backend/src/main/java/com/petshop/backend/security/SecurityConfig.java b/backend/src/main/java/com/petshop/backend/security/SecurityConfig.java new file mode 100644 index 00000000..a846a8ef --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/security/SecurityConfig.java @@ -0,0 +1,71 @@ +package com.petshop.backend.security; + +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.authentication.dao.DaoAuthenticationProvider; +import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +@Configuration +@EnableWebSecurity +@EnableMethodSecurity +@RequiredArgsConstructor +public class SecurityConfig { + + private final JwtAuthenticationFilter jwtAuthFilter; + private final UserDetailsService userDetailsService; + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http + .csrf(AbstractHttpConfigurer::disable) + .authorizeHttpRequests(auth -> auth + .requestMatchers("/api/v1/auth/login").permitAll() + .requestMatchers("/swagger-ui/**", "/v3/api-docs/**", "/swagger-ui.html").permitAll() + .requestMatchers(HttpMethod.GET, "/api/v1/dropdowns/suppliers").hasRole("ADMIN") + .requestMatchers("/api/v1/inventory/**").hasRole("ADMIN") + .requestMatchers("/api/v1/suppliers/**").hasRole("ADMIN") + .requestMatchers("/api/v1/product-suppliers/**").hasRole("ADMIN") + .requestMatchers("/api/v1/purchase-orders/**").hasRole("ADMIN") + .requestMatchers("/api/v1/users/**").hasRole("ADMIN") + .requestMatchers("/api/v1/analytics/**").hasRole("ADMIN") + .anyRequest().authenticated() + ) + .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .authenticationProvider(authenticationProvider()) + .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class); + + return http.build(); + } + + @Bean + public AuthenticationProvider authenticationProvider() { + DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(); + authProvider.setUserDetailsService(userDetailsService); + authProvider.setPasswordEncoder(passwordEncoder()); + return authProvider; + } + + @Bean + public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception { + return config.getAuthenticationManager(); + } + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } +} diff --git a/backend/src/main/java/com/petshop/backend/security/UserDetailsServiceImpl.java b/backend/src/main/java/com/petshop/backend/security/UserDetailsServiceImpl.java new file mode 100644 index 00000000..ec040275 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/security/UserDetailsServiceImpl.java @@ -0,0 +1,35 @@ +package com.petshop.backend.security; + +import com.petshop.backend.entity.User; +import com.petshop.backend.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + +import java.util.Collections; + +@Service +@RequiredArgsConstructor +public class UserDetailsServiceImpl implements UserDetailsService { + + private final UserRepository userRepository; + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + User user = userRepository.findByUsername(username) + .orElseThrow(() -> new UsernameNotFoundException("User not found: " + username)); + + if (!user.getActive()) { + throw new UsernameNotFoundException("User is inactive: " + username); + } + + return new org.springframework.security.core.userdetails.User( + user.getUsername(), + user.getPassword(), + Collections.singletonList(new SimpleGrantedAuthority("ROLE_" + user.getRole().name())) + ); + } +} From 06191d29587b870de4d5473b3c23069a0621be58 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Wed, 4 Mar 2026 17:03:42 -0700 Subject: [PATCH 03/84] Add entities and repositories --- .../com/petshop/backend/entity/Adoption.java | 49 ++++++++++++++ .../petshop/backend/entity/Appointment.java | 67 +++++++++++++++++++ .../com/petshop/backend/entity/Category.java | 36 ++++++++++ .../com/petshop/backend/entity/Customer.java | 42 ++++++++++++ .../com/petshop/backend/entity/Inventory.java | 49 ++++++++++++++ .../java/com/petshop/backend/entity/Pet.java | 54 +++++++++++++++ .../com/petshop/backend/entity/Product.java | 47 +++++++++++++ .../backend/entity/ProductSupplier.java | 56 ++++++++++++++++ .../petshop/backend/entity/PurchaseOrder.java | 61 +++++++++++++++++ .../backend/entity/PurchaseOrderItem.java | 37 ++++++++++ .../com/petshop/backend/entity/Refund.java | 48 +++++++++++++ .../petshop/backend/entity/RefundItem.java | 34 ++++++++++ .../java/com/petshop/backend/entity/Sale.java | 61 +++++++++++++++++ .../com/petshop/backend/entity/SaleItem.java | 37 ++++++++++ .../com/petshop/backend/entity/Service.java | 46 +++++++++++++ .../com/petshop/backend/entity/Store.java | 31 +++++++++ .../com/petshop/backend/entity/Supplier.java | 48 +++++++++++++ .../repository/AdoptionRepository.java | 9 +++ .../repository/AppointmentRepository.java | 18 +++++ .../repository/CategoryRepository.java | 22 ++++++ .../repository/CustomerRepository.java | 19 ++++++ .../repository/InventoryRepository.java | 16 +++++ .../backend/repository/PetRepository.java | 19 ++++++ .../backend/repository/ProductRepository.java | 18 +++++ .../repository/ProductSupplierRepository.java | 9 +++ .../repository/PurchaseOrderRepository.java | 9 +++ .../backend/repository/RefundRepository.java | 9 +++ .../backend/repository/SaleRepository.java | 9 +++ .../backend/repository/ServiceRepository.java | 18 +++++ .../backend/repository/StoreRepository.java | 9 +++ .../repository/SupplierRepository.java | 18 +++++ 31 files changed, 1005 insertions(+) create mode 100644 backend/src/main/java/com/petshop/backend/entity/Adoption.java create mode 100644 backend/src/main/java/com/petshop/backend/entity/Appointment.java create mode 100644 backend/src/main/java/com/petshop/backend/entity/Category.java create mode 100644 backend/src/main/java/com/petshop/backend/entity/Customer.java create mode 100644 backend/src/main/java/com/petshop/backend/entity/Inventory.java create mode 100644 backend/src/main/java/com/petshop/backend/entity/Pet.java create mode 100644 backend/src/main/java/com/petshop/backend/entity/Product.java create mode 100644 backend/src/main/java/com/petshop/backend/entity/ProductSupplier.java create mode 100644 backend/src/main/java/com/petshop/backend/entity/PurchaseOrder.java create mode 100644 backend/src/main/java/com/petshop/backend/entity/PurchaseOrderItem.java create mode 100644 backend/src/main/java/com/petshop/backend/entity/Refund.java create mode 100644 backend/src/main/java/com/petshop/backend/entity/RefundItem.java create mode 100644 backend/src/main/java/com/petshop/backend/entity/Sale.java create mode 100644 backend/src/main/java/com/petshop/backend/entity/SaleItem.java create mode 100644 backend/src/main/java/com/petshop/backend/entity/Service.java create mode 100644 backend/src/main/java/com/petshop/backend/entity/Store.java create mode 100644 backend/src/main/java/com/petshop/backend/entity/Supplier.java create mode 100644 backend/src/main/java/com/petshop/backend/repository/AdoptionRepository.java create mode 100644 backend/src/main/java/com/petshop/backend/repository/AppointmentRepository.java create mode 100644 backend/src/main/java/com/petshop/backend/repository/CategoryRepository.java create mode 100644 backend/src/main/java/com/petshop/backend/repository/CustomerRepository.java create mode 100644 backend/src/main/java/com/petshop/backend/repository/InventoryRepository.java create mode 100644 backend/src/main/java/com/petshop/backend/repository/PetRepository.java create mode 100644 backend/src/main/java/com/petshop/backend/repository/ProductRepository.java create mode 100644 backend/src/main/java/com/petshop/backend/repository/ProductSupplierRepository.java create mode 100644 backend/src/main/java/com/petshop/backend/repository/PurchaseOrderRepository.java create mode 100644 backend/src/main/java/com/petshop/backend/repository/RefundRepository.java create mode 100644 backend/src/main/java/com/petshop/backend/repository/SaleRepository.java create mode 100644 backend/src/main/java/com/petshop/backend/repository/ServiceRepository.java create mode 100644 backend/src/main/java/com/petshop/backend/repository/StoreRepository.java create mode 100644 backend/src/main/java/com/petshop/backend/repository/SupplierRepository.java diff --git a/backend/src/main/java/com/petshop/backend/entity/Adoption.java b/backend/src/main/java/com/petshop/backend/entity/Adoption.java new file mode 100644 index 00000000..f7842f82 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/entity/Adoption.java @@ -0,0 +1,49 @@ +package com.petshop.backend.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; + +@Entity +@Table(name = "adoptions") +@Data +@NoArgsConstructor +@AllArgsConstructor +public class Adoption { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne + @JoinColumn(name = "pet_id", nullable = false) + private Pet pet; + + @ManyToOne + @JoinColumn(name = "customer_id", nullable = false) + private Customer customer; + + @Column(name = "adoption_date", nullable = false) + private LocalDate adoptionDate; + + @Column(name = "adoption_fee", nullable = false, precision = 10, scale = 2) + private BigDecimal adoptionFee; + + @Column(columnDefinition = "TEXT") + private String notes; + + @CreationTimestamp + @Column(name = "created_at", updatable = false) + private LocalDateTime createdAt; + + @UpdateTimestamp + @Column(name = "updated_at") + private LocalDateTime updatedAt; +} diff --git a/backend/src/main/java/com/petshop/backend/entity/Appointment.java b/backend/src/main/java/com/petshop/backend/entity/Appointment.java new file mode 100644 index 00000000..b1810b48 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/entity/Appointment.java @@ -0,0 +1,67 @@ +package com.petshop.backend.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.HashSet; +import java.util.Set; + +@Entity +@Table(name = "appointments") +@Data +@NoArgsConstructor +@AllArgsConstructor +public class Appointment { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne + @JoinColumn(name = "customer_id", nullable = false) + private Customer customer; + + @ManyToOne + @JoinColumn(name = "service_id", nullable = false) + private Service service; + + @Column(name = "appointment_date", nullable = false) + private LocalDate appointmentDate; + + @Column(name = "appointment_time", nullable = false) + private LocalTime appointmentTime; + + @Enumerated(EnumType.STRING) + @Column(nullable = false) + private AppointmentStatus status = AppointmentStatus.Scheduled; + + @Column(columnDefinition = "TEXT") + private String notes; + + @ManyToMany + @JoinTable( + name = "appointment_pets", + joinColumns = @JoinColumn(name = "appointment_id"), + inverseJoinColumns = @JoinColumn(name = "pet_id") + ) + private Set pets = new HashSet<>(); + + @CreationTimestamp + @Column(name = "created_at", updatable = false) + private LocalDateTime createdAt; + + @UpdateTimestamp + @Column(name = "updated_at") + private LocalDateTime updatedAt; + + public enum AppointmentStatus { + Scheduled, Completed, Cancelled + } +} diff --git a/backend/src/main/java/com/petshop/backend/entity/Category.java b/backend/src/main/java/com/petshop/backend/entity/Category.java new file mode 100644 index 00000000..e21289a4 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/entity/Category.java @@ -0,0 +1,36 @@ +package com.petshop.backend.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; + +import java.time.LocalDateTime; + +@Entity +@Table(name = "categories") +@Data +@NoArgsConstructor +@AllArgsConstructor +public class Category { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "category_name", nullable = false, unique = true, length = 100) + private String categoryName; + + @Column(name = "category_description", columnDefinition = "TEXT") + private String categoryDescription; + + @CreationTimestamp + @Column(name = "created_at", updatable = false) + private LocalDateTime createdAt; + + @UpdateTimestamp + @Column(name = "updated_at") + private LocalDateTime updatedAt; +} diff --git a/backend/src/main/java/com/petshop/backend/entity/Customer.java b/backend/src/main/java/com/petshop/backend/entity/Customer.java new file mode 100644 index 00000000..ab10b792 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/entity/Customer.java @@ -0,0 +1,42 @@ +package com.petshop.backend.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; + +import java.time.LocalDateTime; + +@Entity +@Table(name = "customers") +@Data +@NoArgsConstructor +@AllArgsConstructor +public class Customer { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "customer_name", nullable = false, length = 100) + private String customerName; + + @Column(name = "customer_email", length = 100) + private String customerEmail; + + @Column(name = "customer_phone", length = 20) + private String customerPhone; + + @Column(name = "customer_address", columnDefinition = "TEXT") + private String customerAddress; + + @CreationTimestamp + @Column(name = "created_at", updatable = false) + private LocalDateTime createdAt; + + @UpdateTimestamp + @Column(name = "updated_at") + private LocalDateTime updatedAt; +} diff --git a/backend/src/main/java/com/petshop/backend/entity/Inventory.java b/backend/src/main/java/com/petshop/backend/entity/Inventory.java new file mode 100644 index 00000000..66c56b9b --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/entity/Inventory.java @@ -0,0 +1,49 @@ +package com.petshop.backend.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; + +import java.time.LocalDateTime; + +@Entity +@Table(name = "inventory", uniqueConstraints = { + @UniqueConstraint(name = "unique_product_store", columnNames = {"product_id", "store_id"}) +}) +@Data +@NoArgsConstructor +@AllArgsConstructor +public class Inventory { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne + @JoinColumn(name = "product_id", nullable = false) + private Product product; + + @ManyToOne + @JoinColumn(name = "store_id", nullable = false) + private Store store; + + @Column(nullable = false) + private Integer quantity = 0; + + @Column(name = "reorder_level") + private Integer reorderLevel = 10; + + @Column(name = "last_restocked") + private LocalDateTime lastRestocked; + + @CreationTimestamp + @Column(name = "created_at", updatable = false) + private LocalDateTime createdAt; + + @UpdateTimestamp + @Column(name = "updated_at") + private LocalDateTime updatedAt; +} diff --git a/backend/src/main/java/com/petshop/backend/entity/Pet.java b/backend/src/main/java/com/petshop/backend/entity/Pet.java new file mode 100644 index 00000000..de7bf5ab --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/entity/Pet.java @@ -0,0 +1,54 @@ +package com.petshop.backend.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Entity +@Table(name = "pets") +@Data +@NoArgsConstructor +@AllArgsConstructor +public class Pet { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "pet_name", nullable = false, length = 100) + private String petName; + + @Column(name = "pet_species", nullable = false, length = 50) + private String petSpecies; + + @Column(name = "pet_breed", length = 50) + private String petBreed; + + @Column(name = "pet_age") + private Integer petAge; + + @Enumerated(EnumType.STRING) + @Column(name = "pet_status", nullable = false) + private PetStatus petStatus = PetStatus.Available; + + @Column(name = "pet_price", precision = 10, scale = 2) + private BigDecimal petPrice; + + @CreationTimestamp + @Column(name = "created_at", updatable = false) + private LocalDateTime createdAt; + + @UpdateTimestamp + @Column(name = "updated_at") + private LocalDateTime updatedAt; + + public enum PetStatus { + Available, Adopted, Under_Care + } +} diff --git a/backend/src/main/java/com/petshop/backend/entity/Product.java b/backend/src/main/java/com/petshop/backend/entity/Product.java new file mode 100644 index 00000000..8ccc72a1 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/entity/Product.java @@ -0,0 +1,47 @@ +package com.petshop.backend.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Entity +@Table(name = "products") +@Data +@NoArgsConstructor +@AllArgsConstructor +public class Product { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "product_name", nullable = false, length = 100) + private String productName; + + @ManyToOne + @JoinColumn(name = "category_id", nullable = false) + private Category category; + + @Column(name = "product_description", columnDefinition = "TEXT") + private String productDescription; + + @Column(name = "product_price", nullable = false, precision = 10, scale = 2) + private BigDecimal productPrice; + + @Column(nullable = false) + private Boolean active = true; + + @CreationTimestamp + @Column(name = "created_at", updatable = false) + private LocalDateTime createdAt; + + @UpdateTimestamp + @Column(name = "updated_at") + private LocalDateTime updatedAt; +} diff --git a/backend/src/main/java/com/petshop/backend/entity/ProductSupplier.java b/backend/src/main/java/com/petshop/backend/entity/ProductSupplier.java new file mode 100644 index 00000000..9a1f56c8 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/entity/ProductSupplier.java @@ -0,0 +1,56 @@ +package com.petshop.backend.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Entity +@Table(name = "product_suppliers") +@Data +@NoArgsConstructor +@AllArgsConstructor +@IdClass(ProductSupplier.ProductSupplierId.class) +public class ProductSupplier { + + @Id + @ManyToOne + @JoinColumn(name = "product_id", nullable = false) + private Product product; + + @Id + @ManyToOne + @JoinColumn(name = "supplier_id", nullable = false) + private Supplier supplier; + + @Column(name = "cost_price", nullable = false, precision = 10, scale = 2) + private BigDecimal costPrice; + + @Column(name = "lead_time_days") + private Integer leadTimeDays; + + @Column(name = "is_preferred") + private Boolean isPreferred = false; + + @CreationTimestamp + @Column(name = "created_at", updatable = false) + private LocalDateTime createdAt; + + @UpdateTimestamp + @Column(name = "updated_at") + private LocalDateTime updatedAt; + + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class ProductSupplierId implements Serializable { + private Long product; + private Long supplier; + } +} diff --git a/backend/src/main/java/com/petshop/backend/entity/PurchaseOrder.java b/backend/src/main/java/com/petshop/backend/entity/PurchaseOrder.java new file mode 100644 index 00000000..cbeede55 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/entity/PurchaseOrder.java @@ -0,0 +1,61 @@ +package com.petshop.backend.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +@Entity +@Table(name = "purchase_orders") +@Data +@NoArgsConstructor +@AllArgsConstructor +public class PurchaseOrder { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne + @JoinColumn(name = "supplier_id", nullable = false) + private Supplier supplier; + + @Column(name = "order_date", nullable = false) + private LocalDate orderDate; + + @Column(name = "expected_delivery") + private LocalDate expectedDelivery; + + @Enumerated(EnumType.STRING) + @Column(nullable = false) + private OrderStatus status = OrderStatus.Pending; + + @Column(name = "total_amount", nullable = false, precision = 10, scale = 2) + private BigDecimal totalAmount; + + @Column(columnDefinition = "TEXT") + private String notes; + + @OneToMany(mappedBy = "purchaseOrder", cascade = CascadeType.ALL) + private List items = new ArrayList<>(); + + @CreationTimestamp + @Column(name = "created_at", updatable = false) + private LocalDateTime createdAt; + + @UpdateTimestamp + @Column(name = "updated_at") + private LocalDateTime updatedAt; + + public enum OrderStatus { + Pending, Delivered, Cancelled + } +} diff --git a/backend/src/main/java/com/petshop/backend/entity/PurchaseOrderItem.java b/backend/src/main/java/com/petshop/backend/entity/PurchaseOrderItem.java new file mode 100644 index 00000000..4aee2b49 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/entity/PurchaseOrderItem.java @@ -0,0 +1,37 @@ +package com.petshop.backend.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.math.BigDecimal; + +@Entity +@Table(name = "purchase_order_items") +@Data +@NoArgsConstructor +@AllArgsConstructor +public class PurchaseOrderItem { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne + @JoinColumn(name = "purchase_order_id", nullable = false) + private PurchaseOrder purchaseOrder; + + @ManyToOne + @JoinColumn(name = "product_id", nullable = false) + private Product product; + + @Column(nullable = false) + private Integer quantity; + + @Column(name = "unit_cost", nullable = false, precision = 10, scale = 2) + private BigDecimal unitCost; + + @Column(nullable = false, precision = 10, scale = 2) + private BigDecimal subtotal; +} diff --git a/backend/src/main/java/com/petshop/backend/entity/Refund.java b/backend/src/main/java/com/petshop/backend/entity/Refund.java new file mode 100644 index 00000000..70a7b1f2 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/entity/Refund.java @@ -0,0 +1,48 @@ +package com.petshop.backend.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.CreationTimestamp; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +@Entity +@Table(name = "refunds") +@Data +@NoArgsConstructor +@AllArgsConstructor +public class Refund { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne + @JoinColumn(name = "sale_id", nullable = false) + private Sale sale; + + @Column(name = "refund_date", nullable = false) + private LocalDateTime refundDate = LocalDateTime.now(); + + @Column(name = "refund_amount", nullable = false, precision = 10, scale = 2) + private BigDecimal refundAmount; + + @Column(name = "refund_reason", columnDefinition = "TEXT") + private String refundReason; + + @ManyToOne + @JoinColumn(name = "processed_by", nullable = false) + private User processedBy; + + @OneToMany(mappedBy = "refund", cascade = CascadeType.ALL) + private List items = new ArrayList<>(); + + @CreationTimestamp + @Column(name = "created_at", updatable = false) + private LocalDateTime createdAt; +} diff --git a/backend/src/main/java/com/petshop/backend/entity/RefundItem.java b/backend/src/main/java/com/petshop/backend/entity/RefundItem.java new file mode 100644 index 00000000..2f34b537 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/entity/RefundItem.java @@ -0,0 +1,34 @@ +package com.petshop.backend.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.math.BigDecimal; + +@Entity +@Table(name = "refund_items") +@Data +@NoArgsConstructor +@AllArgsConstructor +public class RefundItem { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne + @JoinColumn(name = "refund_id", nullable = false) + private Refund refund; + + @ManyToOne + @JoinColumn(name = "sale_item_id", nullable = false) + private SaleItem saleItem; + + @Column(nullable = false) + private Integer quantity; + + @Column(name = "refund_amount", nullable = false, precision = 10, scale = 2) + private BigDecimal refundAmount; +} diff --git a/backend/src/main/java/com/petshop/backend/entity/Sale.java b/backend/src/main/java/com/petshop/backend/entity/Sale.java new file mode 100644 index 00000000..3f46562c --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/entity/Sale.java @@ -0,0 +1,61 @@ +package com.petshop.backend.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.CreationTimestamp; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +@Entity +@Table(name = "sales") +@Data +@NoArgsConstructor +@AllArgsConstructor +public class Sale { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "sale_date", nullable = false) + private LocalDateTime saleDate = LocalDateTime.now(); + + @ManyToOne + @JoinColumn(name = "employee_id", nullable = false) + private User employee; + + @ManyToOne + @JoinColumn(name = "customer_id") + private Customer customer; + + @ManyToOne + @JoinColumn(name = "store_id") + private Store store; + + @Column(nullable = false, precision = 10, scale = 2) + private BigDecimal subtotal; + + @Column(nullable = false, precision = 10, scale = 2) + private BigDecimal tax = BigDecimal.ZERO; + + @Column(nullable = false, precision = 10, scale = 2) + private BigDecimal total; + + @Column(name = "payment_method", length = 50) + private String paymentMethod; + + @Column(columnDefinition = "TEXT") + private String notes; + + @OneToMany(mappedBy = "sale", cascade = CascadeType.ALL) + private List items = new ArrayList<>(); + + @CreationTimestamp + @Column(name = "created_at", updatable = false) + private LocalDateTime createdAt; +} diff --git a/backend/src/main/java/com/petshop/backend/entity/SaleItem.java b/backend/src/main/java/com/petshop/backend/entity/SaleItem.java new file mode 100644 index 00000000..012f706b --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/entity/SaleItem.java @@ -0,0 +1,37 @@ +package com.petshop.backend.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.math.BigDecimal; + +@Entity +@Table(name = "sale_items") +@Data +@NoArgsConstructor +@AllArgsConstructor +public class SaleItem { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne + @JoinColumn(name = "sale_id", nullable = false) + private Sale sale; + + @ManyToOne + @JoinColumn(name = "product_id", nullable = false) + private Product product; + + @Column(nullable = false) + private Integer quantity; + + @Column(name = "unit_price", nullable = false, precision = 10, scale = 2) + private BigDecimal unitPrice; + + @Column(nullable = false, precision = 10, scale = 2) + private BigDecimal subtotal; +} diff --git a/backend/src/main/java/com/petshop/backend/entity/Service.java b/backend/src/main/java/com/petshop/backend/entity/Service.java new file mode 100644 index 00000000..2373239b --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/entity/Service.java @@ -0,0 +1,46 @@ +package com.petshop.backend.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Entity +@Table(name = "services") +@Data +@NoArgsConstructor +@AllArgsConstructor +public class Service { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "service_name", nullable = false, length = 100) + private String serviceName; + + @Column(name = "service_description", columnDefinition = "TEXT") + private String serviceDescription; + + @Column(name = "service_price", nullable = false, precision = 10, scale = 2) + private BigDecimal servicePrice; + + @Column(name = "service_duration_minutes") + private Integer serviceDurationMinutes; + + @Column(nullable = false) + private Boolean active = true; + + @CreationTimestamp + @Column(name = "created_at", updatable = false) + private LocalDateTime createdAt; + + @UpdateTimestamp + @Column(name = "updated_at") + private LocalDateTime updatedAt; +} diff --git a/backend/src/main/java/com/petshop/backend/entity/Store.java b/backend/src/main/java/com/petshop/backend/entity/Store.java new file mode 100644 index 00000000..fbbd0f21 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/entity/Store.java @@ -0,0 +1,31 @@ +package com.petshop.backend.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.CreationTimestamp; + +import java.time.LocalDateTime; + +@Entity +@Table(name = "stores") +@Data +@NoArgsConstructor +@AllArgsConstructor +public class Store { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "store_name", nullable = false, length = 100) + private String storeName; + + @Column(name = "store_location", length = 200) + private String storeLocation; + + @CreationTimestamp + @Column(name = "created_at", updatable = false) + private LocalDateTime createdAt; +} diff --git a/backend/src/main/java/com/petshop/backend/entity/Supplier.java b/backend/src/main/java/com/petshop/backend/entity/Supplier.java new file mode 100644 index 00000000..57f66bcf --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/entity/Supplier.java @@ -0,0 +1,48 @@ +package com.petshop.backend.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; + +import java.time.LocalDateTime; + +@Entity +@Table(name = "suppliers") +@Data +@NoArgsConstructor +@AllArgsConstructor +public class Supplier { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "supplier_name", nullable = false, length = 100) + private String supplierName; + + @Column(name = "supplier_contact", length = 100) + private String supplierContact; + + @Column(name = "supplier_email", length = 100) + private String supplierEmail; + + @Column(name = "supplier_phone", length = 20) + private String supplierPhone; + + @Column(name = "supplier_address", columnDefinition = "TEXT") + private String supplierAddress; + + @Column(nullable = false) + private Boolean active = true; + + @CreationTimestamp + @Column(name = "created_at", updatable = false) + private LocalDateTime createdAt; + + @UpdateTimestamp + @Column(name = "updated_at") + private LocalDateTime updatedAt; +} diff --git a/backend/src/main/java/com/petshop/backend/repository/AdoptionRepository.java b/backend/src/main/java/com/petshop/backend/repository/AdoptionRepository.java new file mode 100644 index 00000000..8bf64f74 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/repository/AdoptionRepository.java @@ -0,0 +1,9 @@ +package com.petshop.backend.repository; + +import com.petshop.backend.entity.Adoption; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface AdoptionRepository extends JpaRepository { +} diff --git a/backend/src/main/java/com/petshop/backend/repository/AppointmentRepository.java b/backend/src/main/java/com/petshop/backend/repository/AppointmentRepository.java new file mode 100644 index 00000000..5d773e8c --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/repository/AppointmentRepository.java @@ -0,0 +1,18 @@ +package com.petshop.backend.repository; + +import com.petshop.backend.entity.Appointment; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.List; + +@Repository +public interface AppointmentRepository extends JpaRepository { + + @Query("SELECT a FROM Appointment a WHERE a.appointmentDate = :date AND a.appointmentTime = :time") + List findByDateAndTime(@Param("date") LocalDate date, @Param("time") LocalTime time); +} diff --git a/backend/src/main/java/com/petshop/backend/repository/CategoryRepository.java b/backend/src/main/java/com/petshop/backend/repository/CategoryRepository.java new file mode 100644 index 00000000..ae20d6a0 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/repository/CategoryRepository.java @@ -0,0 +1,22 @@ +package com.petshop.backend.repository; + +import com.petshop.backend.entity.Category; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface CategoryRepository extends JpaRepository { + + Optional findByCategoryName(String categoryName); + + @Query("SELECT c FROM Category c WHERE " + + "LOWER(c.categoryName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + + "LOWER(c.categoryDescription) LIKE LOWER(CONCAT('%', :q, '%'))") + Page searchCategories(@Param("q") String query, Pageable pageable); +} diff --git a/backend/src/main/java/com/petshop/backend/repository/CustomerRepository.java b/backend/src/main/java/com/petshop/backend/repository/CustomerRepository.java new file mode 100644 index 00000000..a0bebc69 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/repository/CustomerRepository.java @@ -0,0 +1,19 @@ +package com.petshop.backend.repository; + +import com.petshop.backend.entity.Customer; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +@Repository +public interface CustomerRepository extends JpaRepository { + + @Query("SELECT c FROM Customer c WHERE " + + "LOWER(c.customerName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + + "LOWER(c.customerEmail) LIKE LOWER(CONCAT('%', :q, '%')) OR " + + "LOWER(c.customerPhone) LIKE LOWER(CONCAT('%', :q, '%'))") + Page searchCustomers(@Param("q") String query, Pageable pageable); +} diff --git a/backend/src/main/java/com/petshop/backend/repository/InventoryRepository.java b/backend/src/main/java/com/petshop/backend/repository/InventoryRepository.java new file mode 100644 index 00000000..2d0e0f9c --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/repository/InventoryRepository.java @@ -0,0 +1,16 @@ +package com.petshop.backend.repository; + +import com.petshop.backend.entity.Inventory; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface InventoryRepository extends JpaRepository { + + @Query("SELECT i FROM Inventory i WHERE i.product.id = :productId AND i.store.id = :storeId") + Optional findByProductIdAndStoreId(@Param("productId") Long productId, @Param("storeId") Long storeId); +} diff --git a/backend/src/main/java/com/petshop/backend/repository/PetRepository.java b/backend/src/main/java/com/petshop/backend/repository/PetRepository.java new file mode 100644 index 00000000..fa9aa5e7 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/repository/PetRepository.java @@ -0,0 +1,19 @@ +package com.petshop.backend.repository; + +import com.petshop.backend.entity.Pet; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +@Repository +public interface PetRepository extends JpaRepository { + + @Query("SELECT p FROM Pet p WHERE " + + "LOWER(p.petName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + + "LOWER(p.petSpecies) LIKE LOWER(CONCAT('%', :q, '%')) OR " + + "LOWER(p.petBreed) LIKE LOWER(CONCAT('%', :q, '%'))") + Page searchPets(@Param("q") String query, Pageable pageable); +} diff --git a/backend/src/main/java/com/petshop/backend/repository/ProductRepository.java b/backend/src/main/java/com/petshop/backend/repository/ProductRepository.java new file mode 100644 index 00000000..896602e9 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/repository/ProductRepository.java @@ -0,0 +1,18 @@ +package com.petshop.backend.repository; + +import com.petshop.backend.entity.Product; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +@Repository +public interface ProductRepository extends JpaRepository { + + @Query("SELECT p FROM Product p WHERE " + + "LOWER(p.productName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + + "LOWER(p.productDescription) LIKE LOWER(CONCAT('%', :q, '%'))") + Page searchProducts(@Param("q") String query, Pageable pageable); +} diff --git a/backend/src/main/java/com/petshop/backend/repository/ProductSupplierRepository.java b/backend/src/main/java/com/petshop/backend/repository/ProductSupplierRepository.java new file mode 100644 index 00000000..f8e96185 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/repository/ProductSupplierRepository.java @@ -0,0 +1,9 @@ +package com.petshop.backend.repository; + +import com.petshop.backend.entity.ProductSupplier; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface ProductSupplierRepository extends JpaRepository { +} diff --git a/backend/src/main/java/com/petshop/backend/repository/PurchaseOrderRepository.java b/backend/src/main/java/com/petshop/backend/repository/PurchaseOrderRepository.java new file mode 100644 index 00000000..ed3251ac --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/repository/PurchaseOrderRepository.java @@ -0,0 +1,9 @@ +package com.petshop.backend.repository; + +import com.petshop.backend.entity.PurchaseOrder; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface PurchaseOrderRepository extends JpaRepository { +} diff --git a/backend/src/main/java/com/petshop/backend/repository/RefundRepository.java b/backend/src/main/java/com/petshop/backend/repository/RefundRepository.java new file mode 100644 index 00000000..8378b672 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/repository/RefundRepository.java @@ -0,0 +1,9 @@ +package com.petshop.backend.repository; + +import com.petshop.backend.entity.Refund; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface RefundRepository extends JpaRepository { +} diff --git a/backend/src/main/java/com/petshop/backend/repository/SaleRepository.java b/backend/src/main/java/com/petshop/backend/repository/SaleRepository.java new file mode 100644 index 00000000..f58f0660 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/repository/SaleRepository.java @@ -0,0 +1,9 @@ +package com.petshop.backend.repository; + +import com.petshop.backend.entity.Sale; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface SaleRepository extends JpaRepository { +} diff --git a/backend/src/main/java/com/petshop/backend/repository/ServiceRepository.java b/backend/src/main/java/com/petshop/backend/repository/ServiceRepository.java new file mode 100644 index 00000000..a6c0a084 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/repository/ServiceRepository.java @@ -0,0 +1,18 @@ +package com.petshop.backend.repository; + +import com.petshop.backend.entity.Service; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +@Repository +public interface ServiceRepository extends JpaRepository { + + @Query("SELECT s FROM Service s WHERE " + + "LOWER(s.serviceName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + + "LOWER(s.serviceDescription) LIKE LOWER(CONCAT('%', :q, '%'))") + Page searchServices(@Param("q") String query, Pageable pageable); +} diff --git a/backend/src/main/java/com/petshop/backend/repository/StoreRepository.java b/backend/src/main/java/com/petshop/backend/repository/StoreRepository.java new file mode 100644 index 00000000..4f067633 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/repository/StoreRepository.java @@ -0,0 +1,9 @@ +package com.petshop.backend.repository; + +import com.petshop.backend.entity.Store; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface StoreRepository extends JpaRepository { +} diff --git a/backend/src/main/java/com/petshop/backend/repository/SupplierRepository.java b/backend/src/main/java/com/petshop/backend/repository/SupplierRepository.java new file mode 100644 index 00000000..443455b8 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/repository/SupplierRepository.java @@ -0,0 +1,18 @@ +package com.petshop.backend.repository; + +import com.petshop.backend.entity.Supplier; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +@Repository +public interface SupplierRepository extends JpaRepository { + + @Query("SELECT s FROM Supplier s WHERE " + + "LOWER(s.supplierName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + + "LOWER(s.supplierContact) LIKE LOWER(CONCAT('%', :q, '%'))") + Page searchSuppliers(@Param("q") String query, Pageable pageable); +} From cb432fb2b11d4861599545e32d7d6bfcc24bfed4 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Wed, 4 Mar 2026 17:05:29 -0700 Subject: [PATCH 04/84] Add UI-ready DTOs with joined fields --- .../backend/dto/adoption/AdoptionRequest.java | 26 +++++++++ .../dto/adoption/AdoptionResponse.java | 25 ++++++++ .../dto/analytics/DashboardResponse.java | 57 +++++++++++++++++++ .../dto/appointment/AppointmentRequest.java | 33 +++++++++++ .../dto/appointment/AppointmentResponse.java | 29 ++++++++++ .../backend/dto/category/CategoryRequest.java | 12 ++++ .../dto/category/CategoryResponse.java | 18 ++++++ .../backend/dto/common/BulkDeleteRequest.java | 12 ++++ .../backend/dto/common/DropdownOption.java | 11 ++++ .../backend/dto/customer/CustomerRequest.java | 17 ++++++ .../dto/customer/CustomerResponse.java | 20 +++++++ .../dto/inventory/InventoryRequest.java | 21 +++++++ .../dto/inventory/InventoryResponse.java | 24 ++++++++ .../petshop/backend/dto/pet/PetRequest.java | 28 +++++++++ .../petshop/backend/dto/pet/PetResponse.java | 23 ++++++++ .../backend/dto/product/ProductRequest.java | 25 ++++++++ .../backend/dto/product/ProductResponse.java | 23 ++++++++ .../productsupplier/ProductSupplierKey.java | 18 ++++++ .../ProductSupplierRequest.java | 26 +++++++++ .../ProductSupplierResponse.java | 23 ++++++++ .../purchaseorder/PurchaseOrderResponse.java | 39 +++++++++++++ .../backend/dto/refund/RefundRequest.java | 28 +++++++++ .../backend/dto/refund/RefundResponse.java | 36 ++++++++++++ .../petshop/backend/dto/sale/SaleRequest.java | 38 +++++++++++++ .../backend/dto/sale/SaleResponse.java | 42 ++++++++++++++ .../backend/dto/service/ServiceRequest.java | 25 ++++++++ .../backend/dto/service/ServiceResponse.java | 22 +++++++ .../backend/dto/supplier/SupplierRequest.java | 20 +++++++ .../dto/supplier/SupplierResponse.java | 22 +++++++ .../petshop/backend/dto/user/UserRequest.java | 29 ++++++++++ .../backend/dto/user/UserResponse.java | 21 +++++++ 31 files changed, 793 insertions(+) create mode 100644 backend/src/main/java/com/petshop/backend/dto/adoption/AdoptionRequest.java create mode 100644 backend/src/main/java/com/petshop/backend/dto/adoption/AdoptionResponse.java create mode 100644 backend/src/main/java/com/petshop/backend/dto/analytics/DashboardResponse.java create mode 100644 backend/src/main/java/com/petshop/backend/dto/appointment/AppointmentRequest.java create mode 100644 backend/src/main/java/com/petshop/backend/dto/appointment/AppointmentResponse.java create mode 100644 backend/src/main/java/com/petshop/backend/dto/category/CategoryRequest.java create mode 100644 backend/src/main/java/com/petshop/backend/dto/category/CategoryResponse.java create mode 100644 backend/src/main/java/com/petshop/backend/dto/common/BulkDeleteRequest.java create mode 100644 backend/src/main/java/com/petshop/backend/dto/common/DropdownOption.java create mode 100644 backend/src/main/java/com/petshop/backend/dto/customer/CustomerRequest.java create mode 100644 backend/src/main/java/com/petshop/backend/dto/customer/CustomerResponse.java create mode 100644 backend/src/main/java/com/petshop/backend/dto/inventory/InventoryRequest.java create mode 100644 backend/src/main/java/com/petshop/backend/dto/inventory/InventoryResponse.java create mode 100644 backend/src/main/java/com/petshop/backend/dto/pet/PetRequest.java create mode 100644 backend/src/main/java/com/petshop/backend/dto/pet/PetResponse.java create mode 100644 backend/src/main/java/com/petshop/backend/dto/product/ProductRequest.java create mode 100644 backend/src/main/java/com/petshop/backend/dto/product/ProductResponse.java create mode 100644 backend/src/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierKey.java create mode 100644 backend/src/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierRequest.java create mode 100644 backend/src/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierResponse.java create mode 100644 backend/src/main/java/com/petshop/backend/dto/purchaseorder/PurchaseOrderResponse.java create mode 100644 backend/src/main/java/com/petshop/backend/dto/refund/RefundRequest.java create mode 100644 backend/src/main/java/com/petshop/backend/dto/refund/RefundResponse.java create mode 100644 backend/src/main/java/com/petshop/backend/dto/sale/SaleRequest.java create mode 100644 backend/src/main/java/com/petshop/backend/dto/sale/SaleResponse.java create mode 100644 backend/src/main/java/com/petshop/backend/dto/service/ServiceRequest.java create mode 100644 backend/src/main/java/com/petshop/backend/dto/service/ServiceResponse.java create mode 100644 backend/src/main/java/com/petshop/backend/dto/supplier/SupplierRequest.java create mode 100644 backend/src/main/java/com/petshop/backend/dto/supplier/SupplierResponse.java create mode 100644 backend/src/main/java/com/petshop/backend/dto/user/UserRequest.java create mode 100644 backend/src/main/java/com/petshop/backend/dto/user/UserResponse.java diff --git a/backend/src/main/java/com/petshop/backend/dto/adoption/AdoptionRequest.java b/backend/src/main/java/com/petshop/backend/dto/adoption/AdoptionRequest.java new file mode 100644 index 00000000..96113d13 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/dto/adoption/AdoptionRequest.java @@ -0,0 +1,26 @@ +package com.petshop.backend.dto.adoption; + +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDate; + +@Data +public class AdoptionRequest { + @NotNull(message = "Pet ID is required") + private Long petId; + + @NotNull(message = "Customer ID is required") + private Long customerId; + + @NotNull(message = "Adoption date is required") + private LocalDate adoptionDate; + + @NotNull(message = "Adoption fee is required") + @Positive(message = "Adoption fee must be positive") + private BigDecimal adoptionFee; + + private String notes; +} diff --git a/backend/src/main/java/com/petshop/backend/dto/adoption/AdoptionResponse.java b/backend/src/main/java/com/petshop/backend/dto/adoption/AdoptionResponse.java new file mode 100644 index 00000000..1cf334f7 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/dto/adoption/AdoptionResponse.java @@ -0,0 +1,25 @@ +package com.petshop.backend.dto.adoption; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class AdoptionResponse { + private Long id; + private Long petId; + private String petName; + private Long customerId; + private String customerName; + private LocalDate adoptionDate; + private BigDecimal adoptionFee; + private String notes; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; +} diff --git a/backend/src/main/java/com/petshop/backend/dto/analytics/DashboardResponse.java b/backend/src/main/java/com/petshop/backend/dto/analytics/DashboardResponse.java new file mode 100644 index 00000000..d17967a0 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/dto/analytics/DashboardResponse.java @@ -0,0 +1,57 @@ +package com.petshop.backend.dto.analytics; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.math.BigDecimal; +import java.util.List; +import java.util.Map; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class DashboardResponse { + private SalesSummary salesSummary; + private InventorySummary inventorySummary; + private List topProducts; + private List dailySales; +} + +@Data +@NoArgsConstructor +@AllArgsConstructor +class SalesSummary { + private BigDecimal totalRevenue; + private Long totalSales; + private BigDecimal totalRefunds; + private Long totalRefundCount; +} + +@Data +@NoArgsConstructor +@AllArgsConstructor +class InventorySummary { + private Long totalProducts; + private Long lowStockProducts; + private Long outOfStockProducts; +} + +@Data +@NoArgsConstructor +@AllArgsConstructor +class TopProduct { + private Long productId; + private String productName; + private Long quantitySold; + private BigDecimal revenue; +} + +@Data +@NoArgsConstructor +@AllArgsConstructor +class DailySales { + private String date; + private BigDecimal revenue; + private Long salesCount; +} diff --git a/backend/src/main/java/com/petshop/backend/dto/appointment/AppointmentRequest.java b/backend/src/main/java/com/petshop/backend/dto/appointment/AppointmentRequest.java new file mode 100644 index 00000000..202ba5ae --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/dto/appointment/AppointmentRequest.java @@ -0,0 +1,33 @@ +package com.petshop.backend.dto.appointment; + +import com.petshop.backend.entity.Appointment; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.List; + +@Data +public class AppointmentRequest { + @NotNull(message = "Customer ID is required") + private Long customerId; + + @NotNull(message = "Service ID is required") + private Long serviceId; + + @NotNull(message = "Appointment date is required") + private LocalDate appointmentDate; + + @NotNull(message = "Appointment time is required") + private LocalTime appointmentTime; + + @NotNull(message = "Status is required") + private Appointment.AppointmentStatus status; + + @NotEmpty(message = "At least one pet must be specified") + private List petIds; + + private String notes; +} diff --git a/backend/src/main/java/com/petshop/backend/dto/appointment/AppointmentResponse.java b/backend/src/main/java/com/petshop/backend/dto/appointment/AppointmentResponse.java new file mode 100644 index 00000000..fb528f29 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/dto/appointment/AppointmentResponse.java @@ -0,0 +1,29 @@ +package com.petshop.backend.dto.appointment; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.List; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class AppointmentResponse { + private Long id; + private Long customerId; + private String customerName; + private Long serviceId; + private String serviceName; + private LocalDate appointmentDate; + private LocalTime appointmentTime; + private String status; + private List petNames; + private List petIds; + private String notes; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; +} diff --git a/backend/src/main/java/com/petshop/backend/dto/category/CategoryRequest.java b/backend/src/main/java/com/petshop/backend/dto/category/CategoryRequest.java new file mode 100644 index 00000000..55794933 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/dto/category/CategoryRequest.java @@ -0,0 +1,12 @@ +package com.petshop.backend.dto.category; + +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +@Data +public class CategoryRequest { + @NotBlank(message = "Category name is required") + private String categoryName; + + private String categoryDescription; +} diff --git a/backend/src/main/java/com/petshop/backend/dto/category/CategoryResponse.java b/backend/src/main/java/com/petshop/backend/dto/category/CategoryResponse.java new file mode 100644 index 00000000..0f4939a0 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/dto/category/CategoryResponse.java @@ -0,0 +1,18 @@ +package com.petshop.backend.dto.category; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class CategoryResponse { + private Long id; + private String categoryName; + private String categoryDescription; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; +} diff --git a/backend/src/main/java/com/petshop/backend/dto/common/BulkDeleteRequest.java b/backend/src/main/java/com/petshop/backend/dto/common/BulkDeleteRequest.java new file mode 100644 index 00000000..343ecb5e --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/dto/common/BulkDeleteRequest.java @@ -0,0 +1,12 @@ +package com.petshop.backend.dto.common; + +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; + +import java.util.List; + +@Data +public class BulkDeleteRequest { + @NotEmpty(message = "IDs list cannot be empty") + private List ids; +} diff --git a/backend/src/main/java/com/petshop/backend/dto/common/DropdownOption.java b/backend/src/main/java/com/petshop/backend/dto/common/DropdownOption.java new file mode 100644 index 00000000..8fac390c --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/dto/common/DropdownOption.java @@ -0,0 +1,11 @@ +package com.petshop.backend.dto.common; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class DropdownOption { + private Long id; + private String label; +} diff --git a/backend/src/main/java/com/petshop/backend/dto/customer/CustomerRequest.java b/backend/src/main/java/com/petshop/backend/dto/customer/CustomerRequest.java new file mode 100644 index 00000000..0319f3ea --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/dto/customer/CustomerRequest.java @@ -0,0 +1,17 @@ +package com.petshop.backend.dto.customer; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +@Data +public class CustomerRequest { + @NotBlank(message = "Customer name is required") + private String customerName; + + @Email(message = "Invalid email format") + private String customerEmail; + + private String customerPhone; + private String customerAddress; +} diff --git a/backend/src/main/java/com/petshop/backend/dto/customer/CustomerResponse.java b/backend/src/main/java/com/petshop/backend/dto/customer/CustomerResponse.java new file mode 100644 index 00000000..32c68421 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/dto/customer/CustomerResponse.java @@ -0,0 +1,20 @@ +package com.petshop.backend.dto.customer; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class CustomerResponse { + private Long id; + private String customerName; + private String customerEmail; + private String customerPhone; + private String customerAddress; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; +} diff --git a/backend/src/main/java/com/petshop/backend/dto/inventory/InventoryRequest.java b/backend/src/main/java/com/petshop/backend/dto/inventory/InventoryRequest.java new file mode 100644 index 00000000..8e91e494 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/dto/inventory/InventoryRequest.java @@ -0,0 +1,21 @@ +package com.petshop.backend.dto.inventory; + +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.PositiveOrZero; +import lombok.Data; + +@Data +public class InventoryRequest { + @NotNull(message = "Product ID is required") + private Long productId; + + @NotNull(message = "Store ID is required") + private Long storeId; + + @NotNull(message = "Quantity is required") + @PositiveOrZero(message = "Quantity must be zero or positive") + private Integer quantity; + + @PositiveOrZero(message = "Reorder level must be zero or positive") + private Integer reorderLevel = 10; +} diff --git a/backend/src/main/java/com/petshop/backend/dto/inventory/InventoryResponse.java b/backend/src/main/java/com/petshop/backend/dto/inventory/InventoryResponse.java new file mode 100644 index 00000000..4b5e30d7 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/dto/inventory/InventoryResponse.java @@ -0,0 +1,24 @@ +package com.petshop.backend.dto.inventory; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class InventoryResponse { + private Long id; + private Long productId; + private String productName; + private String categoryName; + private Long storeId; + private String storeName; + private Integer quantity; + private Integer reorderLevel; + private LocalDateTime lastRestocked; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; +} diff --git a/backend/src/main/java/com/petshop/backend/dto/pet/PetRequest.java b/backend/src/main/java/com/petshop/backend/dto/pet/PetRequest.java new file mode 100644 index 00000000..e5014dd7 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/dto/pet/PetRequest.java @@ -0,0 +1,28 @@ +package com.petshop.backend.dto.pet; + +import com.petshop.backend.entity.Pet; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; +import lombok.Data; + +import java.math.BigDecimal; + +@Data +public class PetRequest { + @NotBlank(message = "Pet name is required") + private String petName; + + @NotBlank(message = "Species is required") + private String petSpecies; + + private String petBreed; + + @Positive(message = "Age must be positive") + private Integer petAge; + + @NotNull(message = "Status is required") + private Pet.PetStatus petStatus; + + private BigDecimal petPrice; +} diff --git a/backend/src/main/java/com/petshop/backend/dto/pet/PetResponse.java b/backend/src/main/java/com/petshop/backend/dto/pet/PetResponse.java new file mode 100644 index 00000000..93d64d56 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/dto/pet/PetResponse.java @@ -0,0 +1,23 @@ +package com.petshop.backend.dto.pet; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class PetResponse { + private Long id; + private String petName; + private String petSpecies; + private String petBreed; + private Integer petAge; + private String petStatus; + private BigDecimal petPrice; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; +} diff --git a/backend/src/main/java/com/petshop/backend/dto/product/ProductRequest.java b/backend/src/main/java/com/petshop/backend/dto/product/ProductRequest.java new file mode 100644 index 00000000..2ffe720d --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/dto/product/ProductRequest.java @@ -0,0 +1,25 @@ +package com.petshop.backend.dto.product; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; +import lombok.Data; + +import java.math.BigDecimal; + +@Data +public class ProductRequest { + @NotBlank(message = "Product name is required") + private String productName; + + @NotNull(message = "Category ID is required") + private Long categoryId; + + private String productDescription; + + @NotNull(message = "Product price is required") + @Positive(message = "Price must be positive") + private BigDecimal productPrice; + + private Boolean active = true; +} diff --git a/backend/src/main/java/com/petshop/backend/dto/product/ProductResponse.java b/backend/src/main/java/com/petshop/backend/dto/product/ProductResponse.java new file mode 100644 index 00000000..bc4eaebd --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/dto/product/ProductResponse.java @@ -0,0 +1,23 @@ +package com.petshop.backend.dto.product; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ProductResponse { + private Long id; + private String productName; + private Long categoryId; + private String categoryName; + private String productDescription; + private BigDecimal productPrice; + private Boolean active; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; +} diff --git a/backend/src/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierKey.java b/backend/src/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierKey.java new file mode 100644 index 00000000..191c8575 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierKey.java @@ -0,0 +1,18 @@ +package com.petshop.backend.dto.productsupplier; + +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; + +import java.util.List; + +@Data +public class ProductSupplierKey { + private Long productId; + private Long supplierId; +} + +@Data +class BulkDeleteProductSupplierRequest { + @NotEmpty(message = "Keys list cannot be empty") + private List keys; +} diff --git a/backend/src/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierRequest.java b/backend/src/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierRequest.java new file mode 100644 index 00000000..68263597 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierRequest.java @@ -0,0 +1,26 @@ +package com.petshop.backend.dto.productsupplier; + +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.PositiveOrZero; +import lombok.Data; + +import java.math.BigDecimal; + +@Data +public class ProductSupplierRequest { + @NotNull(message = "Product ID is required") + private Long productId; + + @NotNull(message = "Supplier ID is required") + private Long supplierId; + + @NotNull(message = "Cost price is required") + @Positive(message = "Cost price must be positive") + private BigDecimal costPrice; + + @PositiveOrZero(message = "Lead time must be zero or positive") + private Integer leadTimeDays; + + private Boolean isPreferred = false; +} diff --git a/backend/src/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierResponse.java b/backend/src/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierResponse.java new file mode 100644 index 00000000..371fd34b --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierResponse.java @@ -0,0 +1,23 @@ +package com.petshop.backend.dto.productsupplier; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ProductSupplierResponse { + private Long productId; + private String productName; + private Long supplierId; + private String supplierName; + private BigDecimal costPrice; + private Integer leadTimeDays; + private Boolean isPreferred; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; +} diff --git a/backend/src/main/java/com/petshop/backend/dto/purchaseorder/PurchaseOrderResponse.java b/backend/src/main/java/com/petshop/backend/dto/purchaseorder/PurchaseOrderResponse.java new file mode 100644 index 00000000..657816ea --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/dto/purchaseorder/PurchaseOrderResponse.java @@ -0,0 +1,39 @@ +package com.petshop.backend.dto.purchaseorder; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class PurchaseOrderResponse { + private Long id; + private Long supplierId; + private String supplierName; + private LocalDate orderDate; + private LocalDate expectedDelivery; + private String status; + private BigDecimal totalAmount; + private String notes; + private List items; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; +} + +@Data +@NoArgsConstructor +@AllArgsConstructor +class PurchaseOrderItemResponse { + private Long id; + private Long productId; + private String productName; + private Integer quantity; + private BigDecimal unitCost; + private BigDecimal subtotal; +} diff --git a/backend/src/main/java/com/petshop/backend/dto/refund/RefundRequest.java b/backend/src/main/java/com/petshop/backend/dto/refund/RefundRequest.java new file mode 100644 index 00000000..61ec7b3f --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/dto/refund/RefundRequest.java @@ -0,0 +1,28 @@ +package com.petshop.backend.dto.refund; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; +import lombok.Data; + +import java.util.List; + +@Data +public class RefundRequest { + @NotEmpty(message = "At least one item is required") + @Valid + private List items; + + private String refundReason; +} + +@Data +class RefundItemRequest { + @NotNull(message = "Sale item ID is required") + private Long saleItemId; + + @NotNull(message = "Quantity is required") + @Positive(message = "Quantity must be positive") + private Integer quantity; +} diff --git a/backend/src/main/java/com/petshop/backend/dto/refund/RefundResponse.java b/backend/src/main/java/com/petshop/backend/dto/refund/RefundResponse.java new file mode 100644 index 00000000..50e22874 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/dto/refund/RefundResponse.java @@ -0,0 +1,36 @@ +package com.petshop.backend.dto.refund; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class RefundResponse { + private Long id; + private Long saleId; + private LocalDateTime refundDate; + private BigDecimal refundAmount; + private String refundReason; + private Long processedBy; + private String processedByName; + private List items; + private LocalDateTime createdAt; +} + +@Data +@NoArgsConstructor +@AllArgsConstructor +class RefundItemResponse { + private Long id; + private Long saleItemId; + private Long productId; + private String productName; + private Integer quantity; + private BigDecimal refundAmount; +} diff --git a/backend/src/main/java/com/petshop/backend/dto/sale/SaleRequest.java b/backend/src/main/java/com/petshop/backend/dto/sale/SaleRequest.java new file mode 100644 index 00000000..56a7c71d --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/dto/sale/SaleRequest.java @@ -0,0 +1,38 @@ +package com.petshop.backend.dto.sale; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; +import lombok.Data; + +import java.math.BigDecimal; +import java.util.List; + +@Data +public class SaleRequest { + private Long customerId; + + @NotNull(message = "Store ID is required") + private Long storeId; + + private String paymentMethod; + + private BigDecimal tax = BigDecimal.ZERO; + + @NotEmpty(message = "At least one item is required") + @Valid + private List items; + + private String notes; +} + +@Data +class SaleItemRequest { + @NotNull(message = "Product ID is required") + private Long productId; + + @NotNull(message = "Quantity is required") + @Positive(message = "Quantity must be positive") + private Integer quantity; +} diff --git a/backend/src/main/java/com/petshop/backend/dto/sale/SaleResponse.java b/backend/src/main/java/com/petshop/backend/dto/sale/SaleResponse.java new file mode 100644 index 00000000..b44fd542 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/dto/sale/SaleResponse.java @@ -0,0 +1,42 @@ +package com.petshop.backend.dto.sale; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class SaleResponse { + private Long id; + private LocalDateTime saleDate; + private Long employeeId; + private String employeeName; + private Long customerId; + private String customerName; + private Long storeId; + private String storeName; + private BigDecimal subtotal; + private BigDecimal tax; + private BigDecimal total; + private String paymentMethod; + private String notes; + private List items; + private LocalDateTime createdAt; +} + +@Data +@NoArgsConstructor +@AllArgsConstructor +class SaleItemResponse { + private Long id; + private Long productId; + private String productName; + private Integer quantity; + private BigDecimal unitPrice; + private BigDecimal subtotal; +} diff --git a/backend/src/main/java/com/petshop/backend/dto/service/ServiceRequest.java b/backend/src/main/java/com/petshop/backend/dto/service/ServiceRequest.java new file mode 100644 index 00000000..419b82ab --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/dto/service/ServiceRequest.java @@ -0,0 +1,25 @@ +package com.petshop.backend.dto.service; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; +import lombok.Data; + +import java.math.BigDecimal; + +@Data +public class ServiceRequest { + @NotBlank(message = "Service name is required") + private String serviceName; + + private String serviceDescription; + + @NotNull(message = "Service price is required") + @Positive(message = "Price must be positive") + private BigDecimal servicePrice; + + @Positive(message = "Duration must be positive") + private Integer serviceDurationMinutes; + + private Boolean active = true; +} diff --git a/backend/src/main/java/com/petshop/backend/dto/service/ServiceResponse.java b/backend/src/main/java/com/petshop/backend/dto/service/ServiceResponse.java new file mode 100644 index 00000000..5f786c03 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/dto/service/ServiceResponse.java @@ -0,0 +1,22 @@ +package com.petshop.backend.dto.service; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ServiceResponse { + private Long id; + private String serviceName; + private String serviceDescription; + private BigDecimal servicePrice; + private Integer serviceDurationMinutes; + private Boolean active; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; +} diff --git a/backend/src/main/java/com/petshop/backend/dto/supplier/SupplierRequest.java b/backend/src/main/java/com/petshop/backend/dto/supplier/SupplierRequest.java new file mode 100644 index 00000000..18d0c27a --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/dto/supplier/SupplierRequest.java @@ -0,0 +1,20 @@ +package com.petshop.backend.dto.supplier; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +@Data +public class SupplierRequest { + @NotBlank(message = "Supplier name is required") + private String supplierName; + + private String supplierContact; + + @Email(message = "Invalid email format") + private String supplierEmail; + + private String supplierPhone; + private String supplierAddress; + private Boolean active = true; +} diff --git a/backend/src/main/java/com/petshop/backend/dto/supplier/SupplierResponse.java b/backend/src/main/java/com/petshop/backend/dto/supplier/SupplierResponse.java new file mode 100644 index 00000000..71b07142 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/dto/supplier/SupplierResponse.java @@ -0,0 +1,22 @@ +package com.petshop.backend.dto.supplier; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class SupplierResponse { + private Long id; + private String supplierName; + private String supplierContact; + private String supplierEmail; + private String supplierPhone; + private String supplierAddress; + private Boolean active; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; +} diff --git a/backend/src/main/java/com/petshop/backend/dto/user/UserRequest.java b/backend/src/main/java/com/petshop/backend/dto/user/UserRequest.java new file mode 100644 index 00000000..5018a580 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/dto/user/UserRequest.java @@ -0,0 +1,29 @@ +package com.petshop.backend.dto.user; + +import com.petshop.backend.entity.User; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Data; + +@Data +public class UserRequest { + @NotBlank(message = "Username is required") + @Size(min = 3, max = 50, message = "Username must be between 3 and 50 characters") + private String username; + + @Size(min = 6, message = "Password must be at least 6 characters") + private String password; + + @NotBlank(message = "Full name is required") + private String fullName; + + @Email(message = "Invalid email format") + private String email; + + @NotNull(message = "Role is required") + private User.Role role; + + private Boolean active = true; +} diff --git a/backend/src/main/java/com/petshop/backend/dto/user/UserResponse.java b/backend/src/main/java/com/petshop/backend/dto/user/UserResponse.java new file mode 100644 index 00000000..7d929049 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/dto/user/UserResponse.java @@ -0,0 +1,21 @@ +package com.petshop.backend.dto.user; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class UserResponse { + private Long id; + private String username; + private String fullName; + private String email; + private String role; + private Boolean active; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; +} From 33bbb94a37eda6a6837551d0eaf2168321178c7b Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Wed, 4 Mar 2026 17:10:23 -0700 Subject: [PATCH 05/84] Add staff services and controllers --- .../controller/AdoptionController.java | 55 +++++++ .../controller/AppointmentController.java | 55 +++++++ .../controller/CategoryController.java | 57 +++++++ .../controller/CustomerController.java | 57 +++++++ .../backend/controller/PetController.java | 57 +++++++ .../backend/controller/ProductController.java | 57 +++++++ .../backend/controller/ServiceController.java | 57 +++++++ .../backend/exception/BusinessException.java | 7 + .../exception/GlobalExceptionHandler.java | 65 ++++++++ .../exception/ResourceNotFoundException.java | 7 + .../backend/service/AdoptionService.java | 104 +++++++++++++ .../backend/service/AppointmentService.java | 140 ++++++++++++++++++ .../backend/service/CategoryService.java | 81 ++++++++++ .../backend/service/CustomerService.java | 87 +++++++++++ .../petshop/backend/service/PetService.java | 93 ++++++++++++ .../backend/service/ProductService.java | 100 +++++++++++++ .../backend/service/ServiceService.java | 89 +++++++++++ 17 files changed, 1168 insertions(+) create mode 100644 backend/src/main/java/com/petshop/backend/controller/AdoptionController.java create mode 100644 backend/src/main/java/com/petshop/backend/controller/AppointmentController.java create mode 100644 backend/src/main/java/com/petshop/backend/controller/CategoryController.java create mode 100644 backend/src/main/java/com/petshop/backend/controller/CustomerController.java create mode 100644 backend/src/main/java/com/petshop/backend/controller/PetController.java create mode 100644 backend/src/main/java/com/petshop/backend/controller/ProductController.java create mode 100644 backend/src/main/java/com/petshop/backend/controller/ServiceController.java create mode 100644 backend/src/main/java/com/petshop/backend/exception/BusinessException.java create mode 100644 backend/src/main/java/com/petshop/backend/exception/GlobalExceptionHandler.java create mode 100644 backend/src/main/java/com/petshop/backend/exception/ResourceNotFoundException.java create mode 100644 backend/src/main/java/com/petshop/backend/service/AdoptionService.java create mode 100644 backend/src/main/java/com/petshop/backend/service/AppointmentService.java create mode 100644 backend/src/main/java/com/petshop/backend/service/CategoryService.java create mode 100644 backend/src/main/java/com/petshop/backend/service/CustomerService.java create mode 100644 backend/src/main/java/com/petshop/backend/service/PetService.java create mode 100644 backend/src/main/java/com/petshop/backend/service/ProductService.java create mode 100644 backend/src/main/java/com/petshop/backend/service/ServiceService.java diff --git a/backend/src/main/java/com/petshop/backend/controller/AdoptionController.java b/backend/src/main/java/com/petshop/backend/controller/AdoptionController.java new file mode 100644 index 00000000..0efe8741 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/controller/AdoptionController.java @@ -0,0 +1,55 @@ +package com.petshop.backend.controller; + +import com.petshop.backend.dto.adoption.AdoptionRequest; +import com.petshop.backend.dto.adoption.AdoptionResponse; +import com.petshop.backend.dto.common.BulkDeleteRequest; +import com.petshop.backend.service.AdoptionService; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/v1/adoptions") +@RequiredArgsConstructor +public class AdoptionController { + + private final AdoptionService adoptionService; + + @GetMapping + public ResponseEntity> getAllAdoptions(Pageable pageable) { + return ResponseEntity.ok(adoptionService.getAllAdoptions(pageable)); + } + + @GetMapping("/{id}") + public ResponseEntity getAdoptionById(@PathVariable Long id) { + return ResponseEntity.ok(adoptionService.getAdoptionById(id)); + } + + @PostMapping + public ResponseEntity createAdoption(@Valid @RequestBody AdoptionRequest request) { + return ResponseEntity.status(HttpStatus.CREATED).body(adoptionService.createAdoption(request)); + } + + @PutMapping("/{id}") + public ResponseEntity updateAdoption( + @PathVariable Long id, + @Valid @RequestBody AdoptionRequest request) { + return ResponseEntity.ok(adoptionService.updateAdoption(id, request)); + } + + @DeleteMapping("/{id}") + public ResponseEntity deleteAdoption(@PathVariable Long id) { + adoptionService.deleteAdoption(id); + return ResponseEntity.noContent().build(); + } + + @PostMapping("/bulk-delete") + public ResponseEntity bulkDeleteAdoptions(@Valid @RequestBody BulkDeleteRequest request) { + adoptionService.bulkDeleteAdoptions(request); + return ResponseEntity.noContent().build(); + } +} diff --git a/backend/src/main/java/com/petshop/backend/controller/AppointmentController.java b/backend/src/main/java/com/petshop/backend/controller/AppointmentController.java new file mode 100644 index 00000000..843aafe1 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/controller/AppointmentController.java @@ -0,0 +1,55 @@ +package com.petshop.backend.controller; + +import com.petshop.backend.dto.appointment.AppointmentRequest; +import com.petshop.backend.dto.appointment.AppointmentResponse; +import com.petshop.backend.dto.common.BulkDeleteRequest; +import com.petshop.backend.service.AppointmentService; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/v1/appointments") +@RequiredArgsConstructor +public class AppointmentController { + + private final AppointmentService appointmentService; + + @GetMapping + public ResponseEntity> getAllAppointments(Pageable pageable) { + return ResponseEntity.ok(appointmentService.getAllAppointments(pageable)); + } + + @GetMapping("/{id}") + public ResponseEntity getAppointmentById(@PathVariable Long id) { + return ResponseEntity.ok(appointmentService.getAppointmentById(id)); + } + + @PostMapping + public ResponseEntity createAppointment(@Valid @RequestBody AppointmentRequest request) { + return ResponseEntity.status(HttpStatus.CREATED).body(appointmentService.createAppointment(request)); + } + + @PutMapping("/{id}") + public ResponseEntity updateAppointment( + @PathVariable Long id, + @Valid @RequestBody AppointmentRequest request) { + return ResponseEntity.ok(appointmentService.updateAppointment(id, request)); + } + + @DeleteMapping("/{id}") + public ResponseEntity deleteAppointment(@PathVariable Long id) { + appointmentService.deleteAppointment(id); + return ResponseEntity.noContent().build(); + } + + @PostMapping("/bulk-delete") + public ResponseEntity bulkDeleteAppointments(@Valid @RequestBody BulkDeleteRequest request) { + appointmentService.bulkDeleteAppointments(request); + return ResponseEntity.noContent().build(); + } +} diff --git a/backend/src/main/java/com/petshop/backend/controller/CategoryController.java b/backend/src/main/java/com/petshop/backend/controller/CategoryController.java new file mode 100644 index 00000000..7c6ab6be --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/controller/CategoryController.java @@ -0,0 +1,57 @@ +package com.petshop.backend.controller; + +import com.petshop.backend.dto.category.CategoryRequest; +import com.petshop.backend.dto.category.CategoryResponse; +import com.petshop.backend.dto.common.BulkDeleteRequest; +import com.petshop.backend.service.CategoryService; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/v1/categories") +@RequiredArgsConstructor +public class CategoryController { + + private final CategoryService categoryService; + + @GetMapping + public ResponseEntity> getAllCategories( + @RequestParam(required = false) String q, + Pageable pageable) { + return ResponseEntity.ok(categoryService.getAllCategories(q, pageable)); + } + + @GetMapping("/{id}") + public ResponseEntity getCategoryById(@PathVariable Long id) { + return ResponseEntity.ok(categoryService.getCategoryById(id)); + } + + @PostMapping + public ResponseEntity createCategory(@Valid @RequestBody CategoryRequest request) { + return ResponseEntity.status(HttpStatus.CREATED).body(categoryService.createCategory(request)); + } + + @PutMapping("/{id}") + public ResponseEntity updateCategory( + @PathVariable Long id, + @Valid @RequestBody CategoryRequest request) { + return ResponseEntity.ok(categoryService.updateCategory(id, request)); + } + + @DeleteMapping("/{id}") + public ResponseEntity deleteCategory(@PathVariable Long id) { + categoryService.deleteCategory(id); + return ResponseEntity.noContent().build(); + } + + @PostMapping("/bulk-delete") + public ResponseEntity bulkDeleteCategories(@Valid @RequestBody BulkDeleteRequest request) { + categoryService.bulkDeleteCategories(request); + return ResponseEntity.noContent().build(); + } +} diff --git a/backend/src/main/java/com/petshop/backend/controller/CustomerController.java b/backend/src/main/java/com/petshop/backend/controller/CustomerController.java new file mode 100644 index 00000000..393d043f --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/controller/CustomerController.java @@ -0,0 +1,57 @@ +package com.petshop.backend.controller; + +import com.petshop.backend.dto.common.BulkDeleteRequest; +import com.petshop.backend.dto.customer.CustomerRequest; +import com.petshop.backend.dto.customer.CustomerResponse; +import com.petshop.backend.service.CustomerService; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/v1/customers") +@RequiredArgsConstructor +public class CustomerController { + + private final CustomerService customerService; + + @GetMapping + public ResponseEntity> getAllCustomers( + @RequestParam(required = false) String q, + Pageable pageable) { + return ResponseEntity.ok(customerService.getAllCustomers(q, pageable)); + } + + @GetMapping("/{id}") + public ResponseEntity getCustomerById(@PathVariable Long id) { + return ResponseEntity.ok(customerService.getCustomerById(id)); + } + + @PostMapping + public ResponseEntity createCustomer(@Valid @RequestBody CustomerRequest request) { + return ResponseEntity.status(HttpStatus.CREATED).body(customerService.createCustomer(request)); + } + + @PutMapping("/{id}") + public ResponseEntity updateCustomer( + @PathVariable Long id, + @Valid @RequestBody CustomerRequest request) { + return ResponseEntity.ok(customerService.updateCustomer(id, request)); + } + + @DeleteMapping("/{id}") + public ResponseEntity deleteCustomer(@PathVariable Long id) { + customerService.deleteCustomer(id); + return ResponseEntity.noContent().build(); + } + + @PostMapping("/bulk-delete") + public ResponseEntity bulkDeleteCustomers(@Valid @RequestBody BulkDeleteRequest request) { + customerService.bulkDeleteCustomers(request); + return ResponseEntity.noContent().build(); + } +} diff --git a/backend/src/main/java/com/petshop/backend/controller/PetController.java b/backend/src/main/java/com/petshop/backend/controller/PetController.java new file mode 100644 index 00000000..f5c45015 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/controller/PetController.java @@ -0,0 +1,57 @@ +package com.petshop.backend.controller; + +import com.petshop.backend.dto.common.BulkDeleteRequest; +import com.petshop.backend.dto.pet.PetRequest; +import com.petshop.backend.dto.pet.PetResponse; +import com.petshop.backend.service.PetService; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/v1/pets") +@RequiredArgsConstructor +public class PetController { + + private final PetService petService; + + @GetMapping + public ResponseEntity> getAllPets( + @RequestParam(required = false) String q, + Pageable pageable) { + return ResponseEntity.ok(petService.getAllPets(q, pageable)); + } + + @GetMapping("/{id}") + public ResponseEntity getPetById(@PathVariable Long id) { + return ResponseEntity.ok(petService.getPetById(id)); + } + + @PostMapping + public ResponseEntity createPet(@Valid @RequestBody PetRequest request) { + return ResponseEntity.status(HttpStatus.CREATED).body(petService.createPet(request)); + } + + @PutMapping("/{id}") + public ResponseEntity updatePet( + @PathVariable Long id, + @Valid @RequestBody PetRequest request) { + return ResponseEntity.ok(petService.updatePet(id, request)); + } + + @DeleteMapping("/{id}") + public ResponseEntity deletePet(@PathVariable Long id) { + petService.deletePet(id); + return ResponseEntity.noContent().build(); + } + + @PostMapping("/bulk-delete") + public ResponseEntity bulkDeletePets(@Valid @RequestBody BulkDeleteRequest request) { + petService.bulkDeletePets(request); + return ResponseEntity.noContent().build(); + } +} diff --git a/backend/src/main/java/com/petshop/backend/controller/ProductController.java b/backend/src/main/java/com/petshop/backend/controller/ProductController.java new file mode 100644 index 00000000..7cec17a8 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/controller/ProductController.java @@ -0,0 +1,57 @@ +package com.petshop.backend.controller; + +import com.petshop.backend.dto.common.BulkDeleteRequest; +import com.petshop.backend.dto.product.ProductRequest; +import com.petshop.backend.dto.product.ProductResponse; +import com.petshop.backend.service.ProductService; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/v1/products") +@RequiredArgsConstructor +public class ProductController { + + private final ProductService productService; + + @GetMapping + public ResponseEntity> getAllProducts( + @RequestParam(required = false) String q, + Pageable pageable) { + return ResponseEntity.ok(productService.getAllProducts(q, pageable)); + } + + @GetMapping("/{id}") + public ResponseEntity getProductById(@PathVariable Long id) { + return ResponseEntity.ok(productService.getProductById(id)); + } + + @PostMapping + public ResponseEntity createProduct(@Valid @RequestBody ProductRequest request) { + return ResponseEntity.status(HttpStatus.CREATED).body(productService.createProduct(request)); + } + + @PutMapping("/{id}") + public ResponseEntity updateProduct( + @PathVariable Long id, + @Valid @RequestBody ProductRequest request) { + return ResponseEntity.ok(productService.updateProduct(id, request)); + } + + @DeleteMapping("/{id}") + public ResponseEntity deleteProduct(@PathVariable Long id) { + productService.deleteProduct(id); + return ResponseEntity.noContent().build(); + } + + @PostMapping("/bulk-delete") + public ResponseEntity bulkDeleteProducts(@Valid @RequestBody BulkDeleteRequest request) { + productService.bulkDeleteProducts(request); + return ResponseEntity.noContent().build(); + } +} diff --git a/backend/src/main/java/com/petshop/backend/controller/ServiceController.java b/backend/src/main/java/com/petshop/backend/controller/ServiceController.java new file mode 100644 index 00000000..e1c5415b --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/controller/ServiceController.java @@ -0,0 +1,57 @@ +package com.petshop.backend.controller; + +import com.petshop.backend.dto.common.BulkDeleteRequest; +import com.petshop.backend.dto.service.ServiceRequest; +import com.petshop.backend.dto.service.ServiceResponse; +import com.petshop.backend.service.ServiceService; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/v1/services") +@RequiredArgsConstructor +public class ServiceController { + + private final ServiceService serviceService; + + @GetMapping + public ResponseEntity> getAllServices( + @RequestParam(required = false) String q, + Pageable pageable) { + return ResponseEntity.ok(serviceService.getAllServices(q, pageable)); + } + + @GetMapping("/{id}") + public ResponseEntity getServiceById(@PathVariable Long id) { + return ResponseEntity.ok(serviceService.getServiceById(id)); + } + + @PostMapping + public ResponseEntity createService(@Valid @RequestBody ServiceRequest request) { + return ResponseEntity.status(HttpStatus.CREATED).body(serviceService.createService(request)); + } + + @PutMapping("/{id}") + public ResponseEntity updateService( + @PathVariable Long id, + @Valid @RequestBody ServiceRequest request) { + return ResponseEntity.ok(serviceService.updateService(id, request)); + } + + @DeleteMapping("/{id}") + public ResponseEntity deleteService(@PathVariable Long id) { + serviceService.deleteService(id); + return ResponseEntity.noContent().build(); + } + + @PostMapping("/bulk-delete") + public ResponseEntity bulkDeleteServices(@Valid @RequestBody BulkDeleteRequest request) { + serviceService.bulkDeleteServices(request); + return ResponseEntity.noContent().build(); + } +} diff --git a/backend/src/main/java/com/petshop/backend/exception/BusinessException.java b/backend/src/main/java/com/petshop/backend/exception/BusinessException.java new file mode 100644 index 00000000..005ee62b --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/exception/BusinessException.java @@ -0,0 +1,7 @@ +package com.petshop.backend.exception; + +public class BusinessException extends RuntimeException { + public BusinessException(String message) { + super(message); + } +} diff --git a/backend/src/main/java/com/petshop/backend/exception/GlobalExceptionHandler.java b/backend/src/main/java/com/petshop/backend/exception/GlobalExceptionHandler.java new file mode 100644 index 00000000..290752dd --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/exception/GlobalExceptionHandler.java @@ -0,0 +1,65 @@ +package com.petshop.backend.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.FieldError; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.Map; + +@RestControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(ResourceNotFoundException.class) + public ResponseEntity handleResourceNotFound(ResourceNotFoundException ex) { + ErrorResponse error = new ErrorResponse( + HttpStatus.NOT_FOUND.value(), + ex.getMessage(), + LocalDateTime.now() + ); + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error); + } + + @ExceptionHandler(BusinessException.class) + public ResponseEntity handleBusinessException(BusinessException ex) { + ErrorResponse error = new ErrorResponse( + HttpStatus.BAD_REQUEST.value(), + ex.getMessage(), + LocalDateTime.now() + ); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error); + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity> handleValidationExceptions(MethodArgumentNotValidException ex) { + Map errors = new HashMap<>(); + ex.getBindingResult().getAllErrors().forEach((error) -> { + String fieldName = ((FieldError) error).getField(); + String errorMessage = error.getDefaultMessage(); + errors.put(fieldName, errorMessage); + }); + + Map response = new HashMap<>(); + response.put("status", HttpStatus.BAD_REQUEST.value()); + response.put("errors", errors); + response.put("timestamp", LocalDateTime.now()); + + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response); + } + + @ExceptionHandler(Exception.class) + public ResponseEntity handleGenericException(Exception ex) { + ErrorResponse error = new ErrorResponse( + HttpStatus.INTERNAL_SERVER_ERROR.value(), + "An unexpected error occurred: " + ex.getMessage(), + LocalDateTime.now() + ); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error); + } +} + +record ErrorResponse(int status, String message, LocalDateTime timestamp) {} diff --git a/backend/src/main/java/com/petshop/backend/exception/ResourceNotFoundException.java b/backend/src/main/java/com/petshop/backend/exception/ResourceNotFoundException.java new file mode 100644 index 00000000..08b426b7 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/exception/ResourceNotFoundException.java @@ -0,0 +1,7 @@ +package com.petshop.backend.exception; + +public class ResourceNotFoundException extends RuntimeException { + public ResourceNotFoundException(String message) { + super(message); + } +} diff --git a/backend/src/main/java/com/petshop/backend/service/AdoptionService.java b/backend/src/main/java/com/petshop/backend/service/AdoptionService.java new file mode 100644 index 00000000..f55cf358 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/service/AdoptionService.java @@ -0,0 +1,104 @@ +package com.petshop.backend.service; + +import com.petshop.backend.dto.adoption.AdoptionRequest; +import com.petshop.backend.dto.adoption.AdoptionResponse; +import com.petshop.backend.dto.common.BulkDeleteRequest; +import com.petshop.backend.entity.Adoption; +import com.petshop.backend.entity.Customer; +import com.petshop.backend.entity.Pet; +import com.petshop.backend.exception.ResourceNotFoundException; +import com.petshop.backend.repository.AdoptionRepository; +import com.petshop.backend.repository.CustomerRepository; +import com.petshop.backend.repository.PetRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class AdoptionService { + + private final AdoptionRepository adoptionRepository; + private final PetRepository petRepository; + private final CustomerRepository customerRepository; + + public Page getAllAdoptions(Pageable pageable) { + return adoptionRepository.findAll(pageable).map(this::mapToResponse); + } + + public AdoptionResponse getAdoptionById(Long id) { + Adoption adoption = adoptionRepository.findById(id) + .orElseThrow(() -> new ResourceNotFoundException("Adoption not found with id: " + id)); + return mapToResponse(adoption); + } + + @Transactional + public AdoptionResponse createAdoption(AdoptionRequest request) { + Pet pet = petRepository.findById(request.getPetId()) + .orElseThrow(() -> new ResourceNotFoundException("Pet not found with id: " + request.getPetId())); + + Customer customer = customerRepository.findById(request.getCustomerId()) + .orElseThrow(() -> new ResourceNotFoundException("Customer not found with id: " + request.getCustomerId())); + + Adoption adoption = new Adoption(); + adoption.setPet(pet); + adoption.setCustomer(customer); + adoption.setAdoptionDate(request.getAdoptionDate()); + adoption.setAdoptionFee(request.getAdoptionFee()); + adoption.setNotes(request.getNotes()); + + adoption = adoptionRepository.save(adoption); + return mapToResponse(adoption); + } + + @Transactional + public AdoptionResponse updateAdoption(Long id, AdoptionRequest request) { + Adoption adoption = adoptionRepository.findById(id) + .orElseThrow(() -> new ResourceNotFoundException("Adoption not found with id: " + id)); + + Pet pet = petRepository.findById(request.getPetId()) + .orElseThrow(() -> new ResourceNotFoundException("Pet not found with id: " + request.getPetId())); + + Customer customer = customerRepository.findById(request.getCustomerId()) + .orElseThrow(() -> new ResourceNotFoundException("Customer not found with id: " + request.getCustomerId())); + + adoption.setPet(pet); + adoption.setCustomer(customer); + adoption.setAdoptionDate(request.getAdoptionDate()); + adoption.setAdoptionFee(request.getAdoptionFee()); + adoption.setNotes(request.getNotes()); + + adoption = adoptionRepository.save(adoption); + return mapToResponse(adoption); + } + + @Transactional + public void deleteAdoption(Long id) { + if (!adoptionRepository.existsById(id)) { + throw new ResourceNotFoundException("Adoption not found with id: " + id); + } + adoptionRepository.deleteById(id); + } + + @Transactional + public void bulkDeleteAdoptions(BulkDeleteRequest request) { + adoptionRepository.deleteAllById(request.getIds()); + } + + private AdoptionResponse mapToResponse(Adoption adoption) { + return new AdoptionResponse( + adoption.getId(), + adoption.getPet().getId(), + adoption.getPet().getPetName(), + adoption.getCustomer().getId(), + adoption.getCustomer().getCustomerName(), + adoption.getAdoptionDate(), + adoption.getAdoptionFee(), + adoption.getNotes(), + adoption.getCreatedAt(), + adoption.getUpdatedAt() + ); + } +} diff --git a/backend/src/main/java/com/petshop/backend/service/AppointmentService.java b/backend/src/main/java/com/petshop/backend/service/AppointmentService.java new file mode 100644 index 00000000..3c941b68 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/service/AppointmentService.java @@ -0,0 +1,140 @@ +package com.petshop.backend.service; + +import com.petshop.backend.dto.appointment.AppointmentRequest; +import com.petshop.backend.dto.appointment.AppointmentResponse; +import com.petshop.backend.dto.common.BulkDeleteRequest; +import com.petshop.backend.entity.Appointment; +import com.petshop.backend.entity.Customer; +import com.petshop.backend.entity.Pet; +import com.petshop.backend.exception.ResourceNotFoundException; +import com.petshop.backend.repository.AppointmentRepository; +import com.petshop.backend.repository.CustomerRepository; +import com.petshop.backend.repository.PetRepository; +import com.petshop.backend.repository.ServiceRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class AppointmentService { + + private final AppointmentRepository appointmentRepository; + private final CustomerRepository customerRepository; + private final ServiceRepository serviceRepository; + private final PetRepository petRepository; + + public Page getAllAppointments(Pageable pageable) { + return appointmentRepository.findAll(pageable).map(this::mapToResponse); + } + + public AppointmentResponse getAppointmentById(Long id) { + Appointment appointment = appointmentRepository.findById(id) + .orElseThrow(() -> new ResourceNotFoundException("Appointment not found with id: " + id)); + return mapToResponse(appointment); + } + + @Transactional + public AppointmentResponse createAppointment(AppointmentRequest request) { + Customer customer = customerRepository.findById(request.getCustomerId()) + .orElseThrow(() -> new ResourceNotFoundException("Customer not found with id: " + request.getCustomerId())); + + com.petshop.backend.entity.Service service = serviceRepository.findById(request.getServiceId()) + .orElseThrow(() -> new ResourceNotFoundException("Service not found with id: " + request.getServiceId())); + + Set pets = fetchPets(request.getPetIds()); + + Appointment appointment = new Appointment(); + appointment.setCustomer(customer); + appointment.setService(service); + appointment.setAppointmentDate(request.getAppointmentDate()); + appointment.setAppointmentTime(request.getAppointmentTime()); + appointment.setStatus(request.getStatus()); + appointment.setPets(pets); + appointment.setNotes(request.getNotes()); + + appointment = appointmentRepository.save(appointment); + return mapToResponse(appointment); + } + + @Transactional + public AppointmentResponse updateAppointment(Long id, AppointmentRequest request) { + Appointment appointment = appointmentRepository.findById(id) + .orElseThrow(() -> new ResourceNotFoundException("Appointment not found with id: " + id)); + + Customer customer = customerRepository.findById(request.getCustomerId()) + .orElseThrow(() -> new ResourceNotFoundException("Customer not found with id: " + request.getCustomerId())); + + com.petshop.backend.entity.Service service = serviceRepository.findById(request.getServiceId()) + .orElseThrow(() -> new ResourceNotFoundException("Service not found with id: " + request.getServiceId())); + + Set pets = fetchPets(request.getPetIds()); + + appointment.setCustomer(customer); + appointment.setService(service); + appointment.setAppointmentDate(request.getAppointmentDate()); + appointment.setAppointmentTime(request.getAppointmentTime()); + appointment.setStatus(request.getStatus()); + appointment.setPets(pets); + appointment.setNotes(request.getNotes()); + + appointment = appointmentRepository.save(appointment); + return mapToResponse(appointment); + } + + @Transactional + public void deleteAppointment(Long id) { + if (!appointmentRepository.existsById(id)) { + throw new ResourceNotFoundException("Appointment not found with id: " + id); + } + appointmentRepository.deleteById(id); + } + + @Transactional + public void bulkDeleteAppointments(BulkDeleteRequest request) { + appointmentRepository.deleteAllById(request.getIds()); + } + + private Set fetchPets(List petIds) { + Set pets = new HashSet<>(); + for (Long petId : petIds) { + Pet pet = petRepository.findById(petId) + .orElseThrow(() -> new ResourceNotFoundException("Pet not found with id: " + petId)); + pets.add(pet); + } + return pets; + } + + private AppointmentResponse mapToResponse(Appointment appointment) { + List petNames = appointment.getPets().stream() + .map(Pet::getPetName) + .collect(Collectors.toList()); + + List petIds = appointment.getPets().stream() + .map(Pet::getId) + .collect(Collectors.toList()); + + return new AppointmentResponse( + appointment.getId(), + appointment.getCustomer().getId(), + appointment.getCustomer().getCustomerName(), + appointment.getService().getId(), + appointment.getService().getServiceName(), + appointment.getAppointmentDate(), + appointment.getAppointmentTime(), + appointment.getStatus() != null ? appointment.getStatus().toString() : null, + petNames, + petIds, + appointment.getNotes(), + appointment.getCreatedAt(), + appointment.getUpdatedAt() + ); + } +} diff --git a/backend/src/main/java/com/petshop/backend/service/CategoryService.java b/backend/src/main/java/com/petshop/backend/service/CategoryService.java new file mode 100644 index 00000000..7d596ce7 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/service/CategoryService.java @@ -0,0 +1,81 @@ +package com.petshop.backend.service; + +import com.petshop.backend.dto.category.CategoryRequest; +import com.petshop.backend.dto.category.CategoryResponse; +import com.petshop.backend.dto.common.BulkDeleteRequest; +import com.petshop.backend.entity.Category; +import com.petshop.backend.exception.ResourceNotFoundException; +import com.petshop.backend.repository.CategoryRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class CategoryService { + + private final CategoryRepository categoryRepository; + + public Page getAllCategories(String query, Pageable pageable) { + Page categories; + if (query != null && !query.trim().isEmpty()) { + categories = categoryRepository.searchCategories(query, pageable); + } else { + categories = categoryRepository.findAll(pageable); + } + return categories.map(this::mapToResponse); + } + + public CategoryResponse getCategoryById(Long id) { + Category category = categoryRepository.findById(id) + .orElseThrow(() -> new ResourceNotFoundException("Category not found with id: " + id)); + return mapToResponse(category); + } + + @Transactional + public CategoryResponse createCategory(CategoryRequest request) { + Category category = new Category(); + category.setCategoryName(request.getCategoryName()); + category.setCategoryDescription(request.getCategoryDescription()); + + category = categoryRepository.save(category); + return mapToResponse(category); + } + + @Transactional + public CategoryResponse updateCategory(Long id, CategoryRequest request) { + Category category = categoryRepository.findById(id) + .orElseThrow(() -> new ResourceNotFoundException("Category not found with id: " + id)); + + category.setCategoryName(request.getCategoryName()); + category.setCategoryDescription(request.getCategoryDescription()); + + category = categoryRepository.save(category); + return mapToResponse(category); + } + + @Transactional + public void deleteCategory(Long id) { + if (!categoryRepository.existsById(id)) { + throw new ResourceNotFoundException("Category not found with id: " + id); + } + categoryRepository.deleteById(id); + } + + @Transactional + public void bulkDeleteCategories(BulkDeleteRequest request) { + categoryRepository.deleteAllById(request.getIds()); + } + + private CategoryResponse mapToResponse(Category category) { + return new CategoryResponse( + category.getId(), + category.getCategoryName(), + category.getCategoryDescription(), + category.getCreatedAt(), + category.getUpdatedAt() + ); + } +} diff --git a/backend/src/main/java/com/petshop/backend/service/CustomerService.java b/backend/src/main/java/com/petshop/backend/service/CustomerService.java new file mode 100644 index 00000000..fca95ad8 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/service/CustomerService.java @@ -0,0 +1,87 @@ +package com.petshop.backend.service; + +import com.petshop.backend.dto.common.BulkDeleteRequest; +import com.petshop.backend.dto.customer.CustomerRequest; +import com.petshop.backend.dto.customer.CustomerResponse; +import com.petshop.backend.entity.Customer; +import com.petshop.backend.exception.ResourceNotFoundException; +import com.petshop.backend.repository.CustomerRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class CustomerService { + + private final CustomerRepository customerRepository; + + public Page getAllCustomers(String query, Pageable pageable) { + Page customers; + if (query != null && !query.trim().isEmpty()) { + customers = customerRepository.searchCustomers(query, pageable); + } else { + customers = customerRepository.findAll(pageable); + } + return customers.map(this::mapToResponse); + } + + public CustomerResponse getCustomerById(Long id) { + Customer customer = customerRepository.findById(id) + .orElseThrow(() -> new ResourceNotFoundException("Customer not found with id: " + id)); + return mapToResponse(customer); + } + + @Transactional + public CustomerResponse createCustomer(CustomerRequest request) { + Customer customer = new Customer(); + customer.setCustomerName(request.getCustomerName()); + customer.setCustomerEmail(request.getCustomerEmail()); + customer.setCustomerPhone(request.getCustomerPhone()); + customer.setCustomerAddress(request.getCustomerAddress()); + + customer = customerRepository.save(customer); + return mapToResponse(customer); + } + + @Transactional + public CustomerResponse updateCustomer(Long id, CustomerRequest request) { + Customer customer = customerRepository.findById(id) + .orElseThrow(() -> new ResourceNotFoundException("Customer not found with id: " + id)); + + customer.setCustomerName(request.getCustomerName()); + customer.setCustomerEmail(request.getCustomerEmail()); + customer.setCustomerPhone(request.getCustomerPhone()); + customer.setCustomerAddress(request.getCustomerAddress()); + + customer = customerRepository.save(customer); + return mapToResponse(customer); + } + + @Transactional + public void deleteCustomer(Long id) { + if (!customerRepository.existsById(id)) { + throw new ResourceNotFoundException("Customer not found with id: " + id); + } + customerRepository.deleteById(id); + } + + @Transactional + public void bulkDeleteCustomers(BulkDeleteRequest request) { + customerRepository.deleteAllById(request.getIds()); + } + + private CustomerResponse mapToResponse(Customer customer) { + return new CustomerResponse( + customer.getId(), + customer.getCustomerName(), + customer.getCustomerEmail(), + customer.getCustomerPhone(), + customer.getCustomerAddress(), + customer.getCreatedAt(), + customer.getUpdatedAt() + ); + } +} diff --git a/backend/src/main/java/com/petshop/backend/service/PetService.java b/backend/src/main/java/com/petshop/backend/service/PetService.java new file mode 100644 index 00000000..9d87cde2 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/service/PetService.java @@ -0,0 +1,93 @@ +package com.petshop.backend.service; + +import com.petshop.backend.dto.common.BulkDeleteRequest; +import com.petshop.backend.dto.pet.PetRequest; +import com.petshop.backend.dto.pet.PetResponse; +import com.petshop.backend.entity.Pet; +import com.petshop.backend.exception.ResourceNotFoundException; +import com.petshop.backend.repository.PetRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class PetService { + + private final PetRepository petRepository; + + public Page getAllPets(String query, Pageable pageable) { + Page pets; + if (query != null && !query.trim().isEmpty()) { + pets = petRepository.searchPets(query, pageable); + } else { + pets = petRepository.findAll(pageable); + } + return pets.map(this::mapToResponse); + } + + public PetResponse getPetById(Long id) { + Pet pet = petRepository.findById(id) + .orElseThrow(() -> new ResourceNotFoundException("Pet not found with id: " + id)); + return mapToResponse(pet); + } + + @Transactional + public PetResponse createPet(PetRequest request) { + Pet pet = new Pet(); + pet.setPetName(request.getPetName()); + pet.setPetSpecies(request.getPetSpecies()); + pet.setPetBreed(request.getPetBreed()); + pet.setPetAge(request.getPetAge()); + pet.setPetStatus(request.getPetStatus()); + pet.setPetPrice(request.getPetPrice()); + + pet = petRepository.save(pet); + return mapToResponse(pet); + } + + @Transactional + public PetResponse updatePet(Long id, PetRequest request) { + Pet pet = petRepository.findById(id) + .orElseThrow(() -> new ResourceNotFoundException("Pet not found with id: " + id)); + + pet.setPetName(request.getPetName()); + pet.setPetSpecies(request.getPetSpecies()); + pet.setPetBreed(request.getPetBreed()); + pet.setPetAge(request.getPetAge()); + pet.setPetStatus(request.getPetStatus()); + pet.setPetPrice(request.getPetPrice()); + + pet = petRepository.save(pet); + return mapToResponse(pet); + } + + @Transactional + public void deletePet(Long id) { + if (!petRepository.existsById(id)) { + throw new ResourceNotFoundException("Pet not found with id: " + id); + } + petRepository.deleteById(id); + } + + @Transactional + public void bulkDeletePets(BulkDeleteRequest request) { + petRepository.deleteAllById(request.getIds()); + } + + private PetResponse mapToResponse(Pet pet) { + return new PetResponse( + pet.getId(), + pet.getPetName(), + pet.getPetSpecies(), + pet.getPetBreed(), + pet.getPetAge(), + pet.getPetStatus() != null ? pet.getPetStatus().toString() : null, + pet.getPetPrice(), + pet.getCreatedAt(), + pet.getUpdatedAt() + ); + } +} diff --git a/backend/src/main/java/com/petshop/backend/service/ProductService.java b/backend/src/main/java/com/petshop/backend/service/ProductService.java new file mode 100644 index 00000000..150d676d --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/service/ProductService.java @@ -0,0 +1,100 @@ +package com.petshop.backend.service; + +import com.petshop.backend.dto.common.BulkDeleteRequest; +import com.petshop.backend.dto.product.ProductRequest; +import com.petshop.backend.dto.product.ProductResponse; +import com.petshop.backend.entity.Category; +import com.petshop.backend.entity.Product; +import com.petshop.backend.exception.ResourceNotFoundException; +import com.petshop.backend.repository.CategoryRepository; +import com.petshop.backend.repository.ProductRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class ProductService { + + private final ProductRepository productRepository; + private final CategoryRepository categoryRepository; + + public Page getAllProducts(String query, Pageable pageable) { + Page products; + if (query != null && !query.trim().isEmpty()) { + products = productRepository.searchProducts(query, pageable); + } else { + products = productRepository.findAll(pageable); + } + return products.map(this::mapToResponse); + } + + public ProductResponse getProductById(Long id) { + Product product = productRepository.findById(id) + .orElseThrow(() -> new ResourceNotFoundException("Product not found with id: " + id)); + return mapToResponse(product); + } + + @Transactional + public ProductResponse createProduct(ProductRequest request) { + Category category = categoryRepository.findById(request.getCategoryId()) + .orElseThrow(() -> new ResourceNotFoundException("Category not found with id: " + request.getCategoryId())); + + Product product = new Product(); + product.setProductName(request.getProductName()); + product.setCategory(category); + product.setProductDescription(request.getProductDescription()); + product.setProductPrice(request.getProductPrice()); + product.setActive(request.getActive() != null ? request.getActive() : true); + + product = productRepository.save(product); + return mapToResponse(product); + } + + @Transactional + public ProductResponse updateProduct(Long id, ProductRequest request) { + Product product = productRepository.findById(id) + .orElseThrow(() -> new ResourceNotFoundException("Product not found with id: " + id)); + + Category category = categoryRepository.findById(request.getCategoryId()) + .orElseThrow(() -> new ResourceNotFoundException("Category not found with id: " + request.getCategoryId())); + + product.setProductName(request.getProductName()); + product.setCategory(category); + product.setProductDescription(request.getProductDescription()); + product.setProductPrice(request.getProductPrice()); + product.setActive(request.getActive() != null ? request.getActive() : true); + + product = productRepository.save(product); + return mapToResponse(product); + } + + @Transactional + public void deleteProduct(Long id) { + if (!productRepository.existsById(id)) { + throw new ResourceNotFoundException("Product not found with id: " + id); + } + productRepository.deleteById(id); + } + + @Transactional + public void bulkDeleteProducts(BulkDeleteRequest request) { + productRepository.deleteAllById(request.getIds()); + } + + private ProductResponse mapToResponse(Product product) { + return new ProductResponse( + product.getId(), + product.getProductName(), + product.getCategory().getId(), + product.getCategory().getCategoryName(), + product.getProductDescription(), + product.getProductPrice(), + product.getActive(), + product.getCreatedAt(), + product.getUpdatedAt() + ); + } +} diff --git a/backend/src/main/java/com/petshop/backend/service/ServiceService.java b/backend/src/main/java/com/petshop/backend/service/ServiceService.java new file mode 100644 index 00000000..15faea62 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/service/ServiceService.java @@ -0,0 +1,89 @@ +package com.petshop.backend.service; + +import com.petshop.backend.dto.common.BulkDeleteRequest; +import com.petshop.backend.dto.service.ServiceRequest; +import com.petshop.backend.dto.service.ServiceResponse; +import com.petshop.backend.exception.ResourceNotFoundException; +import com.petshop.backend.repository.ServiceRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class ServiceService { + + private final ServiceRepository serviceRepository; + + public Page getAllServices(String query, Pageable pageable) { + Page services; + if (query != null && !query.trim().isEmpty()) { + services = serviceRepository.searchServices(query, pageable); + } else { + services = serviceRepository.findAll(pageable); + } + return services.map(this::mapToResponse); + } + + public ServiceResponse getServiceById(Long id) { + com.petshop.backend.entity.Service service = serviceRepository.findById(id) + .orElseThrow(() -> new ResourceNotFoundException("Service not found with id: " + id)); + return mapToResponse(service); + } + + @Transactional + public ServiceResponse createService(ServiceRequest request) { + com.petshop.backend.entity.Service service = new com.petshop.backend.entity.Service(); + service.setServiceName(request.getServiceName()); + service.setServiceDescription(request.getServiceDescription()); + service.setServicePrice(request.getServicePrice()); + service.setServiceDurationMinutes(request.getServiceDurationMinutes()); + service.setActive(request.getActive() != null ? request.getActive() : true); + + service = serviceRepository.save(service); + return mapToResponse(service); + } + + @Transactional + public ServiceResponse updateService(Long id, ServiceRequest request) { + com.petshop.backend.entity.Service service = serviceRepository.findById(id) + .orElseThrow(() -> new ResourceNotFoundException("Service not found with id: " + id)); + + service.setServiceName(request.getServiceName()); + service.setServiceDescription(request.getServiceDescription()); + service.setServicePrice(request.getServicePrice()); + service.setServiceDurationMinutes(request.getServiceDurationMinutes()); + service.setActive(request.getActive() != null ? request.getActive() : true); + + service = serviceRepository.save(service); + return mapToResponse(service); + } + + @Transactional + public void deleteService(Long id) { + if (!serviceRepository.existsById(id)) { + throw new ResourceNotFoundException("Service not found with id: " + id); + } + serviceRepository.deleteById(id); + } + + @Transactional + public void bulkDeleteServices(BulkDeleteRequest request) { + serviceRepository.deleteAllById(request.getIds()); + } + + private ServiceResponse mapToResponse(com.petshop.backend.entity.Service service) { + return new ServiceResponse( + service.getId(), + service.getServiceName(), + service.getServiceDescription(), + service.getServicePrice(), + service.getServiceDurationMinutes(), + service.getActive(), + service.getCreatedAt(), + service.getUpdatedAt() + ); + } +} From 883d1d6baa7d3e54de4bc1b013109b210fa47b91 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Wed, 4 Mar 2026 17:14:42 -0700 Subject: [PATCH 06/84] Add admin services and controllers --- .../controller/DropdownController.java | 91 +++++++++++++++ .../controller/InventoryController.java | 57 ++++++++++ .../controller/ProductSupplierController.java | 62 ++++++++++ .../controller/PurchaseOrderController.java | 29 +++++ .../controller/SupplierController.java | 59 ++++++++++ .../backend/controller/UserController.java | 57 ++++++++++ .../productsupplier/ProductSupplierKey.java | 2 +- .../purchaseorder/PurchaseOrderResponse.java | 2 +- .../backend/service/InventoryService.java | 107 ++++++++++++++++++ .../service/ProductSupplierService.java | 105 +++++++++++++++++ .../backend/service/PurchaseOrderService.java | 63 +++++++++++ .../backend/service/SupplierService.java | 93 +++++++++++++++ .../petshop/backend/service/UserService.java | 90 +++++++++++++++ 13 files changed, 815 insertions(+), 2 deletions(-) create mode 100644 backend/src/main/java/com/petshop/backend/controller/DropdownController.java create mode 100644 backend/src/main/java/com/petshop/backend/controller/InventoryController.java create mode 100644 backend/src/main/java/com/petshop/backend/controller/ProductSupplierController.java create mode 100644 backend/src/main/java/com/petshop/backend/controller/PurchaseOrderController.java create mode 100644 backend/src/main/java/com/petshop/backend/controller/SupplierController.java create mode 100644 backend/src/main/java/com/petshop/backend/controller/UserController.java create mode 100644 backend/src/main/java/com/petshop/backend/service/InventoryService.java create mode 100644 backend/src/main/java/com/petshop/backend/service/ProductSupplierService.java create mode 100644 backend/src/main/java/com/petshop/backend/service/PurchaseOrderService.java create mode 100644 backend/src/main/java/com/petshop/backend/service/SupplierService.java create mode 100644 backend/src/main/java/com/petshop/backend/service/UserService.java diff --git a/backend/src/main/java/com/petshop/backend/controller/DropdownController.java b/backend/src/main/java/com/petshop/backend/controller/DropdownController.java new file mode 100644 index 00000000..73ba3cba --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/controller/DropdownController.java @@ -0,0 +1,91 @@ +package com.petshop.backend.controller; + +import com.petshop.backend.dto.common.DropdownOption; +import com.petshop.backend.repository.*; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; +import java.util.stream.Collectors; + +@RestController +@RequestMapping("/api/v1/dropdowns") +@RequiredArgsConstructor +public class DropdownController { + + private final PetRepository petRepository; + private final CustomerRepository customerRepository; + private final ServiceRepository serviceRepository; + private final ProductRepository productRepository; + private final CategoryRepository categoryRepository; + private final StoreRepository storeRepository; + private final SupplierRepository supplierRepository; + + @GetMapping("/pets") + public ResponseEntity> getPets() { + return ResponseEntity.ok( + petRepository.findAll().stream() + .map(p -> new DropdownOption(p.getId(), p.getPetName())) + .collect(Collectors.toList()) + ); + } + + @GetMapping("/customers") + public ResponseEntity> getCustomers() { + return ResponseEntity.ok( + customerRepository.findAll().stream() + .map(c -> new DropdownOption(c.getId(), c.getCustomerName())) + .collect(Collectors.toList()) + ); + } + + @GetMapping("/services") + public ResponseEntity> getServices() { + return ResponseEntity.ok( + serviceRepository.findAll().stream() + .map(s -> new DropdownOption(s.getId(), s.getServiceName())) + .collect(Collectors.toList()) + ); + } + + @GetMapping("/products") + public ResponseEntity> getProducts() { + return ResponseEntity.ok( + productRepository.findAll().stream() + .map(p -> new DropdownOption(p.getId(), p.getProductName())) + .collect(Collectors.toList()) + ); + } + + @GetMapping("/categories") + public ResponseEntity> getCategories() { + return ResponseEntity.ok( + categoryRepository.findAll().stream() + .map(c -> new DropdownOption(c.getId(), c.getCategoryName())) + .collect(Collectors.toList()) + ); + } + + @GetMapping("/stores") + public ResponseEntity> getStores() { + return ResponseEntity.ok( + storeRepository.findAll().stream() + .map(s -> new DropdownOption(s.getId(), s.getStoreName())) + .collect(Collectors.toList()) + ); + } + + @GetMapping("/suppliers") + @PreAuthorize("hasRole('ADMIN')") + public ResponseEntity> getSuppliers() { + return ResponseEntity.ok( + supplierRepository.findAll().stream() + .map(s -> new DropdownOption(s.getId(), s.getSupplierName())) + .collect(Collectors.toList()) + ); + } +} diff --git a/backend/src/main/java/com/petshop/backend/controller/InventoryController.java b/backend/src/main/java/com/petshop/backend/controller/InventoryController.java new file mode 100644 index 00000000..61ac8df2 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/controller/InventoryController.java @@ -0,0 +1,57 @@ +package com.petshop.backend.controller; + +import com.petshop.backend.dto.common.BulkDeleteRequest; +import com.petshop.backend.dto.inventory.InventoryRequest; +import com.petshop.backend.dto.inventory.InventoryResponse; +import com.petshop.backend.service.InventoryService; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/v1/inventory") +@RequiredArgsConstructor +@PreAuthorize("hasRole('ADMIN')") +public class InventoryController { + + private final InventoryService inventoryService; + + @GetMapping + public ResponseEntity> getAllInventory(Pageable pageable) { + return ResponseEntity.ok(inventoryService.getAllInventory(pageable)); + } + + @GetMapping("/{id}") + public ResponseEntity getInventoryById(@PathVariable Long id) { + return ResponseEntity.ok(inventoryService.getInventoryById(id)); + } + + @PostMapping + public ResponseEntity createInventory(@Valid @RequestBody InventoryRequest request) { + return ResponseEntity.status(HttpStatus.CREATED).body(inventoryService.createInventory(request)); + } + + @PutMapping("/{id}") + public ResponseEntity updateInventory( + @PathVariable Long id, + @Valid @RequestBody InventoryRequest request) { + return ResponseEntity.ok(inventoryService.updateInventory(id, request)); + } + + @DeleteMapping("/{id}") + public ResponseEntity deleteInventory(@PathVariable Long id) { + inventoryService.deleteInventory(id); + return ResponseEntity.noContent().build(); + } + + @PostMapping("/bulk-delete") + public ResponseEntity bulkDeleteInventory(@Valid @RequestBody BulkDeleteRequest request) { + inventoryService.bulkDeleteInventory(request); + return ResponseEntity.noContent().build(); + } +} diff --git a/backend/src/main/java/com/petshop/backend/controller/ProductSupplierController.java b/backend/src/main/java/com/petshop/backend/controller/ProductSupplierController.java new file mode 100644 index 00000000..20965a3d --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/controller/ProductSupplierController.java @@ -0,0 +1,62 @@ +package com.petshop.backend.controller; + +import com.petshop.backend.dto.productsupplier.BulkDeleteProductSupplierRequest; +import com.petshop.backend.dto.productsupplier.ProductSupplierRequest; +import com.petshop.backend.dto.productsupplier.ProductSupplierResponse; +import com.petshop.backend.service.ProductSupplierService; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/v1/product-suppliers") +@RequiredArgsConstructor +@PreAuthorize("hasRole('ADMIN')") +public class ProductSupplierController { + + private final ProductSupplierService productSupplierService; + + @GetMapping + public ResponseEntity> getAllProductSuppliers(Pageable pageable) { + return ResponseEntity.ok(productSupplierService.getAllProductSuppliers(pageable)); + } + + @GetMapping("/{productId}/{supplierId}") + public ResponseEntity getProductSupplierById( + @PathVariable Long productId, + @PathVariable Long supplierId) { + return ResponseEntity.ok(productSupplierService.getProductSupplierById(productId, supplierId)); + } + + @PostMapping + public ResponseEntity createProductSupplier(@Valid @RequestBody ProductSupplierRequest request) { + return ResponseEntity.status(HttpStatus.CREATED).body(productSupplierService.createProductSupplier(request)); + } + + @PutMapping("/{productId}/{supplierId}") + public ResponseEntity updateProductSupplier( + @PathVariable Long productId, + @PathVariable Long supplierId, + @Valid @RequestBody ProductSupplierRequest request) { + return ResponseEntity.ok(productSupplierService.updateProductSupplier(productId, supplierId, request)); + } + + @DeleteMapping("/{productId}/{supplierId}") + public ResponseEntity deleteProductSupplier( + @PathVariable Long productId, + @PathVariable Long supplierId) { + productSupplierService.deleteProductSupplier(productId, supplierId); + return ResponseEntity.noContent().build(); + } + + @PostMapping("/bulk-delete") + public ResponseEntity bulkDeleteProductSuppliers(@Valid @RequestBody BulkDeleteProductSupplierRequest request) { + productSupplierService.bulkDeleteProductSuppliers(request); + return ResponseEntity.noContent().build(); + } +} diff --git a/backend/src/main/java/com/petshop/backend/controller/PurchaseOrderController.java b/backend/src/main/java/com/petshop/backend/controller/PurchaseOrderController.java new file mode 100644 index 00000000..fb0a8077 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/controller/PurchaseOrderController.java @@ -0,0 +1,29 @@ +package com.petshop.backend.controller; + +import com.petshop.backend.dto.purchaseorder.PurchaseOrderResponse; +import com.petshop.backend.service.PurchaseOrderService; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/v1/purchase-orders") +@RequiredArgsConstructor +@PreAuthorize("hasRole('ADMIN')") +public class PurchaseOrderController { + + private final PurchaseOrderService purchaseOrderService; + + @GetMapping + public ResponseEntity> getAllPurchaseOrders(Pageable pageable) { + return ResponseEntity.ok(purchaseOrderService.getAllPurchaseOrders(pageable)); + } + + @GetMapping("/{id}") + public ResponseEntity getPurchaseOrderById(@PathVariable Long id) { + return ResponseEntity.ok(purchaseOrderService.getPurchaseOrderById(id)); + } +} diff --git a/backend/src/main/java/com/petshop/backend/controller/SupplierController.java b/backend/src/main/java/com/petshop/backend/controller/SupplierController.java new file mode 100644 index 00000000..ebd4e68d --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/controller/SupplierController.java @@ -0,0 +1,59 @@ +package com.petshop.backend.controller; + +import com.petshop.backend.dto.common.BulkDeleteRequest; +import com.petshop.backend.dto.supplier.SupplierRequest; +import com.petshop.backend.dto.supplier.SupplierResponse; +import com.petshop.backend.service.SupplierService; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/v1/suppliers") +@RequiredArgsConstructor +@PreAuthorize("hasRole('ADMIN')") +public class SupplierController { + + private final SupplierService supplierService; + + @GetMapping + public ResponseEntity> getAllSuppliers( + @RequestParam(required = false) String q, + Pageable pageable) { + return ResponseEntity.ok(supplierService.getAllSuppliers(q, pageable)); + } + + @GetMapping("/{id}") + public ResponseEntity getSupplierById(@PathVariable Long id) { + return ResponseEntity.ok(supplierService.getSupplierById(id)); + } + + @PostMapping + public ResponseEntity createSupplier(@Valid @RequestBody SupplierRequest request) { + return ResponseEntity.status(HttpStatus.CREATED).body(supplierService.createSupplier(request)); + } + + @PutMapping("/{id}") + public ResponseEntity updateSupplier( + @PathVariable Long id, + @Valid @RequestBody SupplierRequest request) { + return ResponseEntity.ok(supplierService.updateSupplier(id, request)); + } + + @DeleteMapping("/{id}") + public ResponseEntity deleteSupplier(@PathVariable Long id) { + supplierService.deleteSupplier(id); + return ResponseEntity.noContent().build(); + } + + @PostMapping("/bulk-delete") + public ResponseEntity bulkDeleteSuppliers(@Valid @RequestBody BulkDeleteRequest request) { + supplierService.bulkDeleteSuppliers(request); + return ResponseEntity.noContent().build(); + } +} diff --git a/backend/src/main/java/com/petshop/backend/controller/UserController.java b/backend/src/main/java/com/petshop/backend/controller/UserController.java new file mode 100644 index 00000000..c316dbe0 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/controller/UserController.java @@ -0,0 +1,57 @@ +package com.petshop.backend.controller; + +import com.petshop.backend.dto.common.BulkDeleteRequest; +import com.petshop.backend.dto.user.UserRequest; +import com.petshop.backend.dto.user.UserResponse; +import com.petshop.backend.service.UserService; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/v1/users") +@RequiredArgsConstructor +@PreAuthorize("hasRole('ADMIN')") +public class UserController { + + private final UserService userService; + + @GetMapping + public ResponseEntity> getAllUsers(Pageable pageable) { + return ResponseEntity.ok(userService.getAllUsers(pageable)); + } + + @GetMapping("/{id}") + public ResponseEntity getUserById(@PathVariable Long id) { + return ResponseEntity.ok(userService.getUserById(id)); + } + + @PostMapping + public ResponseEntity createUser(@Valid @RequestBody UserRequest request) { + return ResponseEntity.status(HttpStatus.CREATED).body(userService.createUser(request)); + } + + @PutMapping("/{id}") + public ResponseEntity updateUser( + @PathVariable Long id, + @Valid @RequestBody UserRequest request) { + return ResponseEntity.ok(userService.updateUser(id, request)); + } + + @DeleteMapping("/{id}") + public ResponseEntity deleteUser(@PathVariable Long id) { + userService.deleteUser(id); + return ResponseEntity.noContent().build(); + } + + @PostMapping("/bulk-delete") + public ResponseEntity bulkDeleteUsers(@Valid @RequestBody BulkDeleteRequest request) { + userService.bulkDeleteUsers(request); + return ResponseEntity.noContent().build(); + } +} diff --git a/backend/src/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierKey.java b/backend/src/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierKey.java index 191c8575..b86b6980 100644 --- a/backend/src/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierKey.java +++ b/backend/src/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierKey.java @@ -12,7 +12,7 @@ public class ProductSupplierKey { } @Data -class BulkDeleteProductSupplierRequest { +public class BulkDeleteProductSupplierRequest { @NotEmpty(message = "Keys list cannot be empty") private List keys; } diff --git a/backend/src/main/java/com/petshop/backend/dto/purchaseorder/PurchaseOrderResponse.java b/backend/src/main/java/com/petshop/backend/dto/purchaseorder/PurchaseOrderResponse.java index 657816ea..4a67865c 100644 --- a/backend/src/main/java/com/petshop/backend/dto/purchaseorder/PurchaseOrderResponse.java +++ b/backend/src/main/java/com/petshop/backend/dto/purchaseorder/PurchaseOrderResponse.java @@ -29,7 +29,7 @@ public class PurchaseOrderResponse { @Data @NoArgsConstructor @AllArgsConstructor -class PurchaseOrderItemResponse { +public class PurchaseOrderItemResponse { private Long id; private Long productId; private String productName; diff --git a/backend/src/main/java/com/petshop/backend/service/InventoryService.java b/backend/src/main/java/com/petshop/backend/service/InventoryService.java new file mode 100644 index 00000000..0599e255 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/service/InventoryService.java @@ -0,0 +1,107 @@ +package com.petshop.backend.service; + +import com.petshop.backend.dto.common.BulkDeleteRequest; +import com.petshop.backend.dto.inventory.InventoryRequest; +import com.petshop.backend.dto.inventory.InventoryResponse; +import com.petshop.backend.entity.Inventory; +import com.petshop.backend.entity.Product; +import com.petshop.backend.entity.Store; +import com.petshop.backend.exception.ResourceNotFoundException; +import com.petshop.backend.repository.InventoryRepository; +import com.petshop.backend.repository.ProductRepository; +import com.petshop.backend.repository.StoreRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; + +@Service +@RequiredArgsConstructor +public class InventoryService { + + private final InventoryRepository inventoryRepository; + private final ProductRepository productRepository; + private final StoreRepository storeRepository; + + public Page getAllInventory(Pageable pageable) { + return inventoryRepository.findAll(pageable).map(this::mapToResponse); + } + + public InventoryResponse getInventoryById(Long id) { + Inventory inventory = inventoryRepository.findById(id) + .orElseThrow(() -> new ResourceNotFoundException("Inventory not found with id: " + id)); + return mapToResponse(inventory); + } + + @Transactional + public InventoryResponse createInventory(InventoryRequest request) { + Product product = productRepository.findById(request.getProductId()) + .orElseThrow(() -> new ResourceNotFoundException("Product not found with id: " + request.getProductId())); + + Store store = storeRepository.findById(request.getStoreId()) + .orElseThrow(() -> new ResourceNotFoundException("Store not found with id: " + request.getStoreId())); + + Inventory inventory = new Inventory(); + inventory.setProduct(product); + inventory.setStore(store); + inventory.setQuantity(request.getQuantity()); + inventory.setReorderLevel(request.getReorderLevel()); + inventory.setLastRestocked(LocalDateTime.now()); + + inventory = inventoryRepository.save(inventory); + return mapToResponse(inventory); + } + + @Transactional + public InventoryResponse updateInventory(Long id, InventoryRequest request) { + Inventory inventory = inventoryRepository.findById(id) + .orElseThrow(() -> new ResourceNotFoundException("Inventory not found with id: " + id)); + + Product product = productRepository.findById(request.getProductId()) + .orElseThrow(() -> new ResourceNotFoundException("Product not found with id: " + request.getProductId())); + + Store store = storeRepository.findById(request.getStoreId()) + .orElseThrow(() -> new ResourceNotFoundException("Store not found with id: " + request.getStoreId())); + + inventory.setProduct(product); + inventory.setStore(store); + inventory.setQuantity(request.getQuantity()); + inventory.setReorderLevel(request.getReorderLevel()); + inventory.setLastRestocked(LocalDateTime.now()); + + inventory = inventoryRepository.save(inventory); + return mapToResponse(inventory); + } + + @Transactional + public void deleteInventory(Long id) { + if (!inventoryRepository.existsById(id)) { + throw new ResourceNotFoundException("Inventory not found with id: " + id); + } + inventoryRepository.deleteById(id); + } + + @Transactional + public void bulkDeleteInventory(BulkDeleteRequest request) { + inventoryRepository.deleteAllById(request.getIds()); + } + + private InventoryResponse mapToResponse(Inventory inventory) { + return new InventoryResponse( + inventory.getId(), + inventory.getProduct().getId(), + inventory.getProduct().getProductName(), + inventory.getProduct().getCategory().getCategoryName(), + inventory.getStore().getId(), + inventory.getStore().getStoreName(), + inventory.getQuantity(), + inventory.getReorderLevel(), + inventory.getLastRestocked(), + inventory.getCreatedAt(), + inventory.getUpdatedAt() + ); + } +} diff --git a/backend/src/main/java/com/petshop/backend/service/ProductSupplierService.java b/backend/src/main/java/com/petshop/backend/service/ProductSupplierService.java new file mode 100644 index 00000000..20c0bb66 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/service/ProductSupplierService.java @@ -0,0 +1,105 @@ +package com.petshop.backend.service; + +import com.petshop.backend.dto.productsupplier.BulkDeleteProductSupplierRequest; +import com.petshop.backend.dto.productsupplier.ProductSupplierRequest; +import com.petshop.backend.dto.productsupplier.ProductSupplierResponse; +import com.petshop.backend.entity.Product; +import com.petshop.backend.entity.ProductSupplier; +import com.petshop.backend.entity.Supplier; +import com.petshop.backend.exception.ResourceNotFoundException; +import com.petshop.backend.repository.ProductRepository; +import com.petshop.backend.repository.ProductSupplierRepository; +import com.petshop.backend.repository.SupplierRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class ProductSupplierService { + + private final ProductSupplierRepository productSupplierRepository; + private final ProductRepository productRepository; + private final SupplierRepository supplierRepository; + + public Page getAllProductSuppliers(Pageable pageable) { + return productSupplierRepository.findAll(pageable).map(this::mapToResponse); + } + + public ProductSupplierResponse getProductSupplierById(Long productId, Long supplierId) { + ProductSupplier.ProductSupplierId id = new ProductSupplier.ProductSupplierId(productId, supplierId); + ProductSupplier productSupplier = productSupplierRepository.findById(id) + .orElseThrow(() -> new ResourceNotFoundException( + "ProductSupplier not found with productId: " + productId + " and supplierId: " + supplierId)); + return mapToResponse(productSupplier); + } + + @Transactional + public ProductSupplierResponse createProductSupplier(ProductSupplierRequest request) { + Product product = productRepository.findById(request.getProductId()) + .orElseThrow(() -> new ResourceNotFoundException("Product not found with id: " + request.getProductId())); + + Supplier supplier = supplierRepository.findById(request.getSupplierId()) + .orElseThrow(() -> new ResourceNotFoundException("Supplier not found with id: " + request.getSupplierId())); + + ProductSupplier productSupplier = new ProductSupplier(); + productSupplier.setProduct(product); + productSupplier.setSupplier(supplier); + productSupplier.setCostPrice(request.getCostPrice()); + productSupplier.setLeadTimeDays(request.getLeadTimeDays()); + productSupplier.setIsPreferred(request.getIsPreferred()); + + productSupplier = productSupplierRepository.save(productSupplier); + return mapToResponse(productSupplier); + } + + @Transactional + public ProductSupplierResponse updateProductSupplier(Long productId, Long supplierId, ProductSupplierRequest request) { + ProductSupplier.ProductSupplierId id = new ProductSupplier.ProductSupplierId(productId, supplierId); + ProductSupplier productSupplier = productSupplierRepository.findById(id) + .orElseThrow(() -> new ResourceNotFoundException( + "ProductSupplier not found with productId: " + productId + " and supplierId: " + supplierId)); + + productSupplier.setCostPrice(request.getCostPrice()); + productSupplier.setLeadTimeDays(request.getLeadTimeDays()); + productSupplier.setIsPreferred(request.getIsPreferred()); + + productSupplier = productSupplierRepository.save(productSupplier); + return mapToResponse(productSupplier); + } + + @Transactional + public void deleteProductSupplier(Long productId, Long supplierId) { + ProductSupplier.ProductSupplierId id = new ProductSupplier.ProductSupplierId(productId, supplierId); + if (!productSupplierRepository.existsById(id)) { + throw new ResourceNotFoundException( + "ProductSupplier not found with productId: " + productId + " and supplierId: " + supplierId); + } + productSupplierRepository.deleteById(id); + } + + @Transactional + public void bulkDeleteProductSuppliers(BulkDeleteProductSupplierRequest request) { + request.getKeys().forEach(key -> { + ProductSupplier.ProductSupplierId id = new ProductSupplier.ProductSupplierId( + key.getProductId(), key.getSupplierId()); + productSupplierRepository.deleteById(id); + }); + } + + private ProductSupplierResponse mapToResponse(ProductSupplier productSupplier) { + return new ProductSupplierResponse( + productSupplier.getProduct().getId(), + productSupplier.getProduct().getProductName(), + productSupplier.getSupplier().getId(), + productSupplier.getSupplier().getSupplierName(), + productSupplier.getCostPrice(), + productSupplier.getLeadTimeDays(), + productSupplier.getIsPreferred(), + productSupplier.getCreatedAt(), + productSupplier.getUpdatedAt() + ); + } +} diff --git a/backend/src/main/java/com/petshop/backend/service/PurchaseOrderService.java b/backend/src/main/java/com/petshop/backend/service/PurchaseOrderService.java new file mode 100644 index 00000000..1e9c61f0 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/service/PurchaseOrderService.java @@ -0,0 +1,63 @@ +package com.petshop.backend.service; + +import com.petshop.backend.dto.purchaseorder.PurchaseOrderItemResponse; +import com.petshop.backend.dto.purchaseorder.PurchaseOrderResponse; +import com.petshop.backend.entity.PurchaseOrder; +import com.petshop.backend.entity.PurchaseOrderItem; +import com.petshop.backend.exception.ResourceNotFoundException; +import com.petshop.backend.repository.PurchaseOrderRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class PurchaseOrderService { + + private final PurchaseOrderRepository purchaseOrderRepository; + + public Page getAllPurchaseOrders(Pageable pageable) { + return purchaseOrderRepository.findAll(pageable).map(this::mapToResponse); + } + + public PurchaseOrderResponse getPurchaseOrderById(Long id) { + PurchaseOrder purchaseOrder = purchaseOrderRepository.findById(id) + .orElseThrow(() -> new ResourceNotFoundException("PurchaseOrder not found with id: " + id)); + return mapToResponse(purchaseOrder); + } + + private PurchaseOrderResponse mapToResponse(PurchaseOrder purchaseOrder) { + List items = purchaseOrder.getItems().stream() + .map(this::mapItemToResponse) + .collect(Collectors.toList()); + + return new PurchaseOrderResponse( + purchaseOrder.getId(), + purchaseOrder.getSupplier().getId(), + purchaseOrder.getSupplier().getSupplierName(), + purchaseOrder.getOrderDate(), + purchaseOrder.getExpectedDelivery(), + purchaseOrder.getStatus().toString(), + purchaseOrder.getTotalAmount(), + purchaseOrder.getNotes(), + items, + purchaseOrder.getCreatedAt(), + purchaseOrder.getUpdatedAt() + ); + } + + private PurchaseOrderItemResponse mapItemToResponse(PurchaseOrderItem item) { + return new PurchaseOrderItemResponse( + item.getId(), + item.getProduct().getId(), + item.getProduct().getProductName(), + item.getQuantity(), + item.getUnitCost(), + item.getSubtotal() + ); + } +} diff --git a/backend/src/main/java/com/petshop/backend/service/SupplierService.java b/backend/src/main/java/com/petshop/backend/service/SupplierService.java new file mode 100644 index 00000000..d86a8c13 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/service/SupplierService.java @@ -0,0 +1,93 @@ +package com.petshop.backend.service; + +import com.petshop.backend.dto.common.BulkDeleteRequest; +import com.petshop.backend.dto.supplier.SupplierRequest; +import com.petshop.backend.dto.supplier.SupplierResponse; +import com.petshop.backend.entity.Supplier; +import com.petshop.backend.exception.ResourceNotFoundException; +import com.petshop.backend.repository.SupplierRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class SupplierService { + + private final SupplierRepository supplierRepository; + + public Page getAllSuppliers(String query, Pageable pageable) { + Page suppliers; + if (query != null && !query.trim().isEmpty()) { + suppliers = supplierRepository.searchSuppliers(query, pageable); + } else { + suppliers = supplierRepository.findAll(pageable); + } + return suppliers.map(this::mapToResponse); + } + + public SupplierResponse getSupplierById(Long id) { + Supplier supplier = supplierRepository.findById(id) + .orElseThrow(() -> new ResourceNotFoundException("Supplier not found with id: " + id)); + return mapToResponse(supplier); + } + + @Transactional + public SupplierResponse createSupplier(SupplierRequest request) { + Supplier supplier = new Supplier(); + supplier.setSupplierName(request.getSupplierName()); + supplier.setSupplierContact(request.getSupplierContact()); + supplier.setSupplierEmail(request.getSupplierEmail()); + supplier.setSupplierPhone(request.getSupplierPhone()); + supplier.setSupplierAddress(request.getSupplierAddress()); + supplier.setActive(request.getActive()); + + supplier = supplierRepository.save(supplier); + return mapToResponse(supplier); + } + + @Transactional + public SupplierResponse updateSupplier(Long id, SupplierRequest request) { + Supplier supplier = supplierRepository.findById(id) + .orElseThrow(() -> new ResourceNotFoundException("Supplier not found with id: " + id)); + + supplier.setSupplierName(request.getSupplierName()); + supplier.setSupplierContact(request.getSupplierContact()); + supplier.setSupplierEmail(request.getSupplierEmail()); + supplier.setSupplierPhone(request.getSupplierPhone()); + supplier.setSupplierAddress(request.getSupplierAddress()); + supplier.setActive(request.getActive()); + + supplier = supplierRepository.save(supplier); + return mapToResponse(supplier); + } + + @Transactional + public void deleteSupplier(Long id) { + if (!supplierRepository.existsById(id)) { + throw new ResourceNotFoundException("Supplier not found with id: " + id); + } + supplierRepository.deleteById(id); + } + + @Transactional + public void bulkDeleteSuppliers(BulkDeleteRequest request) { + supplierRepository.deleteAllById(request.getIds()); + } + + private SupplierResponse mapToResponse(Supplier supplier) { + return new SupplierResponse( + supplier.getId(), + supplier.getSupplierName(), + supplier.getSupplierContact(), + supplier.getSupplierEmail(), + supplier.getSupplierPhone(), + supplier.getSupplierAddress(), + supplier.getActive(), + supplier.getCreatedAt(), + supplier.getUpdatedAt() + ); + } +} diff --git a/backend/src/main/java/com/petshop/backend/service/UserService.java b/backend/src/main/java/com/petshop/backend/service/UserService.java new file mode 100644 index 00000000..da028f9a --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/service/UserService.java @@ -0,0 +1,90 @@ +package com.petshop.backend.service; + +import com.petshop.backend.dto.common.BulkDeleteRequest; +import com.petshop.backend.dto.user.UserRequest; +import com.petshop.backend.dto.user.UserResponse; +import com.petshop.backend.entity.User; +import com.petshop.backend.exception.ResourceNotFoundException; +import com.petshop.backend.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class UserService { + + private final UserRepository userRepository; + private final PasswordEncoder passwordEncoder; + + public Page getAllUsers(Pageable pageable) { + return userRepository.findAll(pageable).map(this::mapToResponse); + } + + public UserResponse getUserById(Long id) { + User user = userRepository.findById(id) + .orElseThrow(() -> new ResourceNotFoundException("User not found with id: " + id)); + return mapToResponse(user); + } + + @Transactional + public UserResponse createUser(UserRequest request) { + User user = new User(); + user.setUsername(request.getUsername()); + user.setPassword(passwordEncoder.encode(request.getPassword())); + user.setFullName(request.getFullName()); + user.setEmail(request.getEmail()); + user.setRole(request.getRole()); + user.setActive(request.getActive()); + + user = userRepository.save(user); + return mapToResponse(user); + } + + @Transactional + public UserResponse updateUser(Long id, UserRequest request) { + User user = userRepository.findById(id) + .orElseThrow(() -> new ResourceNotFoundException("User not found with id: " + id)); + + user.setUsername(request.getUsername()); + if (request.getPassword() != null && !request.getPassword().trim().isEmpty()) { + user.setPassword(passwordEncoder.encode(request.getPassword())); + } + user.setFullName(request.getFullName()); + user.setEmail(request.getEmail()); + user.setRole(request.getRole()); + user.setActive(request.getActive()); + + user = userRepository.save(user); + return mapToResponse(user); + } + + @Transactional + public void deleteUser(Long id) { + if (!userRepository.existsById(id)) { + throw new ResourceNotFoundException("User not found with id: " + id); + } + userRepository.deleteById(id); + } + + @Transactional + public void bulkDeleteUsers(BulkDeleteRequest request) { + userRepository.deleteAllById(request.getIds()); + } + + private UserResponse mapToResponse(User user) { + return new UserResponse( + user.getId(), + user.getUsername(), + user.getFullName(), + user.getEmail(), + user.getRole().toString(), + user.getActive(), + user.getCreatedAt(), + user.getUpdatedAt() + ); + } +} From 0df6d931f2876f6209fc5b84d0b1189ccc45316b Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Wed, 4 Mar 2026 17:18:53 -0700 Subject: [PATCH 07/84] Add sales and refunds with inventory management --- .../backend/controller/RefundController.java | 25 +++ .../backend/controller/SaleController.java | 35 +++++ .../backend/dto/refund/RefundResponse.java | 22 +-- .../backend/dto/sale/SaleResponse.java | 22 +-- .../repository/SaleItemRepository.java | 9 ++ .../backend/service/RefundService.java | 115 ++++++++++++++ .../petshop/backend/service/SaleService.java | 145 ++++++++++++++++++ 7 files changed, 351 insertions(+), 22 deletions(-) create mode 100644 backend/src/main/java/com/petshop/backend/controller/RefundController.java create mode 100644 backend/src/main/java/com/petshop/backend/controller/SaleController.java create mode 100644 backend/src/main/java/com/petshop/backend/repository/SaleItemRepository.java create mode 100644 backend/src/main/java/com/petshop/backend/service/RefundService.java create mode 100644 backend/src/main/java/com/petshop/backend/service/SaleService.java diff --git a/backend/src/main/java/com/petshop/backend/controller/RefundController.java b/backend/src/main/java/com/petshop/backend/controller/RefundController.java new file mode 100644 index 00000000..ef527069 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/controller/RefundController.java @@ -0,0 +1,25 @@ +package com.petshop.backend.controller; + +import com.petshop.backend.dto.refund.RefundRequest; +import com.petshop.backend.dto.refund.RefundResponse; +import com.petshop.backend.service.RefundService; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/v1/sales") +@RequiredArgsConstructor +public class RefundController { + + private final RefundService refundService; + + @PostMapping("/{saleId}/refunds") + public ResponseEntity createRefund( + @PathVariable Long saleId, + @Valid @RequestBody RefundRequest request) { + return ResponseEntity.status(HttpStatus.CREATED).body(refundService.createRefund(saleId, request)); + } +} diff --git a/backend/src/main/java/com/petshop/backend/controller/SaleController.java b/backend/src/main/java/com/petshop/backend/controller/SaleController.java new file mode 100644 index 00000000..6b77ba43 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/controller/SaleController.java @@ -0,0 +1,35 @@ +package com.petshop.backend.controller; + +import com.petshop.backend.dto.sale.SaleRequest; +import com.petshop.backend.dto.sale.SaleResponse; +import com.petshop.backend.service.SaleService; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/v1/sales") +@RequiredArgsConstructor +public class SaleController { + + private final SaleService saleService; + + @GetMapping + public ResponseEntity> getAllSales(Pageable pageable) { + return ResponseEntity.ok(saleService.getAllSales(pageable)); + } + + @GetMapping("/{id}") + public ResponseEntity getSaleById(@PathVariable Long id) { + return ResponseEntity.ok(saleService.getSaleById(id)); + } + + @PostMapping + public ResponseEntity createSale(@Valid @RequestBody SaleRequest request) { + return ResponseEntity.status(HttpStatus.CREATED).body(saleService.createSale(request)); + } +} diff --git a/backend/src/main/java/com/petshop/backend/dto/refund/RefundResponse.java b/backend/src/main/java/com/petshop/backend/dto/refund/RefundResponse.java index 50e22874..4ef1caa4 100644 --- a/backend/src/main/java/com/petshop/backend/dto/refund/RefundResponse.java +++ b/backend/src/main/java/com/petshop/backend/dto/refund/RefundResponse.java @@ -21,16 +21,16 @@ public class RefundResponse { private String processedByName; private List items; private LocalDateTime createdAt; -} -@Data -@NoArgsConstructor -@AllArgsConstructor -class RefundItemResponse { - private Long id; - private Long saleItemId; - private Long productId; - private String productName; - private Integer quantity; - private BigDecimal refundAmount; + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class RefundItemResponse { + private Long id; + private Long saleItemId; + private Long productId; + private String productName; + private Integer quantity; + private BigDecimal refundAmount; + } } diff --git a/backend/src/main/java/com/petshop/backend/dto/sale/SaleResponse.java b/backend/src/main/java/com/petshop/backend/dto/sale/SaleResponse.java index b44fd542..e05063ef 100644 --- a/backend/src/main/java/com/petshop/backend/dto/sale/SaleResponse.java +++ b/backend/src/main/java/com/petshop/backend/dto/sale/SaleResponse.java @@ -27,16 +27,16 @@ public class SaleResponse { private String notes; private List items; private LocalDateTime createdAt; -} -@Data -@NoArgsConstructor -@AllArgsConstructor -class SaleItemResponse { - private Long id; - private Long productId; - private String productName; - private Integer quantity; - private BigDecimal unitPrice; - private BigDecimal subtotal; + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class SaleItemResponse { + private Long id; + private Long productId; + private String productName; + private Integer quantity; + private BigDecimal unitPrice; + private BigDecimal subtotal; + } } diff --git a/backend/src/main/java/com/petshop/backend/repository/SaleItemRepository.java b/backend/src/main/java/com/petshop/backend/repository/SaleItemRepository.java new file mode 100644 index 00000000..0b67f95e --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/repository/SaleItemRepository.java @@ -0,0 +1,9 @@ +package com.petshop.backend.repository; + +import com.petshop.backend.entity.SaleItem; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface SaleItemRepository extends JpaRepository { +} diff --git a/backend/src/main/java/com/petshop/backend/service/RefundService.java b/backend/src/main/java/com/petshop/backend/service/RefundService.java new file mode 100644 index 00000000..8f2a4e6a --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/service/RefundService.java @@ -0,0 +1,115 @@ +package com.petshop.backend.service; + +import com.petshop.backend.dto.refund.RefundRequest; +import com.petshop.backend.dto.refund.RefundResponse; +import com.petshop.backend.entity.*; +import com.petshop.backend.exception.BusinessException; +import com.petshop.backend.exception.ResourceNotFoundException; +import com.petshop.backend.repository.*; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +@Service +@RequiredArgsConstructor +public class RefundService { + + private final RefundRepository refundRepository; + private final SaleRepository saleRepository; + private final SaleItemRepository saleItemRepository; + private final InventoryRepository inventoryRepository; + private final UserRepository userRepository; + + @Transactional + public RefundResponse createRefund(Long saleId, RefundRequest request) { + String username = SecurityContextHolder.getContext().getAuthentication().getName(); + User processedBy = userRepository.findByUsername(username) + .orElseThrow(() -> new ResourceNotFoundException("User not found: " + username)); + + Sale sale = saleRepository.findById(saleId) + .orElseThrow(() -> new ResourceNotFoundException("Sale not found with id: " + saleId)); + + Refund refund = new Refund(); + refund.setSale(sale); + refund.setRefundDate(LocalDateTime.now()); + refund.setRefundReason(request.getRefundReason()); + refund.setProcessedBy(processedBy); + + BigDecimal totalRefundAmount = BigDecimal.ZERO; + List refundItems = new ArrayList<>(); + + for (var itemRequest : request.getItems()) { + SaleItem saleItem = saleItemRepository.findById(itemRequest.getSaleItemId()) + .orElseThrow(() -> new ResourceNotFoundException("Sale item not found with id: " + itemRequest.getSaleItemId())); + + if (!saleItem.getSale().getId().equals(saleId)) { + throw new BusinessException("Sale item " + itemRequest.getSaleItemId() + " does not belong to sale " + saleId); + } + + if (itemRequest.getQuantity() > saleItem.getQuantity()) { + throw new BusinessException("Refund quantity (" + itemRequest.getQuantity() + + ") exceeds original sale quantity (" + saleItem.getQuantity() + ") for product: " + saleItem.getProduct().getProductName()); + } + + Inventory inventory = inventoryRepository.findByProductIdAndStoreId( + saleItem.getProduct().getId(), + sale.getStore().getId()) + .orElseThrow(() -> new ResourceNotFoundException("Inventory not found for product " + + saleItem.getProduct().getId() + " at store " + sale.getStore().getId())); + + inventory.setQuantity(inventory.getQuantity() + itemRequest.getQuantity()); + inventory.setLastRestocked(LocalDateTime.now()); + inventoryRepository.save(inventory); + + BigDecimal itemRefundAmount = saleItem.getUnitPrice().multiply(BigDecimal.valueOf(itemRequest.getQuantity())); + + RefundItem refundItem = new RefundItem(); + refundItem.setRefund(refund); + refundItem.setSaleItem(saleItem); + refundItem.setQuantity(itemRequest.getQuantity()); + refundItem.setRefundAmount(itemRefundAmount); + + refundItems.add(refundItem); + totalRefundAmount = totalRefundAmount.add(itemRefundAmount); + } + + refund.setRefundAmount(totalRefundAmount); + refund.setItems(refundItems); + + Refund savedRefund = refundRepository.save(refund); + return mapToResponse(savedRefund); + } + + private RefundResponse mapToResponse(Refund refund) { + RefundResponse response = new RefundResponse(); + response.setId(refund.getId()); + response.setSaleId(refund.getSale().getId()); + response.setRefundDate(refund.getRefundDate()); + response.setRefundAmount(refund.getRefundAmount()); + response.setRefundReason(refund.getRefundReason()); + response.setProcessedBy(refund.getProcessedBy().getId()); + response.setProcessedByName(refund.getProcessedBy().getFullName()); + response.setCreatedAt(refund.getCreatedAt()); + + List itemResponses = new ArrayList<>(); + for (RefundItem item : refund.getItems()) { + RefundResponse.RefundItemResponse itemResponse = new RefundResponse.RefundItemResponse(); + itemResponse.setId(item.getId()); + itemResponse.setSaleItemId(item.getSaleItem().getId()); + itemResponse.setProductId(item.getSaleItem().getProduct().getId()); + itemResponse.setProductName(item.getSaleItem().getProduct().getProductName()); + itemResponse.setQuantity(item.getQuantity()); + itemResponse.setRefundAmount(item.getRefundAmount()); + itemResponses.add(itemResponse); + } + response.setItems(itemResponses); + + return response; + } +} diff --git a/backend/src/main/java/com/petshop/backend/service/SaleService.java b/backend/src/main/java/com/petshop/backend/service/SaleService.java new file mode 100644 index 00000000..e3769adc --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/service/SaleService.java @@ -0,0 +1,145 @@ +package com.petshop.backend.service; + +import com.petshop.backend.dto.sale.SaleRequest; +import com.petshop.backend.dto.sale.SaleResponse; +import com.petshop.backend.entity.*; +import com.petshop.backend.exception.BusinessException; +import com.petshop.backend.exception.ResourceNotFoundException; +import com.petshop.backend.repository.*; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +@Service +@RequiredArgsConstructor +public class SaleService { + + private final SaleRepository saleRepository; + private final ProductRepository productRepository; + private final CustomerRepository customerRepository; + private final StoreRepository storeRepository; + private final InventoryRepository inventoryRepository; + private final UserRepository userRepository; + + public Page getAllSales(Pageable pageable) { + return saleRepository.findAll(pageable).map(this::mapToResponse); + } + + public SaleResponse getSaleById(Long id) { + Sale sale = saleRepository.findById(id) + .orElseThrow(() -> new ResourceNotFoundException("Sale not found with id: " + id)); + return mapToResponse(sale); + } + + @Transactional + public SaleResponse createSale(SaleRequest request) { + String username = SecurityContextHolder.getContext().getAuthentication().getName(); + User employee = userRepository.findByUsername(username) + .orElseThrow(() -> new ResourceNotFoundException("User not found: " + username)); + + Store store = storeRepository.findById(request.getStoreId()) + .orElseThrow(() -> new ResourceNotFoundException("Store not found with id: " + request.getStoreId())); + + Customer customer = null; + if (request.getCustomerId() != null) { + customer = customerRepository.findById(request.getCustomerId()) + .orElseThrow(() -> new ResourceNotFoundException("Customer not found with id: " + request.getCustomerId())); + } + + Sale sale = new Sale(); + sale.setSaleDate(LocalDateTime.now()); + sale.setEmployee(employee); + sale.setCustomer(customer); + sale.setStore(store); + sale.setPaymentMethod(request.getPaymentMethod()); + sale.setTax(request.getTax()); + sale.setNotes(request.getNotes()); + + BigDecimal subtotal = BigDecimal.ZERO; + List saleItems = new ArrayList<>(); + + for (var itemRequest : request.getItems()) { + Product product = productRepository.findById(itemRequest.getProductId()) + .orElseThrow(() -> new ResourceNotFoundException("Product not found with id: " + itemRequest.getProductId())); + + Inventory inventory = inventoryRepository.findByProductIdAndStoreId(itemRequest.getProductId(), request.getStoreId()) + .orElseThrow(() -> new ResourceNotFoundException("Inventory not found for product " + itemRequest.getProductId() + " at store " + request.getStoreId())); + + if (inventory.getQuantity() < itemRequest.getQuantity()) { + throw new BusinessException("Insufficient stock for product: " + product.getProductName() + + ". Available: " + inventory.getQuantity() + ", requested: " + itemRequest.getQuantity()); + } + + inventory.setQuantity(inventory.getQuantity() - itemRequest.getQuantity()); + inventoryRepository.save(inventory); + + BigDecimal unitPrice = product.getProductPrice(); + BigDecimal itemSubtotal = unitPrice.multiply(BigDecimal.valueOf(itemRequest.getQuantity())); + + SaleItem saleItem = new SaleItem(); + saleItem.setSale(sale); + saleItem.setProduct(product); + saleItem.setQuantity(itemRequest.getQuantity()); + saleItem.setUnitPrice(unitPrice); + saleItem.setSubtotal(itemSubtotal); + + saleItems.add(saleItem); + subtotal = subtotal.add(itemSubtotal); + } + + sale.setSubtotal(subtotal); + sale.setTotal(subtotal.add(sale.getTax())); + sale.setItems(saleItems); + + Sale savedSale = saleRepository.save(sale); + return mapToResponse(savedSale); + } + + private SaleResponse mapToResponse(Sale sale) { + SaleResponse response = new SaleResponse(); + response.setId(sale.getId()); + response.setSaleDate(sale.getSaleDate()); + response.setEmployeeId(sale.getEmployee().getId()); + response.setEmployeeName(sale.getEmployee().getFullName()); + + if (sale.getCustomer() != null) { + response.setCustomerId(sale.getCustomer().getId()); + response.setCustomerName(sale.getCustomer().getCustomerName()); + } + + if (sale.getStore() != null) { + response.setStoreId(sale.getStore().getId()); + response.setStoreName(sale.getStore().getStoreName()); + } + + response.setSubtotal(sale.getSubtotal()); + response.setTax(sale.getTax()); + response.setTotal(sale.getTotal()); + response.setPaymentMethod(sale.getPaymentMethod()); + response.setNotes(sale.getNotes()); + response.setCreatedAt(sale.getCreatedAt()); + + List itemResponses = new ArrayList<>(); + for (SaleItem item : sale.getItems()) { + SaleResponse.SaleItemResponse itemResponse = new SaleResponse.SaleItemResponse(); + itemResponse.setId(item.getId()); + itemResponse.setProductId(item.getProduct().getId()); + itemResponse.setProductName(item.getProduct().getProductName()); + itemResponse.setQuantity(item.getQuantity()); + itemResponse.setUnitPrice(item.getUnitPrice()); + itemResponse.setSubtotal(item.getSubtotal()); + itemResponses.add(itemResponse); + } + response.setItems(itemResponses); + + return response; + } +} From 40c88c916259f845876e21227aafd67a4c729397 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Wed, 4 Mar 2026 17:21:22 -0700 Subject: [PATCH 08/84] Fix compilation issues --- .../BulkDeleteProductSupplierRequest.java | 12 ++++++++++ .../productsupplier/ProductSupplierKey.java | 5 ----- .../purchaseorder/PurchaseOrderResponse.java | 22 +++++++++---------- .../backend/dto/refund/RefundItemRequest.java | 15 +++++++++++++ .../backend/dto/refund/RefundRequest.java | 10 --------- .../backend/dto/sale/SaleItemRequest.java | 15 +++++++++++++ .../petshop/backend/dto/sale/SaleRequest.java | 10 --------- .../backend/service/PurchaseOrderService.java | 2 +- 8 files changed, 54 insertions(+), 37 deletions(-) create mode 100644 backend/src/main/java/com/petshop/backend/dto/productsupplier/BulkDeleteProductSupplierRequest.java create mode 100644 backend/src/main/java/com/petshop/backend/dto/refund/RefundItemRequest.java create mode 100644 backend/src/main/java/com/petshop/backend/dto/sale/SaleItemRequest.java diff --git a/backend/src/main/java/com/petshop/backend/dto/productsupplier/BulkDeleteProductSupplierRequest.java b/backend/src/main/java/com/petshop/backend/dto/productsupplier/BulkDeleteProductSupplierRequest.java new file mode 100644 index 00000000..dfa95bb5 --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/dto/productsupplier/BulkDeleteProductSupplierRequest.java @@ -0,0 +1,12 @@ +package com.petshop.backend.dto.productsupplier; + +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; + +import java.util.List; + +@Data +public class BulkDeleteProductSupplierRequest { + @NotEmpty(message = "Keys list cannot be empty") + private List keys; +} diff --git a/backend/src/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierKey.java b/backend/src/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierKey.java index b86b6980..3826b39c 100644 --- a/backend/src/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierKey.java +++ b/backend/src/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierKey.java @@ -11,8 +11,3 @@ public class ProductSupplierKey { private Long supplierId; } -@Data -public class BulkDeleteProductSupplierRequest { - @NotEmpty(message = "Keys list cannot be empty") - private List keys; -} diff --git a/backend/src/main/java/com/petshop/backend/dto/purchaseorder/PurchaseOrderResponse.java b/backend/src/main/java/com/petshop/backend/dto/purchaseorder/PurchaseOrderResponse.java index 4a67865c..a7e68041 100644 --- a/backend/src/main/java/com/petshop/backend/dto/purchaseorder/PurchaseOrderResponse.java +++ b/backend/src/main/java/com/petshop/backend/dto/purchaseorder/PurchaseOrderResponse.java @@ -24,16 +24,16 @@ public class PurchaseOrderResponse { private List items; private LocalDateTime createdAt; private LocalDateTime updatedAt; -} -@Data -@NoArgsConstructor -@AllArgsConstructor -public class PurchaseOrderItemResponse { - private Long id; - private Long productId; - private String productName; - private Integer quantity; - private BigDecimal unitCost; - private BigDecimal subtotal; + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class PurchaseOrderItemResponse { + private Long id; + private Long productId; + private String productName; + private Integer quantity; + private BigDecimal unitCost; + private BigDecimal subtotal; + } } diff --git a/backend/src/main/java/com/petshop/backend/dto/refund/RefundItemRequest.java b/backend/src/main/java/com/petshop/backend/dto/refund/RefundItemRequest.java new file mode 100644 index 00000000..f972407c --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/dto/refund/RefundItemRequest.java @@ -0,0 +1,15 @@ +package com.petshop.backend.dto.refund; + +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; +import lombok.Data; + +@Data +public class RefundItemRequest { + @NotNull(message = "Sale item ID is required") + private Long saleItemId; + + @NotNull(message = "Quantity is required") + @Positive(message = "Quantity must be positive") + private Integer quantity; +} diff --git a/backend/src/main/java/com/petshop/backend/dto/refund/RefundRequest.java b/backend/src/main/java/com/petshop/backend/dto/refund/RefundRequest.java index 61ec7b3f..1abf9c23 100644 --- a/backend/src/main/java/com/petshop/backend/dto/refund/RefundRequest.java +++ b/backend/src/main/java/com/petshop/backend/dto/refund/RefundRequest.java @@ -16,13 +16,3 @@ public class RefundRequest { private String refundReason; } - -@Data -class RefundItemRequest { - @NotNull(message = "Sale item ID is required") - private Long saleItemId; - - @NotNull(message = "Quantity is required") - @Positive(message = "Quantity must be positive") - private Integer quantity; -} diff --git a/backend/src/main/java/com/petshop/backend/dto/sale/SaleItemRequest.java b/backend/src/main/java/com/petshop/backend/dto/sale/SaleItemRequest.java new file mode 100644 index 00000000..60d5596e --- /dev/null +++ b/backend/src/main/java/com/petshop/backend/dto/sale/SaleItemRequest.java @@ -0,0 +1,15 @@ +package com.petshop.backend.dto.sale; + +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Positive; +import lombok.Data; + +@Data +public class SaleItemRequest { + @NotNull(message = "Product ID is required") + private Long productId; + + @NotNull(message = "Quantity is required") + @Positive(message = "Quantity must be positive") + private Integer quantity; +} diff --git a/backend/src/main/java/com/petshop/backend/dto/sale/SaleRequest.java b/backend/src/main/java/com/petshop/backend/dto/sale/SaleRequest.java index 56a7c71d..220aaa39 100644 --- a/backend/src/main/java/com/petshop/backend/dto/sale/SaleRequest.java +++ b/backend/src/main/java/com/petshop/backend/dto/sale/SaleRequest.java @@ -26,13 +26,3 @@ public class SaleRequest { private String notes; } - -@Data -class SaleItemRequest { - @NotNull(message = "Product ID is required") - private Long productId; - - @NotNull(message = "Quantity is required") - @Positive(message = "Quantity must be positive") - private Integer quantity; -} diff --git a/backend/src/main/java/com/petshop/backend/service/PurchaseOrderService.java b/backend/src/main/java/com/petshop/backend/service/PurchaseOrderService.java index 1e9c61f0..fa5d9859 100644 --- a/backend/src/main/java/com/petshop/backend/service/PurchaseOrderService.java +++ b/backend/src/main/java/com/petshop/backend/service/PurchaseOrderService.java @@ -1,7 +1,7 @@ package com.petshop.backend.service; -import com.petshop.backend.dto.purchaseorder.PurchaseOrderItemResponse; import com.petshop.backend.dto.purchaseorder.PurchaseOrderResponse; +import com.petshop.backend.dto.purchaseorder.PurchaseOrderResponse.PurchaseOrderItemResponse; import com.petshop.backend.entity.PurchaseOrder; import com.petshop.backend.entity.PurchaseOrderItem; import com.petshop.backend.exception.ResourceNotFoundException; From 3f82142d3aa01432f077a2dec96c8ad0163918d1 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Wed, 4 Mar 2026 17:28:41 -0700 Subject: [PATCH 09/84] Remove migrations and tests --- backend/pom.xml | 27 ---------------------- backend/src/main/resources/application.yml | 7 +----- 2 files changed, 1 insertion(+), 33 deletions(-) diff --git a/backend/pom.xml b/backend/pom.xml index 9b11bf68..cf42279b 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -49,16 +49,6 @@ runtime - - org.flywaydb - flyway-core - - - - org.flywaydb - flyway-mysql - - io.jsonwebtoken jjwt-api @@ -87,23 +77,6 @@ - - org.springframework.boot - spring-boot-starter-test - test - - - - org.springframework.security - spring-security-test - test - - - - com.h2database - h2 - test - diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml index 07c5bff0..cd3a99f7 100644 --- a/backend/src/main/resources/application.yml +++ b/backend/src/main/resources/application.yml @@ -10,18 +10,13 @@ spring: jpa: hibernate: - ddl-auto: validate + ddl-auto: update show-sql: ${JPA_SHOW_SQL:false} properties: hibernate: format_sql: true dialect: org.hibernate.dialect.MySQLDialect - flyway: - enabled: true - baseline-on-migrate: true - locations: classpath:db/migration - server: port: ${SERVER_PORT:8080} servlet: From aeb8002b2b86b712b99ac7a376594f4067d92f6b Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Wed, 4 Mar 2026 17:34:43 -0700 Subject: [PATCH 10/84] Move backend to root directory --- backend/.gitignore => .gitignore | 0 backend/pom.xml => pom.xml | 0 .../petshop/backend/BackendApplication.java | 0 .../controller/AdoptionController.java | 3 +- .../controller/AppointmentController.java | 3 +- .../backend/controller/AuthController.java | 3 +- .../controller/CategoryController.java | 3 +- .../controller/CustomerController.java | 3 +- .../controller/DropdownController.java | 3 +- .../controller/InventoryController.java | 3 +- .../backend/controller/PetController.java | 3 +- .../backend/controller/ProductController.java | 3 +- .../controller/ProductSupplierController.java | 3 +- .../controller/PurchaseOrderController.java | 3 +- .../backend/controller/RefundController.java | 3 +- .../backend/controller/SaleController.java | 3 +- .../backend/controller/ServiceController.java | 3 +- .../controller/SupplierController.java | 3 +- .../backend/controller/UserController.java | 3 +- .../backend/dto/adoption/AdoptionRequest.java | 3 +- .../dto/adoption/AdoptionResponse.java | 9 ++--- .../dto/analytics/DashboardResponse.java | 33 +++++++++---------- .../dto/appointment/AppointmentRequest.java | 3 +- .../dto/appointment/AppointmentResponse.java | 9 ++--- .../backend/dto/auth/LoginRequest.java | 3 +- .../backend/dto/auth/LoginResponse.java | 6 ++-- .../backend/dto/auth/UserInfoResponse.java | 6 ++-- .../backend/dto/category/CategoryRequest.java | 3 +- .../dto/category/CategoryResponse.java | 9 ++--- .../backend/dto/common/BulkDeleteRequest.java | 3 +- .../backend/dto/common/DropdownOption.java | 6 ++-- .../backend/dto/customer/CustomerRequest.java | 3 +- .../dto/customer/CustomerResponse.java | 9 ++--- .../dto/inventory/InventoryRequest.java | 3 +- .../dto/inventory/InventoryResponse.java | 9 ++--- .../petshop/backend/dto/pet/PetRequest.java | 3 +- .../petshop/backend/dto/pet/PetResponse.java | 9 ++--- .../backend/dto/product/ProductRequest.java | 3 +- .../backend/dto/product/ProductResponse.java | 9 ++--- .../BulkDeleteProductSupplierRequest.java | 3 +- .../productsupplier/ProductSupplierKey.java | 3 +- .../ProductSupplierRequest.java | 3 +- .../ProductSupplierResponse.java | 9 ++--- .../purchaseorder/PurchaseOrderResponse.java | 15 ++++----- .../backend/dto/refund/RefundItemRequest.java | 3 +- .../backend/dto/refund/RefundRequest.java | 3 +- .../backend/dto/refund/RefundResponse.java | 15 ++++----- .../backend/dto/sale/SaleItemRequest.java | 3 +- .../petshop/backend/dto/sale/SaleRequest.java | 3 +- .../backend/dto/sale/SaleResponse.java | 15 ++++----- .../backend/dto/service/ServiceRequest.java | 3 +- .../backend/dto/service/ServiceResponse.java | 9 ++--- .../backend/dto/supplier/SupplierRequest.java | 3 +- .../dto/supplier/SupplierResponse.java | 9 ++--- .../petshop/backend/dto/user/UserRequest.java | 3 +- .../backend/dto/user/UserResponse.java | 9 ++--- .../com/petshop/backend/entity/Adoption.java | 9 ++--- .../petshop/backend/entity/Appointment.java | 9 ++--- .../com/petshop/backend/entity/Category.java | 9 ++--- .../com/petshop/backend/entity/Customer.java | 9 ++--- .../com/petshop/backend/entity/Inventory.java | 9 ++--- .../java/com/petshop/backend/entity/Pet.java | 9 ++--- .../com/petshop/backend/entity/Product.java | 9 ++--- .../backend/entity/ProductSupplier.java | 15 ++++----- .../petshop/backend/entity/PurchaseOrder.java | 9 ++--- .../backend/entity/PurchaseOrderItem.java | 9 ++--- .../com/petshop/backend/entity/Refund.java | 9 ++--- .../petshop/backend/entity/RefundItem.java | 9 ++--- .../java/com/petshop/backend/entity/Sale.java | 9 ++--- .../com/petshop/backend/entity/SaleItem.java | 9 ++--- .../com/petshop/backend/entity/Service.java | 9 ++--- .../com/petshop/backend/entity/Store.java | 9 ++--- .../com/petshop/backend/entity/Supplier.java | 9 ++--- .../java/com/petshop/backend/entity/User.java | 9 ++--- .../backend/exception/BusinessException.java | 0 .../exception/GlobalExceptionHandler.java | 0 .../exception/ResourceNotFoundException.java | 0 .../repository/AdoptionRepository.java | 0 .../repository/AppointmentRepository.java | 0 .../repository/CategoryRepository.java | 0 .../repository/CustomerRepository.java | 0 .../repository/InventoryRepository.java | 0 .../backend/repository/PetRepository.java | 0 .../backend/repository/ProductRepository.java | 0 .../repository/ProductSupplierRepository.java | 0 .../repository/PurchaseOrderRepository.java | 0 .../backend/repository/RefundRepository.java | 0 .../repository/SaleItemRepository.java | 0 .../backend/repository/SaleRepository.java | 0 .../backend/repository/ServiceRepository.java | 0 .../backend/repository/StoreRepository.java | 0 .../repository/SupplierRepository.java | 0 .../backend/repository/UserRepository.java | 0 .../security/JwtAuthenticationFilter.java | 3 +- .../com/petshop/backend/security/JwtUtil.java | 0 .../backend/security/SecurityConfig.java | 3 +- .../security/UserDetailsServiceImpl.java | 3 +- .../backend/service/AdoptionService.java | 3 +- .../backend/service/AppointmentService.java | 3 +- .../backend/service/CategoryService.java | 3 +- .../backend/service/CustomerService.java | 3 +- .../backend/service/InventoryService.java | 3 +- .../petshop/backend/service/PetService.java | 3 +- .../backend/service/ProductService.java | 3 +- .../service/ProductSupplierService.java | 3 +- .../backend/service/PurchaseOrderService.java | 3 +- .../backend/service/RefundService.java | 3 +- .../petshop/backend/service/SaleService.java | 3 +- .../backend/service/ServiceService.java | 3 +- .../backend/service/SupplierService.java | 3 +- .../petshop/backend/service/UserService.java | 3 +- .../main/resources/application.yml | 0 112 files changed, 181 insertions(+), 338 deletions(-) rename backend/.gitignore => .gitignore (100%) rename backend/pom.xml => pom.xml (100%) rename {backend/src => src}/main/java/com/petshop/backend/BackendApplication.java (100%) rename {backend/src => src}/main/java/com/petshop/backend/controller/AdoptionController.java (96%) rename {backend/src => src}/main/java/com/petshop/backend/controller/AppointmentController.java (97%) rename {backend/src => src}/main/java/com/petshop/backend/controller/AuthController.java (98%) rename {backend/src => src}/main/java/com/petshop/backend/controller/CategoryController.java (97%) rename {backend/src => src}/main/java/com/petshop/backend/controller/CustomerController.java (96%) rename {backend/src => src}/main/java/com/petshop/backend/controller/DropdownController.java (98%) rename {backend/src => src}/main/java/com/petshop/backend/controller/InventoryController.java (97%) rename {backend/src => src}/main/java/com/petshop/backend/controller/PetController.java (96%) rename {backend/src => src}/main/java/com/petshop/backend/controller/ProductController.java (96%) rename {backend/src => src}/main/java/com/petshop/backend/controller/ProductSupplierController.java (97%) rename {backend/src => src}/main/java/com/petshop/backend/controller/PurchaseOrderController.java (94%) rename {backend/src => src}/main/java/com/petshop/backend/controller/RefundController.java (92%) rename {backend/src => src}/main/java/com/petshop/backend/controller/SaleController.java (94%) rename {backend/src => src}/main/java/com/petshop/backend/controller/ServiceController.java (96%) rename {backend/src => src}/main/java/com/petshop/backend/controller/SupplierController.java (97%) rename {backend/src => src}/main/java/com/petshop/backend/controller/UserController.java (96%) rename {backend/src => src}/main/java/com/petshop/backend/dto/adoption/AdoptionRequest.java (96%) rename {backend/src => src}/main/java/com/petshop/backend/dto/adoption/AdoptionResponse.java (78%) rename {backend/src => src}/main/java/com/petshop/backend/dto/analytics/DashboardResponse.java (73%) rename {backend/src => src}/main/java/com/petshop/backend/dto/appointment/AppointmentRequest.java (97%) rename {backend/src => src}/main/java/com/petshop/backend/dto/appointment/AppointmentResponse.java (82%) rename {backend/src => src}/main/java/com/petshop/backend/dto/auth/LoginRequest.java (91%) rename {backend/src => src}/main/java/com/petshop/backend/dto/auth/LoginResponse.java (69%) rename {backend/src => src}/main/java/com/petshop/backend/dto/auth/UserInfoResponse.java (71%) rename {backend/src => src}/main/java/com/petshop/backend/dto/category/CategoryRequest.java (90%) rename {backend/src => src}/main/java/com/petshop/backend/dto/category/CategoryResponse.java (67%) rename {backend/src => src}/main/java/com/petshop/backend/dto/common/BulkDeleteRequest.java (89%) rename {backend/src => src}/main/java/com/petshop/backend/dto/common/DropdownOption.java (60%) rename {backend/src => src}/main/java/com/petshop/backend/dto/customer/CustomerRequest.java (93%) rename {backend/src => src}/main/java/com/petshop/backend/dto/customer/CustomerResponse.java (72%) rename {backend/src => src}/main/java/com/petshop/backend/dto/inventory/InventoryRequest.java (95%) rename {backend/src => src}/main/java/com/petshop/backend/dto/inventory/InventoryResponse.java (77%) rename {backend/src => src}/main/java/com/petshop/backend/dto/pet/PetRequest.java (96%) rename {backend/src => src}/main/java/com/petshop/backend/dto/pet/PetResponse.java (75%) rename {backend/src => src}/main/java/com/petshop/backend/dto/product/ProductRequest.java (96%) rename {backend/src => src}/main/java/com/petshop/backend/dto/product/ProductResponse.java (76%) rename {backend/src => src}/main/java/com/petshop/backend/dto/productsupplier/BulkDeleteProductSupplierRequest.java (91%) rename {backend/src => src}/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierKey.java (89%) rename {backend/src => src}/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierRequest.java (96%) rename {backend/src => src}/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierResponse.java (77%) rename {backend/src => src}/main/java/com/petshop/backend/dto/purchaseorder/PurchaseOrderResponse.java (81%) rename {backend/src => src}/main/java/com/petshop/backend/dto/refund/RefundItemRequest.java (93%) rename {backend/src => src}/main/java/com/petshop/backend/dto/refund/RefundRequest.java (94%) rename {backend/src => src}/main/java/com/petshop/backend/dto/refund/RefundResponse.java (79%) rename {backend/src => src}/main/java/com/petshop/backend/dto/sale/SaleItemRequest.java (93%) rename {backend/src => src}/main/java/com/petshop/backend/dto/sale/SaleRequest.java (95%) rename {backend/src => src}/main/java/com/petshop/backend/dto/sale/SaleResponse.java (82%) rename {backend/src => src}/main/java/com/petshop/backend/dto/service/ServiceRequest.java (96%) rename {backend/src => src}/main/java/com/petshop/backend/dto/service/ServiceResponse.java (75%) rename {backend/src => src}/main/java/com/petshop/backend/dto/supplier/SupplierRequest.java (94%) rename {backend/src => src}/main/java/com/petshop/backend/dto/supplier/SupplierResponse.java (75%) rename {backend/src => src}/main/java/com/petshop/backend/dto/user/UserRequest.java (96%) rename {backend/src => src}/main/java/com/petshop/backend/dto/user/UserResponse.java (71%) rename {backend/src => src}/main/java/com/petshop/backend/entity/Adoption.java (89%) rename {backend/src => src}/main/java/com/petshop/backend/entity/Appointment.java (92%) rename {backend/src => src}/main/java/com/petshop/backend/entity/Category.java (85%) rename {backend/src => src}/main/java/com/petshop/backend/entity/Customer.java (87%) rename {backend/src => src}/main/java/com/petshop/backend/entity/Inventory.java (89%) rename {backend/src => src}/main/java/com/petshop/backend/entity/Pet.java (90%) rename {backend/src => src}/main/java/com/petshop/backend/entity/Product.java (89%) rename {backend/src => src}/main/java/com/petshop/backend/entity/ProductSupplier.java (86%) rename {backend/src => src}/main/java/com/petshop/backend/entity/PurchaseOrder.java (91%) rename {backend/src => src}/main/java/com/petshop/backend/entity/PurchaseOrderItem.java (84%) rename {backend/src => src}/main/java/com/petshop/backend/entity/Refund.java (89%) rename {backend/src => src}/main/java/com/petshop/backend/entity/RefundItem.java (82%) rename {backend/src => src}/main/java/com/petshop/backend/entity/Sale.java (91%) rename {backend/src => src}/main/java/com/petshop/backend/entity/SaleItem.java (84%) rename {backend/src => src}/main/java/com/petshop/backend/entity/Service.java (89%) rename {backend/src => src}/main/java/com/petshop/backend/entity/Store.java (81%) rename {backend/src => src}/main/java/com/petshop/backend/entity/Supplier.java (89%) rename {backend/src => src}/main/java/com/petshop/backend/entity/User.java (88%) rename {backend/src => src}/main/java/com/petshop/backend/exception/BusinessException.java (100%) rename {backend/src => src}/main/java/com/petshop/backend/exception/GlobalExceptionHandler.java (100%) rename {backend/src => src}/main/java/com/petshop/backend/exception/ResourceNotFoundException.java (100%) rename {backend/src => src}/main/java/com/petshop/backend/repository/AdoptionRepository.java (100%) rename {backend/src => src}/main/java/com/petshop/backend/repository/AppointmentRepository.java (100%) rename {backend/src => src}/main/java/com/petshop/backend/repository/CategoryRepository.java (100%) rename {backend/src => src}/main/java/com/petshop/backend/repository/CustomerRepository.java (100%) rename {backend/src => src}/main/java/com/petshop/backend/repository/InventoryRepository.java (100%) rename {backend/src => src}/main/java/com/petshop/backend/repository/PetRepository.java (100%) rename {backend/src => src}/main/java/com/petshop/backend/repository/ProductRepository.java (100%) rename {backend/src => src}/main/java/com/petshop/backend/repository/ProductSupplierRepository.java (100%) rename {backend/src => src}/main/java/com/petshop/backend/repository/PurchaseOrderRepository.java (100%) rename {backend/src => src}/main/java/com/petshop/backend/repository/RefundRepository.java (100%) rename {backend/src => src}/main/java/com/petshop/backend/repository/SaleItemRepository.java (100%) rename {backend/src => src}/main/java/com/petshop/backend/repository/SaleRepository.java (100%) rename {backend/src => src}/main/java/com/petshop/backend/repository/ServiceRepository.java (100%) rename {backend/src => src}/main/java/com/petshop/backend/repository/StoreRepository.java (100%) rename {backend/src => src}/main/java/com/petshop/backend/repository/SupplierRepository.java (100%) rename {backend/src => src}/main/java/com/petshop/backend/repository/UserRepository.java (100%) rename {backend/src => src}/main/java/com/petshop/backend/security/JwtAuthenticationFilter.java (97%) rename {backend/src => src}/main/java/com/petshop/backend/security/JwtUtil.java (100%) rename {backend/src => src}/main/java/com/petshop/backend/security/SecurityConfig.java (98%) rename {backend/src => src}/main/java/com/petshop/backend/security/UserDetailsServiceImpl.java (95%) rename {backend/src => src}/main/java/com/petshop/backend/service/AdoptionService.java (98%) rename {backend/src => src}/main/java/com/petshop/backend/service/AppointmentService.java (98%) rename {backend/src => src}/main/java/com/petshop/backend/service/CategoryService.java (97%) rename {backend/src => src}/main/java/com/petshop/backend/service/CustomerService.java (98%) rename {backend/src => src}/main/java/com/petshop/backend/service/InventoryService.java (98%) rename {backend/src => src}/main/java/com/petshop/backend/service/PetService.java (97%) rename {backend/src => src}/main/java/com/petshop/backend/service/ProductService.java (98%) rename {backend/src => src}/main/java/com/petshop/backend/service/ProductSupplierService.java (98%) rename {backend/src => src}/main/java/com/petshop/backend/service/PurchaseOrderService.java (97%) rename {backend/src => src}/main/java/com/petshop/backend/service/RefundService.java (98%) rename {backend/src => src}/main/java/com/petshop/backend/service/SaleService.java (98%) rename {backend/src => src}/main/java/com/petshop/backend/service/ServiceService.java (98%) rename {backend/src => src}/main/java/com/petshop/backend/service/SupplierService.java (98%) rename {backend/src => src}/main/java/com/petshop/backend/service/UserService.java (97%) rename {backend/src => src}/main/resources/application.yml (100%) diff --git a/backend/.gitignore b/.gitignore similarity index 100% rename from backend/.gitignore rename to .gitignore diff --git a/backend/pom.xml b/pom.xml similarity index 100% rename from backend/pom.xml rename to pom.xml diff --git a/backend/src/main/java/com/petshop/backend/BackendApplication.java b/src/main/java/com/petshop/backend/BackendApplication.java similarity index 100% rename from backend/src/main/java/com/petshop/backend/BackendApplication.java rename to src/main/java/com/petshop/backend/BackendApplication.java diff --git a/backend/src/main/java/com/petshop/backend/controller/AdoptionController.java b/src/main/java/com/petshop/backend/controller/AdoptionController.java similarity index 96% rename from backend/src/main/java/com/petshop/backend/controller/AdoptionController.java rename to src/main/java/com/petshop/backend/controller/AdoptionController.java index 0efe8741..81be9fd7 100644 --- a/backend/src/main/java/com/petshop/backend/controller/AdoptionController.java +++ b/src/main/java/com/petshop/backend/controller/AdoptionController.java @@ -5,7 +5,6 @@ import com.petshop.backend.dto.adoption.AdoptionResponse; import com.petshop.backend.dto.common.BulkDeleteRequest; import com.petshop.backend.service.AdoptionService; import jakarta.validation.Valid; -import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.http.HttpStatus; @@ -14,7 +13,7 @@ import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/v1/adoptions") -@RequiredArgsConstructor + public class AdoptionController { private final AdoptionService adoptionService; diff --git a/backend/src/main/java/com/petshop/backend/controller/AppointmentController.java b/src/main/java/com/petshop/backend/controller/AppointmentController.java similarity index 97% rename from backend/src/main/java/com/petshop/backend/controller/AppointmentController.java rename to src/main/java/com/petshop/backend/controller/AppointmentController.java index 843aafe1..12ba8aa4 100644 --- a/backend/src/main/java/com/petshop/backend/controller/AppointmentController.java +++ b/src/main/java/com/petshop/backend/controller/AppointmentController.java @@ -5,7 +5,6 @@ import com.petshop.backend.dto.appointment.AppointmentResponse; import com.petshop.backend.dto.common.BulkDeleteRequest; import com.petshop.backend.service.AppointmentService; import jakarta.validation.Valid; -import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.http.HttpStatus; @@ -14,7 +13,7 @@ import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/v1/appointments") -@RequiredArgsConstructor + public class AppointmentController { private final AppointmentService appointmentService; diff --git a/backend/src/main/java/com/petshop/backend/controller/AuthController.java b/src/main/java/com/petshop/backend/controller/AuthController.java similarity index 98% rename from backend/src/main/java/com/petshop/backend/controller/AuthController.java rename to src/main/java/com/petshop/backend/controller/AuthController.java index 4d0dea61..cab4ef52 100644 --- a/backend/src/main/java/com/petshop/backend/controller/AuthController.java +++ b/src/main/java/com/petshop/backend/controller/AuthController.java @@ -7,7 +7,6 @@ import com.petshop.backend.entity.User; import com.petshop.backend.repository.UserRepository; import com.petshop.backend.security.JwtUtil; import jakarta.validation.Valid; -import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.authentication.AuthenticationManager; @@ -24,7 +23,7 @@ import java.util.Map; @RestController @RequestMapping("/api/v1/auth") -@RequiredArgsConstructor + public class AuthController { private final AuthenticationManager authenticationManager; diff --git a/backend/src/main/java/com/petshop/backend/controller/CategoryController.java b/src/main/java/com/petshop/backend/controller/CategoryController.java similarity index 97% rename from backend/src/main/java/com/petshop/backend/controller/CategoryController.java rename to src/main/java/com/petshop/backend/controller/CategoryController.java index 7c6ab6be..510f729d 100644 --- a/backend/src/main/java/com/petshop/backend/controller/CategoryController.java +++ b/src/main/java/com/petshop/backend/controller/CategoryController.java @@ -5,7 +5,6 @@ import com.petshop.backend.dto.category.CategoryResponse; import com.petshop.backend.dto.common.BulkDeleteRequest; import com.petshop.backend.service.CategoryService; import jakarta.validation.Valid; -import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.http.HttpStatus; @@ -14,7 +13,7 @@ import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/v1/categories") -@RequiredArgsConstructor + public class CategoryController { private final CategoryService categoryService; diff --git a/backend/src/main/java/com/petshop/backend/controller/CustomerController.java b/src/main/java/com/petshop/backend/controller/CustomerController.java similarity index 96% rename from backend/src/main/java/com/petshop/backend/controller/CustomerController.java rename to src/main/java/com/petshop/backend/controller/CustomerController.java index 393d043f..96ac564f 100644 --- a/backend/src/main/java/com/petshop/backend/controller/CustomerController.java +++ b/src/main/java/com/petshop/backend/controller/CustomerController.java @@ -5,7 +5,6 @@ import com.petshop.backend.dto.customer.CustomerRequest; import com.petshop.backend.dto.customer.CustomerResponse; import com.petshop.backend.service.CustomerService; import jakarta.validation.Valid; -import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.http.HttpStatus; @@ -14,7 +13,7 @@ import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/v1/customers") -@RequiredArgsConstructor + public class CustomerController { private final CustomerService customerService; diff --git a/backend/src/main/java/com/petshop/backend/controller/DropdownController.java b/src/main/java/com/petshop/backend/controller/DropdownController.java similarity index 98% rename from backend/src/main/java/com/petshop/backend/controller/DropdownController.java rename to src/main/java/com/petshop/backend/controller/DropdownController.java index 73ba3cba..48cc4b5e 100644 --- a/backend/src/main/java/com/petshop/backend/controller/DropdownController.java +++ b/src/main/java/com/petshop/backend/controller/DropdownController.java @@ -2,7 +2,6 @@ package com.petshop.backend.controller; import com.petshop.backend.dto.common.DropdownOption; import com.petshop.backend.repository.*; -import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.GetMapping; @@ -14,7 +13,7 @@ import java.util.stream.Collectors; @RestController @RequestMapping("/api/v1/dropdowns") -@RequiredArgsConstructor + public class DropdownController { private final PetRepository petRepository; diff --git a/backend/src/main/java/com/petshop/backend/controller/InventoryController.java b/src/main/java/com/petshop/backend/controller/InventoryController.java similarity index 97% rename from backend/src/main/java/com/petshop/backend/controller/InventoryController.java rename to src/main/java/com/petshop/backend/controller/InventoryController.java index 61ac8df2..f4c6742a 100644 --- a/backend/src/main/java/com/petshop/backend/controller/InventoryController.java +++ b/src/main/java/com/petshop/backend/controller/InventoryController.java @@ -5,7 +5,6 @@ import com.petshop.backend.dto.inventory.InventoryRequest; import com.petshop.backend.dto.inventory.InventoryResponse; import com.petshop.backend.service.InventoryService; import jakarta.validation.Valid; -import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.http.HttpStatus; @@ -15,7 +14,7 @@ import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/v1/inventory") -@RequiredArgsConstructor + @PreAuthorize("hasRole('ADMIN')") public class InventoryController { diff --git a/backend/src/main/java/com/petshop/backend/controller/PetController.java b/src/main/java/com/petshop/backend/controller/PetController.java similarity index 96% rename from backend/src/main/java/com/petshop/backend/controller/PetController.java rename to src/main/java/com/petshop/backend/controller/PetController.java index f5c45015..1d70bf87 100644 --- a/backend/src/main/java/com/petshop/backend/controller/PetController.java +++ b/src/main/java/com/petshop/backend/controller/PetController.java @@ -5,7 +5,6 @@ import com.petshop.backend.dto.pet.PetRequest; import com.petshop.backend.dto.pet.PetResponse; import com.petshop.backend.service.PetService; import jakarta.validation.Valid; -import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.http.HttpStatus; @@ -14,7 +13,7 @@ import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/v1/pets") -@RequiredArgsConstructor + public class PetController { private final PetService petService; diff --git a/backend/src/main/java/com/petshop/backend/controller/ProductController.java b/src/main/java/com/petshop/backend/controller/ProductController.java similarity index 96% rename from backend/src/main/java/com/petshop/backend/controller/ProductController.java rename to src/main/java/com/petshop/backend/controller/ProductController.java index 7cec17a8..98d0fe10 100644 --- a/backend/src/main/java/com/petshop/backend/controller/ProductController.java +++ b/src/main/java/com/petshop/backend/controller/ProductController.java @@ -5,7 +5,6 @@ import com.petshop.backend.dto.product.ProductRequest; import com.petshop.backend.dto.product.ProductResponse; import com.petshop.backend.service.ProductService; import jakarta.validation.Valid; -import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.http.HttpStatus; @@ -14,7 +13,7 @@ import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/v1/products") -@RequiredArgsConstructor + public class ProductController { private final ProductService productService; diff --git a/backend/src/main/java/com/petshop/backend/controller/ProductSupplierController.java b/src/main/java/com/petshop/backend/controller/ProductSupplierController.java similarity index 97% rename from backend/src/main/java/com/petshop/backend/controller/ProductSupplierController.java rename to src/main/java/com/petshop/backend/controller/ProductSupplierController.java index 20965a3d..18c31c79 100644 --- a/backend/src/main/java/com/petshop/backend/controller/ProductSupplierController.java +++ b/src/main/java/com/petshop/backend/controller/ProductSupplierController.java @@ -5,7 +5,6 @@ import com.petshop.backend.dto.productsupplier.ProductSupplierRequest; import com.petshop.backend.dto.productsupplier.ProductSupplierResponse; import com.petshop.backend.service.ProductSupplierService; import jakarta.validation.Valid; -import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.http.HttpStatus; @@ -15,7 +14,7 @@ import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/v1/product-suppliers") -@RequiredArgsConstructor + @PreAuthorize("hasRole('ADMIN')") public class ProductSupplierController { diff --git a/backend/src/main/java/com/petshop/backend/controller/PurchaseOrderController.java b/src/main/java/com/petshop/backend/controller/PurchaseOrderController.java similarity index 94% rename from backend/src/main/java/com/petshop/backend/controller/PurchaseOrderController.java rename to src/main/java/com/petshop/backend/controller/PurchaseOrderController.java index fb0a8077..4496b750 100644 --- a/backend/src/main/java/com/petshop/backend/controller/PurchaseOrderController.java +++ b/src/main/java/com/petshop/backend/controller/PurchaseOrderController.java @@ -2,7 +2,6 @@ package com.petshop.backend.controller; import com.petshop.backend.dto.purchaseorder.PurchaseOrderResponse; import com.petshop.backend.service.PurchaseOrderService; -import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.http.ResponseEntity; @@ -11,7 +10,7 @@ import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/v1/purchase-orders") -@RequiredArgsConstructor + @PreAuthorize("hasRole('ADMIN')") public class PurchaseOrderController { diff --git a/backend/src/main/java/com/petshop/backend/controller/RefundController.java b/src/main/java/com/petshop/backend/controller/RefundController.java similarity index 92% rename from backend/src/main/java/com/petshop/backend/controller/RefundController.java rename to src/main/java/com/petshop/backend/controller/RefundController.java index ef527069..2410f0b5 100644 --- a/backend/src/main/java/com/petshop/backend/controller/RefundController.java +++ b/src/main/java/com/petshop/backend/controller/RefundController.java @@ -4,14 +4,13 @@ import com.petshop.backend.dto.refund.RefundRequest; import com.petshop.backend.dto.refund.RefundResponse; import com.petshop.backend.service.RefundService; import jakarta.validation.Valid; -import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/v1/sales") -@RequiredArgsConstructor + public class RefundController { private final RefundService refundService; diff --git a/backend/src/main/java/com/petshop/backend/controller/SaleController.java b/src/main/java/com/petshop/backend/controller/SaleController.java similarity index 94% rename from backend/src/main/java/com/petshop/backend/controller/SaleController.java rename to src/main/java/com/petshop/backend/controller/SaleController.java index 6b77ba43..71599551 100644 --- a/backend/src/main/java/com/petshop/backend/controller/SaleController.java +++ b/src/main/java/com/petshop/backend/controller/SaleController.java @@ -4,7 +4,6 @@ import com.petshop.backend.dto.sale.SaleRequest; import com.petshop.backend.dto.sale.SaleResponse; import com.petshop.backend.service.SaleService; import jakarta.validation.Valid; -import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.http.HttpStatus; @@ -13,7 +12,7 @@ import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/v1/sales") -@RequiredArgsConstructor + public class SaleController { private final SaleService saleService; diff --git a/backend/src/main/java/com/petshop/backend/controller/ServiceController.java b/src/main/java/com/petshop/backend/controller/ServiceController.java similarity index 96% rename from backend/src/main/java/com/petshop/backend/controller/ServiceController.java rename to src/main/java/com/petshop/backend/controller/ServiceController.java index e1c5415b..979028c9 100644 --- a/backend/src/main/java/com/petshop/backend/controller/ServiceController.java +++ b/src/main/java/com/petshop/backend/controller/ServiceController.java @@ -5,7 +5,6 @@ import com.petshop.backend.dto.service.ServiceRequest; import com.petshop.backend.dto.service.ServiceResponse; import com.petshop.backend.service.ServiceService; import jakarta.validation.Valid; -import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.http.HttpStatus; @@ -14,7 +13,7 @@ import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/v1/services") -@RequiredArgsConstructor + public class ServiceController { private final ServiceService serviceService; diff --git a/backend/src/main/java/com/petshop/backend/controller/SupplierController.java b/src/main/java/com/petshop/backend/controller/SupplierController.java similarity index 97% rename from backend/src/main/java/com/petshop/backend/controller/SupplierController.java rename to src/main/java/com/petshop/backend/controller/SupplierController.java index ebd4e68d..fd9c4622 100644 --- a/backend/src/main/java/com/petshop/backend/controller/SupplierController.java +++ b/src/main/java/com/petshop/backend/controller/SupplierController.java @@ -5,7 +5,6 @@ import com.petshop.backend.dto.supplier.SupplierRequest; import com.petshop.backend.dto.supplier.SupplierResponse; import com.petshop.backend.service.SupplierService; import jakarta.validation.Valid; -import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.http.HttpStatus; @@ -15,7 +14,7 @@ import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/v1/suppliers") -@RequiredArgsConstructor + @PreAuthorize("hasRole('ADMIN')") public class SupplierController { diff --git a/backend/src/main/java/com/petshop/backend/controller/UserController.java b/src/main/java/com/petshop/backend/controller/UserController.java similarity index 96% rename from backend/src/main/java/com/petshop/backend/controller/UserController.java rename to src/main/java/com/petshop/backend/controller/UserController.java index c316dbe0..d6719f2b 100644 --- a/backend/src/main/java/com/petshop/backend/controller/UserController.java +++ b/src/main/java/com/petshop/backend/controller/UserController.java @@ -5,7 +5,6 @@ import com.petshop.backend.dto.user.UserRequest; import com.petshop.backend.dto.user.UserResponse; import com.petshop.backend.service.UserService; import jakarta.validation.Valid; -import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.http.HttpStatus; @@ -15,7 +14,7 @@ import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/v1/users") -@RequiredArgsConstructor + @PreAuthorize("hasRole('ADMIN')") public class UserController { diff --git a/backend/src/main/java/com/petshop/backend/dto/adoption/AdoptionRequest.java b/src/main/java/com/petshop/backend/dto/adoption/AdoptionRequest.java similarity index 96% rename from backend/src/main/java/com/petshop/backend/dto/adoption/AdoptionRequest.java rename to src/main/java/com/petshop/backend/dto/adoption/AdoptionRequest.java index 96113d13..690ba25b 100644 --- a/backend/src/main/java/com/petshop/backend/dto/adoption/AdoptionRequest.java +++ b/src/main/java/com/petshop/backend/dto/adoption/AdoptionRequest.java @@ -2,12 +2,11 @@ package com.petshop.backend.dto.adoption; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Positive; -import lombok.Data; import java.math.BigDecimal; import java.time.LocalDate; -@Data + public class AdoptionRequest { @NotNull(message = "Pet ID is required") private Long petId; diff --git a/backend/src/main/java/com/petshop/backend/dto/adoption/AdoptionResponse.java b/src/main/java/com/petshop/backend/dto/adoption/AdoptionResponse.java similarity index 78% rename from backend/src/main/java/com/petshop/backend/dto/adoption/AdoptionResponse.java rename to src/main/java/com/petshop/backend/dto/adoption/AdoptionResponse.java index 1cf334f7..796a82ff 100644 --- a/backend/src/main/java/com/petshop/backend/dto/adoption/AdoptionResponse.java +++ b/src/main/java/com/petshop/backend/dto/adoption/AdoptionResponse.java @@ -1,16 +1,13 @@ package com.petshop.backend.dto.adoption; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; -@Data -@NoArgsConstructor -@AllArgsConstructor + + + public class AdoptionResponse { private Long id; private Long petId; diff --git a/backend/src/main/java/com/petshop/backend/dto/analytics/DashboardResponse.java b/src/main/java/com/petshop/backend/dto/analytics/DashboardResponse.java similarity index 73% rename from backend/src/main/java/com/petshop/backend/dto/analytics/DashboardResponse.java rename to src/main/java/com/petshop/backend/dto/analytics/DashboardResponse.java index d17967a0..b7d420f8 100644 --- a/backend/src/main/java/com/petshop/backend/dto/analytics/DashboardResponse.java +++ b/src/main/java/com/petshop/backend/dto/analytics/DashboardResponse.java @@ -1,16 +1,13 @@ package com.petshop.backend.dto.analytics; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; import java.math.BigDecimal; import java.util.List; import java.util.Map; -@Data -@NoArgsConstructor -@AllArgsConstructor + + + public class DashboardResponse { private SalesSummary salesSummary; private InventorySummary inventorySummary; @@ -18,9 +15,9 @@ public class DashboardResponse { private List dailySales; } -@Data -@NoArgsConstructor -@AllArgsConstructor + + + class SalesSummary { private BigDecimal totalRevenue; private Long totalSales; @@ -28,18 +25,18 @@ class SalesSummary { private Long totalRefundCount; } -@Data -@NoArgsConstructor -@AllArgsConstructor + + + class InventorySummary { private Long totalProducts; private Long lowStockProducts; private Long outOfStockProducts; } -@Data -@NoArgsConstructor -@AllArgsConstructor + + + class TopProduct { private Long productId; private String productName; @@ -47,9 +44,9 @@ class TopProduct { private BigDecimal revenue; } -@Data -@NoArgsConstructor -@AllArgsConstructor + + + class DailySales { private String date; private BigDecimal revenue; diff --git a/backend/src/main/java/com/petshop/backend/dto/appointment/AppointmentRequest.java b/src/main/java/com/petshop/backend/dto/appointment/AppointmentRequest.java similarity index 97% rename from backend/src/main/java/com/petshop/backend/dto/appointment/AppointmentRequest.java rename to src/main/java/com/petshop/backend/dto/appointment/AppointmentRequest.java index 202ba5ae..2e51532e 100644 --- a/backend/src/main/java/com/petshop/backend/dto/appointment/AppointmentRequest.java +++ b/src/main/java/com/petshop/backend/dto/appointment/AppointmentRequest.java @@ -3,13 +3,12 @@ package com.petshop.backend.dto.appointment; import com.petshop.backend.entity.Appointment; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; -import lombok.Data; import java.time.LocalDate; import java.time.LocalTime; import java.util.List; -@Data + public class AppointmentRequest { @NotNull(message = "Customer ID is required") private Long customerId; diff --git a/backend/src/main/java/com/petshop/backend/dto/appointment/AppointmentResponse.java b/src/main/java/com/petshop/backend/dto/appointment/AppointmentResponse.java similarity index 82% rename from backend/src/main/java/com/petshop/backend/dto/appointment/AppointmentResponse.java rename to src/main/java/com/petshop/backend/dto/appointment/AppointmentResponse.java index fb528f29..d2b54f66 100644 --- a/backend/src/main/java/com/petshop/backend/dto/appointment/AppointmentResponse.java +++ b/src/main/java/com/petshop/backend/dto/appointment/AppointmentResponse.java @@ -1,17 +1,14 @@ package com.petshop.backend.dto.appointment; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.util.List; -@Data -@NoArgsConstructor -@AllArgsConstructor + + + public class AppointmentResponse { private Long id; private Long customerId; diff --git a/backend/src/main/java/com/petshop/backend/dto/auth/LoginRequest.java b/src/main/java/com/petshop/backend/dto/auth/LoginRequest.java similarity index 91% rename from backend/src/main/java/com/petshop/backend/dto/auth/LoginRequest.java rename to src/main/java/com/petshop/backend/dto/auth/LoginRequest.java index c80971c3..3adfd91a 100644 --- a/backend/src/main/java/com/petshop/backend/dto/auth/LoginRequest.java +++ b/src/main/java/com/petshop/backend/dto/auth/LoginRequest.java @@ -1,9 +1,8 @@ package com.petshop.backend.dto.auth; import jakarta.validation.constraints.NotBlank; -import lombok.Data; -@Data + public class LoginRequest { @NotBlank(message = "Username is required") private String username; diff --git a/backend/src/main/java/com/petshop/backend/dto/auth/LoginResponse.java b/src/main/java/com/petshop/backend/dto/auth/LoginResponse.java similarity index 69% rename from backend/src/main/java/com/petshop/backend/dto/auth/LoginResponse.java rename to src/main/java/com/petshop/backend/dto/auth/LoginResponse.java index d65b69f0..67574120 100644 --- a/backend/src/main/java/com/petshop/backend/dto/auth/LoginResponse.java +++ b/src/main/java/com/petshop/backend/dto/auth/LoginResponse.java @@ -1,10 +1,8 @@ package com.petshop.backend.dto.auth; -import lombok.AllArgsConstructor; -import lombok.Data; -@Data -@AllArgsConstructor + + public class LoginResponse { private String token; private String username; diff --git a/backend/src/main/java/com/petshop/backend/dto/auth/UserInfoResponse.java b/src/main/java/com/petshop/backend/dto/auth/UserInfoResponse.java similarity index 71% rename from backend/src/main/java/com/petshop/backend/dto/auth/UserInfoResponse.java rename to src/main/java/com/petshop/backend/dto/auth/UserInfoResponse.java index b96a01b1..9e863cb4 100644 --- a/backend/src/main/java/com/petshop/backend/dto/auth/UserInfoResponse.java +++ b/src/main/java/com/petshop/backend/dto/auth/UserInfoResponse.java @@ -1,10 +1,8 @@ package com.petshop.backend.dto.auth; -import lombok.AllArgsConstructor; -import lombok.Data; -@Data -@AllArgsConstructor + + public class UserInfoResponse { private Long id; private String username; diff --git a/backend/src/main/java/com/petshop/backend/dto/category/CategoryRequest.java b/src/main/java/com/petshop/backend/dto/category/CategoryRequest.java similarity index 90% rename from backend/src/main/java/com/petshop/backend/dto/category/CategoryRequest.java rename to src/main/java/com/petshop/backend/dto/category/CategoryRequest.java index 55794933..19b7d4b4 100644 --- a/backend/src/main/java/com/petshop/backend/dto/category/CategoryRequest.java +++ b/src/main/java/com/petshop/backend/dto/category/CategoryRequest.java @@ -1,9 +1,8 @@ package com.petshop.backend.dto.category; import jakarta.validation.constraints.NotBlank; -import lombok.Data; -@Data + public class CategoryRequest { @NotBlank(message = "Category name is required") private String categoryName; diff --git a/backend/src/main/java/com/petshop/backend/dto/category/CategoryResponse.java b/src/main/java/com/petshop/backend/dto/category/CategoryResponse.java similarity index 67% rename from backend/src/main/java/com/petshop/backend/dto/category/CategoryResponse.java rename to src/main/java/com/petshop/backend/dto/category/CategoryResponse.java index 0f4939a0..758be129 100644 --- a/backend/src/main/java/com/petshop/backend/dto/category/CategoryResponse.java +++ b/src/main/java/com/petshop/backend/dto/category/CategoryResponse.java @@ -1,14 +1,11 @@ package com.petshop.backend.dto.category; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; import java.time.LocalDateTime; -@Data -@NoArgsConstructor -@AllArgsConstructor + + + public class CategoryResponse { private Long id; private String categoryName; diff --git a/backend/src/main/java/com/petshop/backend/dto/common/BulkDeleteRequest.java b/src/main/java/com/petshop/backend/dto/common/BulkDeleteRequest.java similarity index 89% rename from backend/src/main/java/com/petshop/backend/dto/common/BulkDeleteRequest.java rename to src/main/java/com/petshop/backend/dto/common/BulkDeleteRequest.java index 343ecb5e..99391913 100644 --- a/backend/src/main/java/com/petshop/backend/dto/common/BulkDeleteRequest.java +++ b/src/main/java/com/petshop/backend/dto/common/BulkDeleteRequest.java @@ -1,11 +1,10 @@ package com.petshop.backend.dto.common; import jakarta.validation.constraints.NotEmpty; -import lombok.Data; import java.util.List; -@Data + public class BulkDeleteRequest { @NotEmpty(message = "IDs list cannot be empty") private List ids; diff --git a/backend/src/main/java/com/petshop/backend/dto/common/DropdownOption.java b/src/main/java/com/petshop/backend/dto/common/DropdownOption.java similarity index 60% rename from backend/src/main/java/com/petshop/backend/dto/common/DropdownOption.java rename to src/main/java/com/petshop/backend/dto/common/DropdownOption.java index 8fac390c..494fa5a7 100644 --- a/backend/src/main/java/com/petshop/backend/dto/common/DropdownOption.java +++ b/src/main/java/com/petshop/backend/dto/common/DropdownOption.java @@ -1,10 +1,8 @@ package com.petshop.backend.dto.common; -import lombok.AllArgsConstructor; -import lombok.Data; -@Data -@AllArgsConstructor + + public class DropdownOption { private Long id; private String label; diff --git a/backend/src/main/java/com/petshop/backend/dto/customer/CustomerRequest.java b/src/main/java/com/petshop/backend/dto/customer/CustomerRequest.java similarity index 93% rename from backend/src/main/java/com/petshop/backend/dto/customer/CustomerRequest.java rename to src/main/java/com/petshop/backend/dto/customer/CustomerRequest.java index 0319f3ea..5f068b32 100644 --- a/backend/src/main/java/com/petshop/backend/dto/customer/CustomerRequest.java +++ b/src/main/java/com/petshop/backend/dto/customer/CustomerRequest.java @@ -2,9 +2,8 @@ package com.petshop.backend.dto.customer; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; -import lombok.Data; -@Data + public class CustomerRequest { @NotBlank(message = "Customer name is required") private String customerName; diff --git a/backend/src/main/java/com/petshop/backend/dto/customer/CustomerResponse.java b/src/main/java/com/petshop/backend/dto/customer/CustomerResponse.java similarity index 72% rename from backend/src/main/java/com/petshop/backend/dto/customer/CustomerResponse.java rename to src/main/java/com/petshop/backend/dto/customer/CustomerResponse.java index 32c68421..6f929bae 100644 --- a/backend/src/main/java/com/petshop/backend/dto/customer/CustomerResponse.java +++ b/src/main/java/com/petshop/backend/dto/customer/CustomerResponse.java @@ -1,14 +1,11 @@ package com.petshop.backend.dto.customer; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; import java.time.LocalDateTime; -@Data -@NoArgsConstructor -@AllArgsConstructor + + + public class CustomerResponse { private Long id; private String customerName; diff --git a/backend/src/main/java/com/petshop/backend/dto/inventory/InventoryRequest.java b/src/main/java/com/petshop/backend/dto/inventory/InventoryRequest.java similarity index 95% rename from backend/src/main/java/com/petshop/backend/dto/inventory/InventoryRequest.java rename to src/main/java/com/petshop/backend/dto/inventory/InventoryRequest.java index 8e91e494..fb4ff6b9 100644 --- a/backend/src/main/java/com/petshop/backend/dto/inventory/InventoryRequest.java +++ b/src/main/java/com/petshop/backend/dto/inventory/InventoryRequest.java @@ -2,9 +2,8 @@ package com.petshop.backend.dto.inventory; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.PositiveOrZero; -import lombok.Data; -@Data + public class InventoryRequest { @NotNull(message = "Product ID is required") private Long productId; diff --git a/backend/src/main/java/com/petshop/backend/dto/inventory/InventoryResponse.java b/src/main/java/com/petshop/backend/dto/inventory/InventoryResponse.java similarity index 77% rename from backend/src/main/java/com/petshop/backend/dto/inventory/InventoryResponse.java rename to src/main/java/com/petshop/backend/dto/inventory/InventoryResponse.java index 4b5e30d7..feff7600 100644 --- a/backend/src/main/java/com/petshop/backend/dto/inventory/InventoryResponse.java +++ b/src/main/java/com/petshop/backend/dto/inventory/InventoryResponse.java @@ -1,14 +1,11 @@ package com.petshop.backend.dto.inventory; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; import java.time.LocalDateTime; -@Data -@NoArgsConstructor -@AllArgsConstructor + + + public class InventoryResponse { private Long id; private Long productId; diff --git a/backend/src/main/java/com/petshop/backend/dto/pet/PetRequest.java b/src/main/java/com/petshop/backend/dto/pet/PetRequest.java similarity index 96% rename from backend/src/main/java/com/petshop/backend/dto/pet/PetRequest.java rename to src/main/java/com/petshop/backend/dto/pet/PetRequest.java index e5014dd7..202dc6e7 100644 --- a/backend/src/main/java/com/petshop/backend/dto/pet/PetRequest.java +++ b/src/main/java/com/petshop/backend/dto/pet/PetRequest.java @@ -4,11 +4,10 @@ import com.petshop.backend.entity.Pet; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Positive; -import lombok.Data; import java.math.BigDecimal; -@Data + public class PetRequest { @NotBlank(message = "Pet name is required") private String petName; diff --git a/backend/src/main/java/com/petshop/backend/dto/pet/PetResponse.java b/src/main/java/com/petshop/backend/dto/pet/PetResponse.java similarity index 75% rename from backend/src/main/java/com/petshop/backend/dto/pet/PetResponse.java rename to src/main/java/com/petshop/backend/dto/pet/PetResponse.java index 93d64d56..9409b0ad 100644 --- a/backend/src/main/java/com/petshop/backend/dto/pet/PetResponse.java +++ b/src/main/java/com/petshop/backend/dto/pet/PetResponse.java @@ -1,15 +1,12 @@ package com.petshop.backend.dto.pet; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; import java.math.BigDecimal; import java.time.LocalDateTime; -@Data -@NoArgsConstructor -@AllArgsConstructor + + + public class PetResponse { private Long id; private String petName; diff --git a/backend/src/main/java/com/petshop/backend/dto/product/ProductRequest.java b/src/main/java/com/petshop/backend/dto/product/ProductRequest.java similarity index 96% rename from backend/src/main/java/com/petshop/backend/dto/product/ProductRequest.java rename to src/main/java/com/petshop/backend/dto/product/ProductRequest.java index 2ffe720d..c784513d 100644 --- a/backend/src/main/java/com/petshop/backend/dto/product/ProductRequest.java +++ b/src/main/java/com/petshop/backend/dto/product/ProductRequest.java @@ -3,11 +3,10 @@ package com.petshop.backend.dto.product; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Positive; -import lombok.Data; import java.math.BigDecimal; -@Data + public class ProductRequest { @NotBlank(message = "Product name is required") private String productName; diff --git a/backend/src/main/java/com/petshop/backend/dto/product/ProductResponse.java b/src/main/java/com/petshop/backend/dto/product/ProductResponse.java similarity index 76% rename from backend/src/main/java/com/petshop/backend/dto/product/ProductResponse.java rename to src/main/java/com/petshop/backend/dto/product/ProductResponse.java index bc4eaebd..ec6b3ae9 100644 --- a/backend/src/main/java/com/petshop/backend/dto/product/ProductResponse.java +++ b/src/main/java/com/petshop/backend/dto/product/ProductResponse.java @@ -1,15 +1,12 @@ package com.petshop.backend.dto.product; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; import java.math.BigDecimal; import java.time.LocalDateTime; -@Data -@NoArgsConstructor -@AllArgsConstructor + + + public class ProductResponse { private Long id; private String productName; diff --git a/backend/src/main/java/com/petshop/backend/dto/productsupplier/BulkDeleteProductSupplierRequest.java b/src/main/java/com/petshop/backend/dto/productsupplier/BulkDeleteProductSupplierRequest.java similarity index 91% rename from backend/src/main/java/com/petshop/backend/dto/productsupplier/BulkDeleteProductSupplierRequest.java rename to src/main/java/com/petshop/backend/dto/productsupplier/BulkDeleteProductSupplierRequest.java index dfa95bb5..97704bd8 100644 --- a/backend/src/main/java/com/petshop/backend/dto/productsupplier/BulkDeleteProductSupplierRequest.java +++ b/src/main/java/com/petshop/backend/dto/productsupplier/BulkDeleteProductSupplierRequest.java @@ -1,11 +1,10 @@ package com.petshop.backend.dto.productsupplier; import jakarta.validation.constraints.NotEmpty; -import lombok.Data; import java.util.List; -@Data + public class BulkDeleteProductSupplierRequest { @NotEmpty(message = "Keys list cannot be empty") private List keys; diff --git a/backend/src/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierKey.java b/src/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierKey.java similarity index 89% rename from backend/src/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierKey.java rename to src/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierKey.java index 3826b39c..ca1c0c96 100644 --- a/backend/src/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierKey.java +++ b/src/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierKey.java @@ -1,11 +1,10 @@ package com.petshop.backend.dto.productsupplier; import jakarta.validation.constraints.NotEmpty; -import lombok.Data; import java.util.List; -@Data + public class ProductSupplierKey { private Long productId; private Long supplierId; diff --git a/backend/src/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierRequest.java b/src/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierRequest.java similarity index 96% rename from backend/src/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierRequest.java rename to src/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierRequest.java index 68263597..7e0ef87a 100644 --- a/backend/src/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierRequest.java +++ b/src/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierRequest.java @@ -3,11 +3,10 @@ package com.petshop.backend.dto.productsupplier; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Positive; import jakarta.validation.constraints.PositiveOrZero; -import lombok.Data; import java.math.BigDecimal; -@Data + public class ProductSupplierRequest { @NotNull(message = "Product ID is required") private Long productId; diff --git a/backend/src/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierResponse.java b/src/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierResponse.java similarity index 77% rename from backend/src/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierResponse.java rename to src/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierResponse.java index 371fd34b..49535076 100644 --- a/backend/src/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierResponse.java +++ b/src/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierResponse.java @@ -1,15 +1,12 @@ package com.petshop.backend.dto.productsupplier; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; import java.math.BigDecimal; import java.time.LocalDateTime; -@Data -@NoArgsConstructor -@AllArgsConstructor + + + public class ProductSupplierResponse { private Long productId; private String productName; diff --git a/backend/src/main/java/com/petshop/backend/dto/purchaseorder/PurchaseOrderResponse.java b/src/main/java/com/petshop/backend/dto/purchaseorder/PurchaseOrderResponse.java similarity index 81% rename from backend/src/main/java/com/petshop/backend/dto/purchaseorder/PurchaseOrderResponse.java rename to src/main/java/com/petshop/backend/dto/purchaseorder/PurchaseOrderResponse.java index a7e68041..d6f214c2 100644 --- a/backend/src/main/java/com/petshop/backend/dto/purchaseorder/PurchaseOrderResponse.java +++ b/src/main/java/com/petshop/backend/dto/purchaseorder/PurchaseOrderResponse.java @@ -1,17 +1,14 @@ package com.petshop.backend.dto.purchaseorder; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.List; -@Data -@NoArgsConstructor -@AllArgsConstructor + + + public class PurchaseOrderResponse { private Long id; private Long supplierId; @@ -25,9 +22,9 @@ public class PurchaseOrderResponse { private LocalDateTime createdAt; private LocalDateTime updatedAt; - @Data - @NoArgsConstructor - @AllArgsConstructor + + + public static class PurchaseOrderItemResponse { private Long id; private Long productId; diff --git a/backend/src/main/java/com/petshop/backend/dto/refund/RefundItemRequest.java b/src/main/java/com/petshop/backend/dto/refund/RefundItemRequest.java similarity index 93% rename from backend/src/main/java/com/petshop/backend/dto/refund/RefundItemRequest.java rename to src/main/java/com/petshop/backend/dto/refund/RefundItemRequest.java index f972407c..96ed87f8 100644 --- a/backend/src/main/java/com/petshop/backend/dto/refund/RefundItemRequest.java +++ b/src/main/java/com/petshop/backend/dto/refund/RefundItemRequest.java @@ -2,9 +2,8 @@ package com.petshop.backend.dto.refund; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Positive; -import lombok.Data; -@Data + public class RefundItemRequest { @NotNull(message = "Sale item ID is required") private Long saleItemId; diff --git a/backend/src/main/java/com/petshop/backend/dto/refund/RefundRequest.java b/src/main/java/com/petshop/backend/dto/refund/RefundRequest.java similarity index 94% rename from backend/src/main/java/com/petshop/backend/dto/refund/RefundRequest.java rename to src/main/java/com/petshop/backend/dto/refund/RefundRequest.java index 1abf9c23..677c3439 100644 --- a/backend/src/main/java/com/petshop/backend/dto/refund/RefundRequest.java +++ b/src/main/java/com/petshop/backend/dto/refund/RefundRequest.java @@ -4,11 +4,10 @@ import jakarta.validation.Valid; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Positive; -import lombok.Data; import java.util.List; -@Data + public class RefundRequest { @NotEmpty(message = "At least one item is required") @Valid diff --git a/backend/src/main/java/com/petshop/backend/dto/refund/RefundResponse.java b/src/main/java/com/petshop/backend/dto/refund/RefundResponse.java similarity index 79% rename from backend/src/main/java/com/petshop/backend/dto/refund/RefundResponse.java rename to src/main/java/com/petshop/backend/dto/refund/RefundResponse.java index 4ef1caa4..2f031b65 100644 --- a/backend/src/main/java/com/petshop/backend/dto/refund/RefundResponse.java +++ b/src/main/java/com/petshop/backend/dto/refund/RefundResponse.java @@ -1,16 +1,13 @@ package com.petshop.backend.dto.refund; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; import java.math.BigDecimal; import java.time.LocalDateTime; import java.util.List; -@Data -@NoArgsConstructor -@AllArgsConstructor + + + public class RefundResponse { private Long id; private Long saleId; @@ -22,9 +19,9 @@ public class RefundResponse { private List items; private LocalDateTime createdAt; - @Data - @NoArgsConstructor - @AllArgsConstructor + + + public static class RefundItemResponse { private Long id; private Long saleItemId; diff --git a/backend/src/main/java/com/petshop/backend/dto/sale/SaleItemRequest.java b/src/main/java/com/petshop/backend/dto/sale/SaleItemRequest.java similarity index 93% rename from backend/src/main/java/com/petshop/backend/dto/sale/SaleItemRequest.java rename to src/main/java/com/petshop/backend/dto/sale/SaleItemRequest.java index 60d5596e..0722fc77 100644 --- a/backend/src/main/java/com/petshop/backend/dto/sale/SaleItemRequest.java +++ b/src/main/java/com/petshop/backend/dto/sale/SaleItemRequest.java @@ -2,9 +2,8 @@ package com.petshop.backend.dto.sale; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Positive; -import lombok.Data; -@Data + public class SaleItemRequest { @NotNull(message = "Product ID is required") private Long productId; diff --git a/backend/src/main/java/com/petshop/backend/dto/sale/SaleRequest.java b/src/main/java/com/petshop/backend/dto/sale/SaleRequest.java similarity index 95% rename from backend/src/main/java/com/petshop/backend/dto/sale/SaleRequest.java rename to src/main/java/com/petshop/backend/dto/sale/SaleRequest.java index 220aaa39..26b22952 100644 --- a/backend/src/main/java/com/petshop/backend/dto/sale/SaleRequest.java +++ b/src/main/java/com/petshop/backend/dto/sale/SaleRequest.java @@ -4,12 +4,11 @@ import jakarta.validation.Valid; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Positive; -import lombok.Data; import java.math.BigDecimal; import java.util.List; -@Data + public class SaleRequest { private Long customerId; diff --git a/backend/src/main/java/com/petshop/backend/dto/sale/SaleResponse.java b/src/main/java/com/petshop/backend/dto/sale/SaleResponse.java similarity index 82% rename from backend/src/main/java/com/petshop/backend/dto/sale/SaleResponse.java rename to src/main/java/com/petshop/backend/dto/sale/SaleResponse.java index e05063ef..d1cf1c45 100644 --- a/backend/src/main/java/com/petshop/backend/dto/sale/SaleResponse.java +++ b/src/main/java/com/petshop/backend/dto/sale/SaleResponse.java @@ -1,16 +1,13 @@ package com.petshop.backend.dto.sale; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; import java.math.BigDecimal; import java.time.LocalDateTime; import java.util.List; -@Data -@NoArgsConstructor -@AllArgsConstructor + + + public class SaleResponse { private Long id; private LocalDateTime saleDate; @@ -28,9 +25,9 @@ public class SaleResponse { private List items; private LocalDateTime createdAt; - @Data - @NoArgsConstructor - @AllArgsConstructor + + + public static class SaleItemResponse { private Long id; private Long productId; diff --git a/backend/src/main/java/com/petshop/backend/dto/service/ServiceRequest.java b/src/main/java/com/petshop/backend/dto/service/ServiceRequest.java similarity index 96% rename from backend/src/main/java/com/petshop/backend/dto/service/ServiceRequest.java rename to src/main/java/com/petshop/backend/dto/service/ServiceRequest.java index 419b82ab..b5c6bbce 100644 --- a/backend/src/main/java/com/petshop/backend/dto/service/ServiceRequest.java +++ b/src/main/java/com/petshop/backend/dto/service/ServiceRequest.java @@ -3,11 +3,10 @@ package com.petshop.backend.dto.service; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Positive; -import lombok.Data; import java.math.BigDecimal; -@Data + public class ServiceRequest { @NotBlank(message = "Service name is required") private String serviceName; diff --git a/backend/src/main/java/com/petshop/backend/dto/service/ServiceResponse.java b/src/main/java/com/petshop/backend/dto/service/ServiceResponse.java similarity index 75% rename from backend/src/main/java/com/petshop/backend/dto/service/ServiceResponse.java rename to src/main/java/com/petshop/backend/dto/service/ServiceResponse.java index 5f786c03..992d8fdc 100644 --- a/backend/src/main/java/com/petshop/backend/dto/service/ServiceResponse.java +++ b/src/main/java/com/petshop/backend/dto/service/ServiceResponse.java @@ -1,15 +1,12 @@ package com.petshop.backend.dto.service; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; import java.math.BigDecimal; import java.time.LocalDateTime; -@Data -@NoArgsConstructor -@AllArgsConstructor + + + public class ServiceResponse { private Long id; private String serviceName; diff --git a/backend/src/main/java/com/petshop/backend/dto/supplier/SupplierRequest.java b/src/main/java/com/petshop/backend/dto/supplier/SupplierRequest.java similarity index 94% rename from backend/src/main/java/com/petshop/backend/dto/supplier/SupplierRequest.java rename to src/main/java/com/petshop/backend/dto/supplier/SupplierRequest.java index 18d0c27a..36081cf2 100644 --- a/backend/src/main/java/com/petshop/backend/dto/supplier/SupplierRequest.java +++ b/src/main/java/com/petshop/backend/dto/supplier/SupplierRequest.java @@ -2,9 +2,8 @@ package com.petshop.backend.dto.supplier; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; -import lombok.Data; -@Data + public class SupplierRequest { @NotBlank(message = "Supplier name is required") private String supplierName; diff --git a/backend/src/main/java/com/petshop/backend/dto/supplier/SupplierResponse.java b/src/main/java/com/petshop/backend/dto/supplier/SupplierResponse.java similarity index 75% rename from backend/src/main/java/com/petshop/backend/dto/supplier/SupplierResponse.java rename to src/main/java/com/petshop/backend/dto/supplier/SupplierResponse.java index 71b07142..8af255d1 100644 --- a/backend/src/main/java/com/petshop/backend/dto/supplier/SupplierResponse.java +++ b/src/main/java/com/petshop/backend/dto/supplier/SupplierResponse.java @@ -1,14 +1,11 @@ package com.petshop.backend.dto.supplier; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; import java.time.LocalDateTime; -@Data -@NoArgsConstructor -@AllArgsConstructor + + + public class SupplierResponse { private Long id; private String supplierName; diff --git a/backend/src/main/java/com/petshop/backend/dto/user/UserRequest.java b/src/main/java/com/petshop/backend/dto/user/UserRequest.java similarity index 96% rename from backend/src/main/java/com/petshop/backend/dto/user/UserRequest.java rename to src/main/java/com/petshop/backend/dto/user/UserRequest.java index 5018a580..ca68354b 100644 --- a/backend/src/main/java/com/petshop/backend/dto/user/UserRequest.java +++ b/src/main/java/com/petshop/backend/dto/user/UserRequest.java @@ -5,9 +5,8 @@ import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; -import lombok.Data; -@Data + public class UserRequest { @NotBlank(message = "Username is required") @Size(min = 3, max = 50, message = "Username must be between 3 and 50 characters") diff --git a/backend/src/main/java/com/petshop/backend/dto/user/UserResponse.java b/src/main/java/com/petshop/backend/dto/user/UserResponse.java similarity index 71% rename from backend/src/main/java/com/petshop/backend/dto/user/UserResponse.java rename to src/main/java/com/petshop/backend/dto/user/UserResponse.java index 7d929049..0fac71c9 100644 --- a/backend/src/main/java/com/petshop/backend/dto/user/UserResponse.java +++ b/src/main/java/com/petshop/backend/dto/user/UserResponse.java @@ -1,14 +1,11 @@ package com.petshop.backend.dto.user; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; import java.time.LocalDateTime; -@Data -@NoArgsConstructor -@AllArgsConstructor + + + public class UserResponse { private Long id; private String username; diff --git a/backend/src/main/java/com/petshop/backend/entity/Adoption.java b/src/main/java/com/petshop/backend/entity/Adoption.java similarity index 89% rename from backend/src/main/java/com/petshop/backend/entity/Adoption.java rename to src/main/java/com/petshop/backend/entity/Adoption.java index f7842f82..51366e27 100644 --- a/backend/src/main/java/com/petshop/backend/entity/Adoption.java +++ b/src/main/java/com/petshop/backend/entity/Adoption.java @@ -1,9 +1,6 @@ package com.petshop.backend.entity; import jakarta.persistence.*; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; import org.hibernate.annotations.CreationTimestamp; import org.hibernate.annotations.UpdateTimestamp; @@ -13,9 +10,9 @@ import java.time.LocalDateTime; @Entity @Table(name = "adoptions") -@Data -@NoArgsConstructor -@AllArgsConstructor + + + public class Adoption { @Id diff --git a/backend/src/main/java/com/petshop/backend/entity/Appointment.java b/src/main/java/com/petshop/backend/entity/Appointment.java similarity index 92% rename from backend/src/main/java/com/petshop/backend/entity/Appointment.java rename to src/main/java/com/petshop/backend/entity/Appointment.java index b1810b48..60b6d1c0 100644 --- a/backend/src/main/java/com/petshop/backend/entity/Appointment.java +++ b/src/main/java/com/petshop/backend/entity/Appointment.java @@ -1,9 +1,6 @@ package com.petshop.backend.entity; import jakarta.persistence.*; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; import org.hibernate.annotations.CreationTimestamp; import org.hibernate.annotations.UpdateTimestamp; @@ -15,9 +12,9 @@ import java.util.Set; @Entity @Table(name = "appointments") -@Data -@NoArgsConstructor -@AllArgsConstructor + + + public class Appointment { @Id diff --git a/backend/src/main/java/com/petshop/backend/entity/Category.java b/src/main/java/com/petshop/backend/entity/Category.java similarity index 85% rename from backend/src/main/java/com/petshop/backend/entity/Category.java rename to src/main/java/com/petshop/backend/entity/Category.java index e21289a4..7380c6d3 100644 --- a/backend/src/main/java/com/petshop/backend/entity/Category.java +++ b/src/main/java/com/petshop/backend/entity/Category.java @@ -1,9 +1,6 @@ package com.petshop.backend.entity; import jakarta.persistence.*; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; import org.hibernate.annotations.CreationTimestamp; import org.hibernate.annotations.UpdateTimestamp; @@ -11,9 +8,9 @@ import java.time.LocalDateTime; @Entity @Table(name = "categories") -@Data -@NoArgsConstructor -@AllArgsConstructor + + + public class Category { @Id diff --git a/backend/src/main/java/com/petshop/backend/entity/Customer.java b/src/main/java/com/petshop/backend/entity/Customer.java similarity index 87% rename from backend/src/main/java/com/petshop/backend/entity/Customer.java rename to src/main/java/com/petshop/backend/entity/Customer.java index ab10b792..496cd705 100644 --- a/backend/src/main/java/com/petshop/backend/entity/Customer.java +++ b/src/main/java/com/petshop/backend/entity/Customer.java @@ -1,9 +1,6 @@ package com.petshop.backend.entity; import jakarta.persistence.*; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; import org.hibernate.annotations.CreationTimestamp; import org.hibernate.annotations.UpdateTimestamp; @@ -11,9 +8,9 @@ import java.time.LocalDateTime; @Entity @Table(name = "customers") -@Data -@NoArgsConstructor -@AllArgsConstructor + + + public class Customer { @Id diff --git a/backend/src/main/java/com/petshop/backend/entity/Inventory.java b/src/main/java/com/petshop/backend/entity/Inventory.java similarity index 89% rename from backend/src/main/java/com/petshop/backend/entity/Inventory.java rename to src/main/java/com/petshop/backend/entity/Inventory.java index 66c56b9b..bdcd42ef 100644 --- a/backend/src/main/java/com/petshop/backend/entity/Inventory.java +++ b/src/main/java/com/petshop/backend/entity/Inventory.java @@ -1,9 +1,6 @@ package com.petshop.backend.entity; import jakarta.persistence.*; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; import org.hibernate.annotations.CreationTimestamp; import org.hibernate.annotations.UpdateTimestamp; @@ -13,9 +10,9 @@ import java.time.LocalDateTime; @Table(name = "inventory", uniqueConstraints = { @UniqueConstraint(name = "unique_product_store", columnNames = {"product_id", "store_id"}) }) -@Data -@NoArgsConstructor -@AllArgsConstructor + + + public class Inventory { @Id diff --git a/backend/src/main/java/com/petshop/backend/entity/Pet.java b/src/main/java/com/petshop/backend/entity/Pet.java similarity index 90% rename from backend/src/main/java/com/petshop/backend/entity/Pet.java rename to src/main/java/com/petshop/backend/entity/Pet.java index de7bf5ab..8e0587ba 100644 --- a/backend/src/main/java/com/petshop/backend/entity/Pet.java +++ b/src/main/java/com/petshop/backend/entity/Pet.java @@ -1,9 +1,6 @@ package com.petshop.backend.entity; import jakarta.persistence.*; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; import org.hibernate.annotations.CreationTimestamp; import org.hibernate.annotations.UpdateTimestamp; @@ -12,9 +9,9 @@ import java.time.LocalDateTime; @Entity @Table(name = "pets") -@Data -@NoArgsConstructor -@AllArgsConstructor + + + public class Pet { @Id diff --git a/backend/src/main/java/com/petshop/backend/entity/Product.java b/src/main/java/com/petshop/backend/entity/Product.java similarity index 89% rename from backend/src/main/java/com/petshop/backend/entity/Product.java rename to src/main/java/com/petshop/backend/entity/Product.java index 8ccc72a1..49ebf645 100644 --- a/backend/src/main/java/com/petshop/backend/entity/Product.java +++ b/src/main/java/com/petshop/backend/entity/Product.java @@ -1,9 +1,6 @@ package com.petshop.backend.entity; import jakarta.persistence.*; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; import org.hibernate.annotations.CreationTimestamp; import org.hibernate.annotations.UpdateTimestamp; @@ -12,9 +9,9 @@ import java.time.LocalDateTime; @Entity @Table(name = "products") -@Data -@NoArgsConstructor -@AllArgsConstructor + + + public class Product { @Id diff --git a/backend/src/main/java/com/petshop/backend/entity/ProductSupplier.java b/src/main/java/com/petshop/backend/entity/ProductSupplier.java similarity index 86% rename from backend/src/main/java/com/petshop/backend/entity/ProductSupplier.java rename to src/main/java/com/petshop/backend/entity/ProductSupplier.java index 9a1f56c8..bb157386 100644 --- a/backend/src/main/java/com/petshop/backend/entity/ProductSupplier.java +++ b/src/main/java/com/petshop/backend/entity/ProductSupplier.java @@ -1,9 +1,6 @@ package com.petshop.backend.entity; import jakarta.persistence.*; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; import org.hibernate.annotations.CreationTimestamp; import org.hibernate.annotations.UpdateTimestamp; @@ -13,9 +10,9 @@ import java.time.LocalDateTime; @Entity @Table(name = "product_suppliers") -@Data -@NoArgsConstructor -@AllArgsConstructor + + + @IdClass(ProductSupplier.ProductSupplierId.class) public class ProductSupplier { @@ -46,9 +43,9 @@ public class ProductSupplier { @Column(name = "updated_at") private LocalDateTime updatedAt; - @Data - @NoArgsConstructor - @AllArgsConstructor + + + public static class ProductSupplierId implements Serializable { private Long product; private Long supplier; diff --git a/backend/src/main/java/com/petshop/backend/entity/PurchaseOrder.java b/src/main/java/com/petshop/backend/entity/PurchaseOrder.java similarity index 91% rename from backend/src/main/java/com/petshop/backend/entity/PurchaseOrder.java rename to src/main/java/com/petshop/backend/entity/PurchaseOrder.java index cbeede55..3112a1aa 100644 --- a/backend/src/main/java/com/petshop/backend/entity/PurchaseOrder.java +++ b/src/main/java/com/petshop/backend/entity/PurchaseOrder.java @@ -1,9 +1,6 @@ package com.petshop.backend.entity; import jakarta.persistence.*; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; import org.hibernate.annotations.CreationTimestamp; import org.hibernate.annotations.UpdateTimestamp; @@ -15,9 +12,9 @@ import java.util.List; @Entity @Table(name = "purchase_orders") -@Data -@NoArgsConstructor -@AllArgsConstructor + + + public class PurchaseOrder { @Id diff --git a/backend/src/main/java/com/petshop/backend/entity/PurchaseOrderItem.java b/src/main/java/com/petshop/backend/entity/PurchaseOrderItem.java similarity index 84% rename from backend/src/main/java/com/petshop/backend/entity/PurchaseOrderItem.java rename to src/main/java/com/petshop/backend/entity/PurchaseOrderItem.java index 4aee2b49..f6485616 100644 --- a/backend/src/main/java/com/petshop/backend/entity/PurchaseOrderItem.java +++ b/src/main/java/com/petshop/backend/entity/PurchaseOrderItem.java @@ -1,17 +1,14 @@ package com.petshop.backend.entity; import jakarta.persistence.*; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; import java.math.BigDecimal; @Entity @Table(name = "purchase_order_items") -@Data -@NoArgsConstructor -@AllArgsConstructor + + + public class PurchaseOrderItem { @Id diff --git a/backend/src/main/java/com/petshop/backend/entity/Refund.java b/src/main/java/com/petshop/backend/entity/Refund.java similarity index 89% rename from backend/src/main/java/com/petshop/backend/entity/Refund.java rename to src/main/java/com/petshop/backend/entity/Refund.java index 70a7b1f2..48297ec1 100644 --- a/backend/src/main/java/com/petshop/backend/entity/Refund.java +++ b/src/main/java/com/petshop/backend/entity/Refund.java @@ -1,9 +1,6 @@ package com.petshop.backend.entity; import jakarta.persistence.*; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; import org.hibernate.annotations.CreationTimestamp; import java.math.BigDecimal; @@ -13,9 +10,9 @@ import java.util.List; @Entity @Table(name = "refunds") -@Data -@NoArgsConstructor -@AllArgsConstructor + + + public class Refund { @Id diff --git a/backend/src/main/java/com/petshop/backend/entity/RefundItem.java b/src/main/java/com/petshop/backend/entity/RefundItem.java similarity index 82% rename from backend/src/main/java/com/petshop/backend/entity/RefundItem.java rename to src/main/java/com/petshop/backend/entity/RefundItem.java index 2f34b537..59826733 100644 --- a/backend/src/main/java/com/petshop/backend/entity/RefundItem.java +++ b/src/main/java/com/petshop/backend/entity/RefundItem.java @@ -1,17 +1,14 @@ package com.petshop.backend.entity; import jakarta.persistence.*; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; import java.math.BigDecimal; @Entity @Table(name = "refund_items") -@Data -@NoArgsConstructor -@AllArgsConstructor + + + public class RefundItem { @Id diff --git a/backend/src/main/java/com/petshop/backend/entity/Sale.java b/src/main/java/com/petshop/backend/entity/Sale.java similarity index 91% rename from backend/src/main/java/com/petshop/backend/entity/Sale.java rename to src/main/java/com/petshop/backend/entity/Sale.java index 3f46562c..29ccff01 100644 --- a/backend/src/main/java/com/petshop/backend/entity/Sale.java +++ b/src/main/java/com/petshop/backend/entity/Sale.java @@ -1,9 +1,6 @@ package com.petshop.backend.entity; import jakarta.persistence.*; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; import org.hibernate.annotations.CreationTimestamp; import java.math.BigDecimal; @@ -13,9 +10,9 @@ import java.util.List; @Entity @Table(name = "sales") -@Data -@NoArgsConstructor -@AllArgsConstructor + + + public class Sale { @Id diff --git a/backend/src/main/java/com/petshop/backend/entity/SaleItem.java b/src/main/java/com/petshop/backend/entity/SaleItem.java similarity index 84% rename from backend/src/main/java/com/petshop/backend/entity/SaleItem.java rename to src/main/java/com/petshop/backend/entity/SaleItem.java index 012f706b..c5f183d9 100644 --- a/backend/src/main/java/com/petshop/backend/entity/SaleItem.java +++ b/src/main/java/com/petshop/backend/entity/SaleItem.java @@ -1,17 +1,14 @@ package com.petshop.backend.entity; import jakarta.persistence.*; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; import java.math.BigDecimal; @Entity @Table(name = "sale_items") -@Data -@NoArgsConstructor -@AllArgsConstructor + + + public class SaleItem { @Id diff --git a/backend/src/main/java/com/petshop/backend/entity/Service.java b/src/main/java/com/petshop/backend/entity/Service.java similarity index 89% rename from backend/src/main/java/com/petshop/backend/entity/Service.java rename to src/main/java/com/petshop/backend/entity/Service.java index 2373239b..7c293e78 100644 --- a/backend/src/main/java/com/petshop/backend/entity/Service.java +++ b/src/main/java/com/petshop/backend/entity/Service.java @@ -1,9 +1,6 @@ package com.petshop.backend.entity; import jakarta.persistence.*; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; import org.hibernate.annotations.CreationTimestamp; import org.hibernate.annotations.UpdateTimestamp; @@ -12,9 +9,9 @@ import java.time.LocalDateTime; @Entity @Table(name = "services") -@Data -@NoArgsConstructor -@AllArgsConstructor + + + public class Service { @Id diff --git a/backend/src/main/java/com/petshop/backend/entity/Store.java b/src/main/java/com/petshop/backend/entity/Store.java similarity index 81% rename from backend/src/main/java/com/petshop/backend/entity/Store.java rename to src/main/java/com/petshop/backend/entity/Store.java index fbbd0f21..8f9befe2 100644 --- a/backend/src/main/java/com/petshop/backend/entity/Store.java +++ b/src/main/java/com/petshop/backend/entity/Store.java @@ -1,18 +1,15 @@ package com.petshop.backend.entity; import jakarta.persistence.*; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; import org.hibernate.annotations.CreationTimestamp; import java.time.LocalDateTime; @Entity @Table(name = "stores") -@Data -@NoArgsConstructor -@AllArgsConstructor + + + public class Store { @Id diff --git a/backend/src/main/java/com/petshop/backend/entity/Supplier.java b/src/main/java/com/petshop/backend/entity/Supplier.java similarity index 89% rename from backend/src/main/java/com/petshop/backend/entity/Supplier.java rename to src/main/java/com/petshop/backend/entity/Supplier.java index 57f66bcf..3aa5f66d 100644 --- a/backend/src/main/java/com/petshop/backend/entity/Supplier.java +++ b/src/main/java/com/petshop/backend/entity/Supplier.java @@ -1,9 +1,6 @@ package com.petshop.backend.entity; import jakarta.persistence.*; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; import org.hibernate.annotations.CreationTimestamp; import org.hibernate.annotations.UpdateTimestamp; @@ -11,9 +8,9 @@ import java.time.LocalDateTime; @Entity @Table(name = "suppliers") -@Data -@NoArgsConstructor -@AllArgsConstructor + + + public class Supplier { @Id diff --git a/backend/src/main/java/com/petshop/backend/entity/User.java b/src/main/java/com/petshop/backend/entity/User.java similarity index 88% rename from backend/src/main/java/com/petshop/backend/entity/User.java rename to src/main/java/com/petshop/backend/entity/User.java index 5ac817f6..22674b64 100644 --- a/backend/src/main/java/com/petshop/backend/entity/User.java +++ b/src/main/java/com/petshop/backend/entity/User.java @@ -1,9 +1,6 @@ package com.petshop.backend.entity; import jakarta.persistence.*; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; import org.hibernate.annotations.CreationTimestamp; import org.hibernate.annotations.UpdateTimestamp; @@ -11,9 +8,9 @@ import java.time.LocalDateTime; @Entity @Table(name = "users") -@Data -@NoArgsConstructor -@AllArgsConstructor + + + public class User { @Id diff --git a/backend/src/main/java/com/petshop/backend/exception/BusinessException.java b/src/main/java/com/petshop/backend/exception/BusinessException.java similarity index 100% rename from backend/src/main/java/com/petshop/backend/exception/BusinessException.java rename to src/main/java/com/petshop/backend/exception/BusinessException.java diff --git a/backend/src/main/java/com/petshop/backend/exception/GlobalExceptionHandler.java b/src/main/java/com/petshop/backend/exception/GlobalExceptionHandler.java similarity index 100% rename from backend/src/main/java/com/petshop/backend/exception/GlobalExceptionHandler.java rename to src/main/java/com/petshop/backend/exception/GlobalExceptionHandler.java diff --git a/backend/src/main/java/com/petshop/backend/exception/ResourceNotFoundException.java b/src/main/java/com/petshop/backend/exception/ResourceNotFoundException.java similarity index 100% rename from backend/src/main/java/com/petshop/backend/exception/ResourceNotFoundException.java rename to src/main/java/com/petshop/backend/exception/ResourceNotFoundException.java diff --git a/backend/src/main/java/com/petshop/backend/repository/AdoptionRepository.java b/src/main/java/com/petshop/backend/repository/AdoptionRepository.java similarity index 100% rename from backend/src/main/java/com/petshop/backend/repository/AdoptionRepository.java rename to src/main/java/com/petshop/backend/repository/AdoptionRepository.java diff --git a/backend/src/main/java/com/petshop/backend/repository/AppointmentRepository.java b/src/main/java/com/petshop/backend/repository/AppointmentRepository.java similarity index 100% rename from backend/src/main/java/com/petshop/backend/repository/AppointmentRepository.java rename to src/main/java/com/petshop/backend/repository/AppointmentRepository.java diff --git a/backend/src/main/java/com/petshop/backend/repository/CategoryRepository.java b/src/main/java/com/petshop/backend/repository/CategoryRepository.java similarity index 100% rename from backend/src/main/java/com/petshop/backend/repository/CategoryRepository.java rename to src/main/java/com/petshop/backend/repository/CategoryRepository.java diff --git a/backend/src/main/java/com/petshop/backend/repository/CustomerRepository.java b/src/main/java/com/petshop/backend/repository/CustomerRepository.java similarity index 100% rename from backend/src/main/java/com/petshop/backend/repository/CustomerRepository.java rename to src/main/java/com/petshop/backend/repository/CustomerRepository.java diff --git a/backend/src/main/java/com/petshop/backend/repository/InventoryRepository.java b/src/main/java/com/petshop/backend/repository/InventoryRepository.java similarity index 100% rename from backend/src/main/java/com/petshop/backend/repository/InventoryRepository.java rename to src/main/java/com/petshop/backend/repository/InventoryRepository.java diff --git a/backend/src/main/java/com/petshop/backend/repository/PetRepository.java b/src/main/java/com/petshop/backend/repository/PetRepository.java similarity index 100% rename from backend/src/main/java/com/petshop/backend/repository/PetRepository.java rename to src/main/java/com/petshop/backend/repository/PetRepository.java diff --git a/backend/src/main/java/com/petshop/backend/repository/ProductRepository.java b/src/main/java/com/petshop/backend/repository/ProductRepository.java similarity index 100% rename from backend/src/main/java/com/petshop/backend/repository/ProductRepository.java rename to src/main/java/com/petshop/backend/repository/ProductRepository.java diff --git a/backend/src/main/java/com/petshop/backend/repository/ProductSupplierRepository.java b/src/main/java/com/petshop/backend/repository/ProductSupplierRepository.java similarity index 100% rename from backend/src/main/java/com/petshop/backend/repository/ProductSupplierRepository.java rename to src/main/java/com/petshop/backend/repository/ProductSupplierRepository.java diff --git a/backend/src/main/java/com/petshop/backend/repository/PurchaseOrderRepository.java b/src/main/java/com/petshop/backend/repository/PurchaseOrderRepository.java similarity index 100% rename from backend/src/main/java/com/petshop/backend/repository/PurchaseOrderRepository.java rename to src/main/java/com/petshop/backend/repository/PurchaseOrderRepository.java diff --git a/backend/src/main/java/com/petshop/backend/repository/RefundRepository.java b/src/main/java/com/petshop/backend/repository/RefundRepository.java similarity index 100% rename from backend/src/main/java/com/petshop/backend/repository/RefundRepository.java rename to src/main/java/com/petshop/backend/repository/RefundRepository.java diff --git a/backend/src/main/java/com/petshop/backend/repository/SaleItemRepository.java b/src/main/java/com/petshop/backend/repository/SaleItemRepository.java similarity index 100% rename from backend/src/main/java/com/petshop/backend/repository/SaleItemRepository.java rename to src/main/java/com/petshop/backend/repository/SaleItemRepository.java diff --git a/backend/src/main/java/com/petshop/backend/repository/SaleRepository.java b/src/main/java/com/petshop/backend/repository/SaleRepository.java similarity index 100% rename from backend/src/main/java/com/petshop/backend/repository/SaleRepository.java rename to src/main/java/com/petshop/backend/repository/SaleRepository.java diff --git a/backend/src/main/java/com/petshop/backend/repository/ServiceRepository.java b/src/main/java/com/petshop/backend/repository/ServiceRepository.java similarity index 100% rename from backend/src/main/java/com/petshop/backend/repository/ServiceRepository.java rename to src/main/java/com/petshop/backend/repository/ServiceRepository.java diff --git a/backend/src/main/java/com/petshop/backend/repository/StoreRepository.java b/src/main/java/com/petshop/backend/repository/StoreRepository.java similarity index 100% rename from backend/src/main/java/com/petshop/backend/repository/StoreRepository.java rename to src/main/java/com/petshop/backend/repository/StoreRepository.java diff --git a/backend/src/main/java/com/petshop/backend/repository/SupplierRepository.java b/src/main/java/com/petshop/backend/repository/SupplierRepository.java similarity index 100% rename from backend/src/main/java/com/petshop/backend/repository/SupplierRepository.java rename to src/main/java/com/petshop/backend/repository/SupplierRepository.java diff --git a/backend/src/main/java/com/petshop/backend/repository/UserRepository.java b/src/main/java/com/petshop/backend/repository/UserRepository.java similarity index 100% rename from backend/src/main/java/com/petshop/backend/repository/UserRepository.java rename to src/main/java/com/petshop/backend/repository/UserRepository.java diff --git a/backend/src/main/java/com/petshop/backend/security/JwtAuthenticationFilter.java b/src/main/java/com/petshop/backend/security/JwtAuthenticationFilter.java similarity index 97% rename from backend/src/main/java/com/petshop/backend/security/JwtAuthenticationFilter.java rename to src/main/java/com/petshop/backend/security/JwtAuthenticationFilter.java index f08d4b5b..9ef6610b 100644 --- a/backend/src/main/java/com/petshop/backend/security/JwtAuthenticationFilter.java +++ b/src/main/java/com/petshop/backend/security/JwtAuthenticationFilter.java @@ -4,7 +4,6 @@ import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import lombok.RequiredArgsConstructor; import org.springframework.lang.NonNull; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; @@ -17,7 +16,7 @@ import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; @Component -@RequiredArgsConstructor + public class JwtAuthenticationFilter extends OncePerRequestFilter { private final JwtUtil jwtUtil; diff --git a/backend/src/main/java/com/petshop/backend/security/JwtUtil.java b/src/main/java/com/petshop/backend/security/JwtUtil.java similarity index 100% rename from backend/src/main/java/com/petshop/backend/security/JwtUtil.java rename to src/main/java/com/petshop/backend/security/JwtUtil.java diff --git a/backend/src/main/java/com/petshop/backend/security/SecurityConfig.java b/src/main/java/com/petshop/backend/security/SecurityConfig.java similarity index 98% rename from backend/src/main/java/com/petshop/backend/security/SecurityConfig.java rename to src/main/java/com/petshop/backend/security/SecurityConfig.java index a846a8ef..573281ec 100644 --- a/backend/src/main/java/com/petshop/backend/security/SecurityConfig.java +++ b/src/main/java/com/petshop/backend/security/SecurityConfig.java @@ -1,6 +1,5 @@ package com.petshop.backend.security; -import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; @@ -22,7 +21,7 @@ import org.springframework.security.web.authentication.UsernamePasswordAuthentic @Configuration @EnableWebSecurity @EnableMethodSecurity -@RequiredArgsConstructor + public class SecurityConfig { private final JwtAuthenticationFilter jwtAuthFilter; diff --git a/backend/src/main/java/com/petshop/backend/security/UserDetailsServiceImpl.java b/src/main/java/com/petshop/backend/security/UserDetailsServiceImpl.java similarity index 95% rename from backend/src/main/java/com/petshop/backend/security/UserDetailsServiceImpl.java rename to src/main/java/com/petshop/backend/security/UserDetailsServiceImpl.java index ec040275..09dd2d5b 100644 --- a/backend/src/main/java/com/petshop/backend/security/UserDetailsServiceImpl.java +++ b/src/main/java/com/petshop/backend/security/UserDetailsServiceImpl.java @@ -2,7 +2,6 @@ package com.petshop.backend.security; import com.petshop.backend.entity.User; import com.petshop.backend.repository.UserRepository; -import lombok.RequiredArgsConstructor; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; @@ -12,7 +11,7 @@ import org.springframework.stereotype.Service; import java.util.Collections; @Service -@RequiredArgsConstructor + public class UserDetailsServiceImpl implements UserDetailsService { private final UserRepository userRepository; diff --git a/backend/src/main/java/com/petshop/backend/service/AdoptionService.java b/src/main/java/com/petshop/backend/service/AdoptionService.java similarity index 98% rename from backend/src/main/java/com/petshop/backend/service/AdoptionService.java rename to src/main/java/com/petshop/backend/service/AdoptionService.java index f55cf358..089bc164 100644 --- a/backend/src/main/java/com/petshop/backend/service/AdoptionService.java +++ b/src/main/java/com/petshop/backend/service/AdoptionService.java @@ -10,14 +10,13 @@ import com.petshop.backend.exception.ResourceNotFoundException; import com.petshop.backend.repository.AdoptionRepository; import com.petshop.backend.repository.CustomerRepository; import com.petshop.backend.repository.PetRepository; -import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service -@RequiredArgsConstructor + public class AdoptionService { private final AdoptionRepository adoptionRepository; diff --git a/backend/src/main/java/com/petshop/backend/service/AppointmentService.java b/src/main/java/com/petshop/backend/service/AppointmentService.java similarity index 98% rename from backend/src/main/java/com/petshop/backend/service/AppointmentService.java rename to src/main/java/com/petshop/backend/service/AppointmentService.java index 3c941b68..fcd7eaed 100644 --- a/backend/src/main/java/com/petshop/backend/service/AppointmentService.java +++ b/src/main/java/com/petshop/backend/service/AppointmentService.java @@ -11,7 +11,6 @@ import com.petshop.backend.repository.AppointmentRepository; import com.petshop.backend.repository.CustomerRepository; import com.petshop.backend.repository.PetRepository; import com.petshop.backend.repository.ServiceRepository; -import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; @@ -23,7 +22,7 @@ import java.util.Set; import java.util.stream.Collectors; @Service -@RequiredArgsConstructor + public class AppointmentService { private final AppointmentRepository appointmentRepository; diff --git a/backend/src/main/java/com/petshop/backend/service/CategoryService.java b/src/main/java/com/petshop/backend/service/CategoryService.java similarity index 97% rename from backend/src/main/java/com/petshop/backend/service/CategoryService.java rename to src/main/java/com/petshop/backend/service/CategoryService.java index 7d596ce7..f8fdf7e8 100644 --- a/backend/src/main/java/com/petshop/backend/service/CategoryService.java +++ b/src/main/java/com/petshop/backend/service/CategoryService.java @@ -6,14 +6,13 @@ import com.petshop.backend.dto.common.BulkDeleteRequest; import com.petshop.backend.entity.Category; import com.petshop.backend.exception.ResourceNotFoundException; import com.petshop.backend.repository.CategoryRepository; -import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service -@RequiredArgsConstructor + public class CategoryService { private final CategoryRepository categoryRepository; diff --git a/backend/src/main/java/com/petshop/backend/service/CustomerService.java b/src/main/java/com/petshop/backend/service/CustomerService.java similarity index 98% rename from backend/src/main/java/com/petshop/backend/service/CustomerService.java rename to src/main/java/com/petshop/backend/service/CustomerService.java index fca95ad8..608aa535 100644 --- a/backend/src/main/java/com/petshop/backend/service/CustomerService.java +++ b/src/main/java/com/petshop/backend/service/CustomerService.java @@ -6,14 +6,13 @@ import com.petshop.backend.dto.customer.CustomerResponse; import com.petshop.backend.entity.Customer; import com.petshop.backend.exception.ResourceNotFoundException; import com.petshop.backend.repository.CustomerRepository; -import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service -@RequiredArgsConstructor + public class CustomerService { private final CustomerRepository customerRepository; diff --git a/backend/src/main/java/com/petshop/backend/service/InventoryService.java b/src/main/java/com/petshop/backend/service/InventoryService.java similarity index 98% rename from backend/src/main/java/com/petshop/backend/service/InventoryService.java rename to src/main/java/com/petshop/backend/service/InventoryService.java index 0599e255..d2a4395d 100644 --- a/backend/src/main/java/com/petshop/backend/service/InventoryService.java +++ b/src/main/java/com/petshop/backend/service/InventoryService.java @@ -10,7 +10,6 @@ import com.petshop.backend.exception.ResourceNotFoundException; import com.petshop.backend.repository.InventoryRepository; import com.petshop.backend.repository.ProductRepository; import com.petshop.backend.repository.StoreRepository; -import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; @@ -19,7 +18,7 @@ import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; @Service -@RequiredArgsConstructor + public class InventoryService { private final InventoryRepository inventoryRepository; diff --git a/backend/src/main/java/com/petshop/backend/service/PetService.java b/src/main/java/com/petshop/backend/service/PetService.java similarity index 97% rename from backend/src/main/java/com/petshop/backend/service/PetService.java rename to src/main/java/com/petshop/backend/service/PetService.java index 9d87cde2..0509f346 100644 --- a/backend/src/main/java/com/petshop/backend/service/PetService.java +++ b/src/main/java/com/petshop/backend/service/PetService.java @@ -6,14 +6,13 @@ import com.petshop.backend.dto.pet.PetResponse; import com.petshop.backend.entity.Pet; import com.petshop.backend.exception.ResourceNotFoundException; import com.petshop.backend.repository.PetRepository; -import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service -@RequiredArgsConstructor + public class PetService { private final PetRepository petRepository; diff --git a/backend/src/main/java/com/petshop/backend/service/ProductService.java b/src/main/java/com/petshop/backend/service/ProductService.java similarity index 98% rename from backend/src/main/java/com/petshop/backend/service/ProductService.java rename to src/main/java/com/petshop/backend/service/ProductService.java index 150d676d..2b7646de 100644 --- a/backend/src/main/java/com/petshop/backend/service/ProductService.java +++ b/src/main/java/com/petshop/backend/service/ProductService.java @@ -8,14 +8,13 @@ import com.petshop.backend.entity.Product; import com.petshop.backend.exception.ResourceNotFoundException; import com.petshop.backend.repository.CategoryRepository; import com.petshop.backend.repository.ProductRepository; -import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service -@RequiredArgsConstructor + public class ProductService { private final ProductRepository productRepository; diff --git a/backend/src/main/java/com/petshop/backend/service/ProductSupplierService.java b/src/main/java/com/petshop/backend/service/ProductSupplierService.java similarity index 98% rename from backend/src/main/java/com/petshop/backend/service/ProductSupplierService.java rename to src/main/java/com/petshop/backend/service/ProductSupplierService.java index 20c0bb66..c93c5899 100644 --- a/backend/src/main/java/com/petshop/backend/service/ProductSupplierService.java +++ b/src/main/java/com/petshop/backend/service/ProductSupplierService.java @@ -10,14 +10,13 @@ import com.petshop.backend.exception.ResourceNotFoundException; import com.petshop.backend.repository.ProductRepository; import com.petshop.backend.repository.ProductSupplierRepository; import com.petshop.backend.repository.SupplierRepository; -import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service -@RequiredArgsConstructor + public class ProductSupplierService { private final ProductSupplierRepository productSupplierRepository; diff --git a/backend/src/main/java/com/petshop/backend/service/PurchaseOrderService.java b/src/main/java/com/petshop/backend/service/PurchaseOrderService.java similarity index 97% rename from backend/src/main/java/com/petshop/backend/service/PurchaseOrderService.java rename to src/main/java/com/petshop/backend/service/PurchaseOrderService.java index fa5d9859..d3ecd74a 100644 --- a/backend/src/main/java/com/petshop/backend/service/PurchaseOrderService.java +++ b/src/main/java/com/petshop/backend/service/PurchaseOrderService.java @@ -6,7 +6,6 @@ import com.petshop.backend.entity.PurchaseOrder; import com.petshop.backend.entity.PurchaseOrderItem; import com.petshop.backend.exception.ResourceNotFoundException; import com.petshop.backend.repository.PurchaseOrderRepository; -import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; @@ -15,7 +14,7 @@ import java.util.List; import java.util.stream.Collectors; @Service -@RequiredArgsConstructor + public class PurchaseOrderService { private final PurchaseOrderRepository purchaseOrderRepository; diff --git a/backend/src/main/java/com/petshop/backend/service/RefundService.java b/src/main/java/com/petshop/backend/service/RefundService.java similarity index 98% rename from backend/src/main/java/com/petshop/backend/service/RefundService.java rename to src/main/java/com/petshop/backend/service/RefundService.java index 8f2a4e6a..1758d732 100644 --- a/backend/src/main/java/com/petshop/backend/service/RefundService.java +++ b/src/main/java/com/petshop/backend/service/RefundService.java @@ -6,7 +6,6 @@ import com.petshop.backend.entity.*; import com.petshop.backend.exception.BusinessException; import com.petshop.backend.exception.ResourceNotFoundException; import com.petshop.backend.repository.*; -import lombok.RequiredArgsConstructor; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -17,7 +16,7 @@ import java.util.ArrayList; import java.util.List; @Service -@RequiredArgsConstructor + public class RefundService { private final RefundRepository refundRepository; diff --git a/backend/src/main/java/com/petshop/backend/service/SaleService.java b/src/main/java/com/petshop/backend/service/SaleService.java similarity index 98% rename from backend/src/main/java/com/petshop/backend/service/SaleService.java rename to src/main/java/com/petshop/backend/service/SaleService.java index e3769adc..d74b6fa8 100644 --- a/backend/src/main/java/com/petshop/backend/service/SaleService.java +++ b/src/main/java/com/petshop/backend/service/SaleService.java @@ -6,7 +6,6 @@ import com.petshop.backend.entity.*; import com.petshop.backend.exception.BusinessException; import com.petshop.backend.exception.ResourceNotFoundException; import com.petshop.backend.repository.*; -import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.security.core.context.SecurityContextHolder; @@ -19,7 +18,7 @@ import java.util.ArrayList; import java.util.List; @Service -@RequiredArgsConstructor + public class SaleService { private final SaleRepository saleRepository; diff --git a/backend/src/main/java/com/petshop/backend/service/ServiceService.java b/src/main/java/com/petshop/backend/service/ServiceService.java similarity index 98% rename from backend/src/main/java/com/petshop/backend/service/ServiceService.java rename to src/main/java/com/petshop/backend/service/ServiceService.java index 15faea62..e01b2092 100644 --- a/backend/src/main/java/com/petshop/backend/service/ServiceService.java +++ b/src/main/java/com/petshop/backend/service/ServiceService.java @@ -5,14 +5,13 @@ import com.petshop.backend.dto.service.ServiceRequest; import com.petshop.backend.dto.service.ServiceResponse; import com.petshop.backend.exception.ResourceNotFoundException; import com.petshop.backend.repository.ServiceRepository; -import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service -@RequiredArgsConstructor + public class ServiceService { private final ServiceRepository serviceRepository; diff --git a/backend/src/main/java/com/petshop/backend/service/SupplierService.java b/src/main/java/com/petshop/backend/service/SupplierService.java similarity index 98% rename from backend/src/main/java/com/petshop/backend/service/SupplierService.java rename to src/main/java/com/petshop/backend/service/SupplierService.java index d86a8c13..91daaa35 100644 --- a/backend/src/main/java/com/petshop/backend/service/SupplierService.java +++ b/src/main/java/com/petshop/backend/service/SupplierService.java @@ -6,14 +6,13 @@ import com.petshop.backend.dto.supplier.SupplierResponse; import com.petshop.backend.entity.Supplier; import com.petshop.backend.exception.ResourceNotFoundException; import com.petshop.backend.repository.SupplierRepository; -import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service -@RequiredArgsConstructor + public class SupplierService { private final SupplierRepository supplierRepository; diff --git a/backend/src/main/java/com/petshop/backend/service/UserService.java b/src/main/java/com/petshop/backend/service/UserService.java similarity index 97% rename from backend/src/main/java/com/petshop/backend/service/UserService.java rename to src/main/java/com/petshop/backend/service/UserService.java index da028f9a..e9e7c3e9 100644 --- a/backend/src/main/java/com/petshop/backend/service/UserService.java +++ b/src/main/java/com/petshop/backend/service/UserService.java @@ -6,7 +6,6 @@ import com.petshop.backend.dto.user.UserResponse; import com.petshop.backend.entity.User; import com.petshop.backend.exception.ResourceNotFoundException; import com.petshop.backend.repository.UserRepository; -import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.security.crypto.password.PasswordEncoder; @@ -14,7 +13,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service -@RequiredArgsConstructor + public class UserService { private final UserRepository userRepository; diff --git a/backend/src/main/resources/application.yml b/src/main/resources/application.yml similarity index 100% rename from backend/src/main/resources/application.yml rename to src/main/resources/application.yml From 5b4797fdccc19b206c8f1afc818431af8f9d8ed1 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Wed, 4 Mar 2026 21:46:29 -0700 Subject: [PATCH 11/84] Add Docker support with auto database initialization --- Dockerfile | 13 ++++++ docker-compose.yml | 36 +++++++++++++++++ .../backend/config/DataInitializer.java | 40 +++++++++++++++++++ src/main/resources/application.yml | 8 ++-- 4 files changed, 93 insertions(+), 4 deletions(-) create mode 100644 Dockerfile create mode 100644 docker-compose.yml create mode 100644 src/main/java/com/petshop/backend/config/DataInitializer.java diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..95c76bc2 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,13 @@ +# Build +FROM maven:3.9-eclipse-temurin-17 AS build +WORKDIR /app +COPY pom.xml . +COPY src ./src +RUN mvn -q -DskipTests package + +# Run +FROM eclipse-temurin:17-jre +WORKDIR /app +COPY --from=build /app/target/*.jar app.jar +EXPOSE 8080 +ENTRYPOINT ["java","-jar","app.jar"] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..f82b2d8e --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,36 @@ +services: + db: + image: mysql:8.0 + container_name: petshop-db + environment: + MYSQL_ROOT_PASSWORD: root + MYSQL_DATABASE: Petstoredb + MYSQL_USER: petshop + MYSQL_PASSWORD: petshop + ports: + - "3306:3306" + volumes: + - db_data:/var/lib/mysql + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-uroot", "-proot"] + interval: 5s + timeout: 5s + retries: 30 + + api: + build: . + container_name: petshop-api + environment: + SPRING_DATASOURCE_URL: jdbc:mysql://db:3306/Petstoredb?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC + SPRING_DATASOURCE_USERNAME: petshop + SPRING_DATASOURCE_PASSWORD: petshop + # Change this in real use + JWT_SECRET: change_me_please + ports: + - "8080:8080" + depends_on: + db: + condition: service_healthy + +volumes: + db_data: diff --git a/src/main/java/com/petshop/backend/config/DataInitializer.java b/src/main/java/com/petshop/backend/config/DataInitializer.java new file mode 100644 index 00000000..93c41309 --- /dev/null +++ b/src/main/java/com/petshop/backend/config/DataInitializer.java @@ -0,0 +1,40 @@ +package com.petshop.backend.config; + +import com.petshop.backend.entity.User; +import com.petshop.backend.repository.UserRepository; +import org.springframework.boot.CommandLineRunner; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Component; + +@Component + +public class DataInitializer implements CommandLineRunner { + + private final UserRepository userRepository; + private final PasswordEncoder passwordEncoder; + + @Override + public void run(String... args) { + if (userRepository.findByUsername("admin").isEmpty()) { + User admin = new User(); + admin.setUsername("admin"); + admin.setPassword(passwordEncoder.encode("admin123")); + admin.setFullName("Admin User"); + admin.setEmail("admin@petshop.com"); + admin.setRole(User.Role.ADMIN); + admin.setActive(true); + userRepository.save(admin); + } + + if (userRepository.findByUsername("staff").isEmpty()) { + User staff = new User(); + staff.setUsername("staff"); + staff.setPassword(passwordEncoder.encode("staff123")); + staff.setFullName("Staff User"); + staff.setEmail("staff@petshop.com"); + staff.setRole(User.Role.STAFF); + staff.setActive(true); + userRepository.save(staff); + } + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index cd3a99f7..315667e7 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -3,9 +3,9 @@ spring: name: petshop-backend datasource: - url: ${DB_URL:jdbc:mysql://localhost:3306/petshop?createDatabaseIfNotExist=true} - username: ${DB_USERNAME:root} - password: ${DB_PASSWORD:password} + url: ${SPRING_DATASOURCE_URL:jdbc:mysql://localhost:3306/Petstoredb?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC} + username: ${SPRING_DATASOURCE_USERNAME:petshop} + password: ${SPRING_DATASOURCE_PASSWORD:petshop} driver-class-name: com.mysql.cj.jdbc.Driver jpa: @@ -29,7 +29,7 @@ springdoc: path: /swagger-ui jwt: - secret: ${JWT_SECRET:your-256-bit-secret-key-change-this-in-production-min-32-chars} + secret: ${JWT_SECRET:change_me_please_make_this_at_least_32_characters_long_for_security} expiration: ${JWT_EXPIRATION:86400000} logging: From c5da0582fb9abcfec8848303c6c5f90a4703b1cc Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Wed, 4 Mar 2026 21:52:20 -0700 Subject: [PATCH 12/84] Add IntelliJ run configurations --- .gitignore | 3 ++- docker-compose.db.yml | 21 +++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 docker-compose.db.yml diff --git a/.gitignore b/.gitignore index 16d47a38..34394cf7 100644 --- a/.gitignore +++ b/.gitignore @@ -13,7 +13,8 @@ target/ .sts4-cache ### IntelliJ IDEA ### -.idea +.idea/* +!.idea/runConfigurations/ *.iws *.iml *.ipr diff --git a/docker-compose.db.yml b/docker-compose.db.yml new file mode 100644 index 00000000..7d8408d0 --- /dev/null +++ b/docker-compose.db.yml @@ -0,0 +1,21 @@ +services: + db: + image: mysql:8.0 + container_name: petshop-db + environment: + MYSQL_ROOT_PASSWORD: root + MYSQL_DATABASE: Petstoredb + MYSQL_USER: petshop + MYSQL_PASSWORD: petshop + ports: + - "3306:3306" + volumes: + - db_data:/var/lib/mysql + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-uroot", "-proot"] + interval: 5s + timeout: 5s + retries: 30 + +volumes: + db_data: From 7609a8e825036ad21bec4f112bb0fae3efdb0431 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Wed, 4 Mar 2026 22:00:42 -0700 Subject: [PATCH 13/84] Fix annotation processing in Maven --- pom.xml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index cf42279b..73c8b055 100644 --- a/pom.xml +++ b/pom.xml @@ -82,8 +82,10 @@ - org.springframework.boot - spring-boot-maven-plugin + org.apache.maven.plugins + maven-compiler-plugin - - + 17 + 17 + + From d6eaeb2d885bb663b482bed9a3163230efe2e023 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Thu, 5 Mar 2026 06:00:38 -0700 Subject: [PATCH 14/84] Restore complete implementations --- pom.xml | 13 +- .../backend/config/DataInitializer.java | 6 +- .../controller/AdoptionController.java | 5 +- .../controller/AppointmentController.java | 5 +- .../backend/controller/AuthController.java | 7 +- .../controller/CategoryController.java | 5 +- .../controller/CustomerController.java | 5 +- .../controller/DropdownController.java | 14 +- .../controller/InventoryController.java | 5 +- .../backend/controller/PetController.java | 5 +- .../backend/controller/ProductController.java | 5 +- .../controller/ProductSupplierController.java | 5 +- .../controller/PurchaseOrderController.java | 5 +- .../backend/controller/RefundController.java | 5 +- .../backend/controller/SaleController.java | 5 +- .../backend/controller/ServiceController.java | 5 +- .../controller/SupplierController.java | 5 +- .../backend/controller/UserController.java | 5 +- .../backend/dto/adoption/AdoptionRequest.java | 71 +++- .../dto/adoption/AdoptionResponse.java | 130 ++++++- .../dto/analytics/DashboardResponse.java | 323 +++++++++++++++++- .../dto/appointment/AppointmentRequest.java | 91 ++++- .../dto/appointment/AppointmentResponse.java | 160 ++++++++- .../backend/dto/auth/LoginRequest.java | 40 ++- .../backend/dto/auth/LoginResponse.java | 68 +++- .../backend/dto/auth/UserInfoResponse.java | 78 ++++- .../backend/dto/category/CategoryRequest.java | 40 ++- .../dto/category/CategoryResponse.java | 80 ++++- .../backend/dto/common/BulkDeleteRequest.java | 31 +- .../backend/dto/common/DropdownOption.java | 48 ++- .../backend/dto/customer/CustomerRequest.java | 60 +++- .../dto/customer/CustomerResponse.java | 100 +++++- .../dto/inventory/InventoryRequest.java | 60 +++- .../dto/inventory/InventoryResponse.java | 140 +++++++- .../petshop/backend/dto/pet/PetRequest.java | 81 ++++- .../petshop/backend/dto/pet/PetResponse.java | 120 ++++++- .../backend/dto/product/ProductRequest.java | 71 +++- .../backend/dto/product/ProductResponse.java | 120 ++++++- .../BulkDeleteProductSupplierRequest.java | 31 +- .../productsupplier/ProductSupplierKey.java | 50 ++- .../ProductSupplierRequest.java | 71 +++- .../ProductSupplierResponse.java | 120 ++++++- .../purchaseorder/PurchaseOrderResponse.java | 228 ++++++++++++- .../backend/dto/refund/RefundItemRequest.java | 40 ++- .../backend/dto/refund/RefundRequest.java | 43 ++- .../backend/dto/refund/RefundResponse.java | 208 ++++++++++- .../backend/dto/sale/SaleItemRequest.java | 40 ++- .../petshop/backend/dto/sale/SaleRequest.java | 82 ++++- .../backend/dto/sale/SaleResponse.java | 268 ++++++++++++++- .../backend/dto/service/ServiceRequest.java | 71 +++- .../backend/dto/service/ServiceResponse.java | 110 +++++- .../backend/dto/supplier/SupplierRequest.java | 80 ++++- .../dto/supplier/SupplierResponse.java | 120 ++++++- .../petshop/backend/dto/user/UserRequest.java | 80 ++++- .../backend/dto/user/UserResponse.java | 110 +++++- .../com/petshop/backend/entity/Adoption.java | 109 +++++- .../petshop/backend/entity/Appointment.java | 129 ++++++- .../com/petshop/backend/entity/Category.java | 79 ++++- .../com/petshop/backend/entity/Customer.java | 99 +++++- .../com/petshop/backend/entity/Inventory.java | 109 +++++- .../java/com/petshop/backend/entity/Pet.java | 119 ++++++- .../com/petshop/backend/entity/Product.java | 109 +++++- .../backend/entity/ProductSupplier.java | 147 +++++++- .../petshop/backend/entity/PurchaseOrder.java | 129 ++++++- .../backend/entity/PurchaseOrderItem.java | 89 ++++- .../com/petshop/backend/entity/Refund.java | 109 +++++- .../petshop/backend/entity/RefundItem.java | 79 ++++- .../java/com/petshop/backend/entity/Sale.java | 149 +++++++- .../com/petshop/backend/entity/SaleItem.java | 89 ++++- .../com/petshop/backend/entity/Service.java | 109 +++++- .../com/petshop/backend/entity/Store.java | 69 +++- .../com/petshop/backend/entity/Supplier.java | 119 ++++++- .../java/com/petshop/backend/entity/User.java | 119 ++++++- .../security/JwtAuthenticationFilter.java | 6 +- .../backend/security/SecurityConfig.java | 6 +- .../security/UserDetailsServiceImpl.java | 5 +- .../backend/service/AdoptionService.java | 7 +- .../backend/service/AppointmentService.java | 8 +- .../backend/service/CategoryService.java | 5 +- .../backend/service/CustomerService.java | 5 +- .../backend/service/InventoryService.java | 7 +- .../petshop/backend/service/PetService.java | 5 +- .../backend/service/ProductService.java | 6 +- .../service/ProductSupplierService.java | 7 +- .../backend/service/PurchaseOrderService.java | 5 +- .../backend/service/RefundService.java | 9 +- .../petshop/backend/service/SaleService.java | 10 +- .../backend/service/ServiceService.java | 5 +- .../backend/service/SupplierService.java | 5 +- .../petshop/backend/service/UserService.java | 6 +- 90 files changed, 5623 insertions(+), 218 deletions(-) diff --git a/pom.xml b/pom.xml index 73c8b055..47a732fe 100644 --- a/pom.xml +++ b/pom.xml @@ -75,8 +75,6 @@ 2.3.0 - - @@ -87,5 +85,12 @@ 17 17 - - + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/src/main/java/com/petshop/backend/config/DataInitializer.java b/src/main/java/com/petshop/backend/config/DataInitializer.java index 93c41309..cf311496 100644 --- a/src/main/java/com/petshop/backend/config/DataInitializer.java +++ b/src/main/java/com/petshop/backend/config/DataInitializer.java @@ -7,12 +7,16 @@ import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Component; @Component - public class DataInitializer implements CommandLineRunner { private final UserRepository userRepository; private final PasswordEncoder passwordEncoder; + public DataInitializer(UserRepository userRepository, PasswordEncoder passwordEncoder) { + this.userRepository = userRepository; + this.passwordEncoder = passwordEncoder; + } + @Override public void run(String... args) { if (userRepository.findByUsername("admin").isEmpty()) { diff --git a/src/main/java/com/petshop/backend/controller/AdoptionController.java b/src/main/java/com/petshop/backend/controller/AdoptionController.java index 81be9fd7..8fbc4db3 100644 --- a/src/main/java/com/petshop/backend/controller/AdoptionController.java +++ b/src/main/java/com/petshop/backend/controller/AdoptionController.java @@ -13,11 +13,14 @@ import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/v1/adoptions") - public class AdoptionController { private final AdoptionService adoptionService; + public AdoptionController(AdoptionService adoptionService) { + this.adoptionService = adoptionService; + } + @GetMapping public ResponseEntity> getAllAdoptions(Pageable pageable) { return ResponseEntity.ok(adoptionService.getAllAdoptions(pageable)); diff --git a/src/main/java/com/petshop/backend/controller/AppointmentController.java b/src/main/java/com/petshop/backend/controller/AppointmentController.java index 12ba8aa4..a08416b5 100644 --- a/src/main/java/com/petshop/backend/controller/AppointmentController.java +++ b/src/main/java/com/petshop/backend/controller/AppointmentController.java @@ -13,11 +13,14 @@ import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/v1/appointments") - public class AppointmentController { private final AppointmentService appointmentService; + public AppointmentController(AppointmentService appointmentService) { + this.appointmentService = appointmentService; + } + @GetMapping public ResponseEntity> getAllAppointments(Pageable pageable) { return ResponseEntity.ok(appointmentService.getAllAppointments(pageable)); diff --git a/src/main/java/com/petshop/backend/controller/AuthController.java b/src/main/java/com/petshop/backend/controller/AuthController.java index cab4ef52..970d45c9 100644 --- a/src/main/java/com/petshop/backend/controller/AuthController.java +++ b/src/main/java/com/petshop/backend/controller/AuthController.java @@ -23,13 +23,18 @@ import java.util.Map; @RestController @RequestMapping("/api/v1/auth") - public class AuthController { private final AuthenticationManager authenticationManager; private final UserRepository userRepository; private final JwtUtil jwtUtil; + public AuthController(AuthenticationManager authenticationManager, UserRepository userRepository, JwtUtil jwtUtil) { + this.authenticationManager = authenticationManager; + this.userRepository = userRepository; + this.jwtUtil = jwtUtil; + } + @PostMapping("/login") public ResponseEntity login(@Valid @RequestBody LoginRequest request) { try { diff --git a/src/main/java/com/petshop/backend/controller/CategoryController.java b/src/main/java/com/petshop/backend/controller/CategoryController.java index 510f729d..f03055b4 100644 --- a/src/main/java/com/petshop/backend/controller/CategoryController.java +++ b/src/main/java/com/petshop/backend/controller/CategoryController.java @@ -13,11 +13,14 @@ import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/v1/categories") - public class CategoryController { private final CategoryService categoryService; + public CategoryController(CategoryService categoryService) { + this.categoryService = categoryService; + } + @GetMapping public ResponseEntity> getAllCategories( @RequestParam(required = false) String q, diff --git a/src/main/java/com/petshop/backend/controller/CustomerController.java b/src/main/java/com/petshop/backend/controller/CustomerController.java index 96ac564f..75bed4fc 100644 --- a/src/main/java/com/petshop/backend/controller/CustomerController.java +++ b/src/main/java/com/petshop/backend/controller/CustomerController.java @@ -13,11 +13,14 @@ import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/v1/customers") - public class CustomerController { private final CustomerService customerService; + public CustomerController(CustomerService customerService) { + this.customerService = customerService; + } + @GetMapping public ResponseEntity> getAllCustomers( @RequestParam(required = false) String q, diff --git a/src/main/java/com/petshop/backend/controller/DropdownController.java b/src/main/java/com/petshop/backend/controller/DropdownController.java index 48cc4b5e..b0b60930 100644 --- a/src/main/java/com/petshop/backend/controller/DropdownController.java +++ b/src/main/java/com/petshop/backend/controller/DropdownController.java @@ -13,7 +13,6 @@ import java.util.stream.Collectors; @RestController @RequestMapping("/api/v1/dropdowns") - public class DropdownController { private final PetRepository petRepository; @@ -24,6 +23,19 @@ public class DropdownController { private final StoreRepository storeRepository; private final SupplierRepository supplierRepository; + public DropdownController(PetRepository petRepository, CustomerRepository customerRepository, + ServiceRepository serviceRepository, ProductRepository productRepository, + CategoryRepository categoryRepository, StoreRepository storeRepository, + SupplierRepository supplierRepository) { + this.petRepository = petRepository; + this.customerRepository = customerRepository; + this.serviceRepository = serviceRepository; + this.productRepository = productRepository; + this.categoryRepository = categoryRepository; + this.storeRepository = storeRepository; + this.supplierRepository = supplierRepository; + } + @GetMapping("/pets") public ResponseEntity> getPets() { return ResponseEntity.ok( diff --git a/src/main/java/com/petshop/backend/controller/InventoryController.java b/src/main/java/com/petshop/backend/controller/InventoryController.java index f4c6742a..44db2471 100644 --- a/src/main/java/com/petshop/backend/controller/InventoryController.java +++ b/src/main/java/com/petshop/backend/controller/InventoryController.java @@ -14,12 +14,15 @@ import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/v1/inventory") - @PreAuthorize("hasRole('ADMIN')") public class InventoryController { private final InventoryService inventoryService; + public InventoryController(InventoryService inventoryService) { + this.inventoryService = inventoryService; + } + @GetMapping public ResponseEntity> getAllInventory(Pageable pageable) { return ResponseEntity.ok(inventoryService.getAllInventory(pageable)); diff --git a/src/main/java/com/petshop/backend/controller/PetController.java b/src/main/java/com/petshop/backend/controller/PetController.java index 1d70bf87..70c68e8f 100644 --- a/src/main/java/com/petshop/backend/controller/PetController.java +++ b/src/main/java/com/petshop/backend/controller/PetController.java @@ -13,11 +13,14 @@ import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/v1/pets") - public class PetController { private final PetService petService; + public PetController(PetService petService) { + this.petService = petService; + } + @GetMapping public ResponseEntity> getAllPets( @RequestParam(required = false) String q, diff --git a/src/main/java/com/petshop/backend/controller/ProductController.java b/src/main/java/com/petshop/backend/controller/ProductController.java index 98d0fe10..12999b70 100644 --- a/src/main/java/com/petshop/backend/controller/ProductController.java +++ b/src/main/java/com/petshop/backend/controller/ProductController.java @@ -13,11 +13,14 @@ import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/v1/products") - public class ProductController { private final ProductService productService; + public ProductController(ProductService productService) { + this.productService = productService; + } + @GetMapping public ResponseEntity> getAllProducts( @RequestParam(required = false) String q, diff --git a/src/main/java/com/petshop/backend/controller/ProductSupplierController.java b/src/main/java/com/petshop/backend/controller/ProductSupplierController.java index 18c31c79..ea56c4ad 100644 --- a/src/main/java/com/petshop/backend/controller/ProductSupplierController.java +++ b/src/main/java/com/petshop/backend/controller/ProductSupplierController.java @@ -14,12 +14,15 @@ import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/v1/product-suppliers") - @PreAuthorize("hasRole('ADMIN')") public class ProductSupplierController { private final ProductSupplierService productSupplierService; + public ProductSupplierController(ProductSupplierService productSupplierService) { + this.productSupplierService = productSupplierService; + } + @GetMapping public ResponseEntity> getAllProductSuppliers(Pageable pageable) { return ResponseEntity.ok(productSupplierService.getAllProductSuppliers(pageable)); diff --git a/src/main/java/com/petshop/backend/controller/PurchaseOrderController.java b/src/main/java/com/petshop/backend/controller/PurchaseOrderController.java index 4496b750..5a3c214c 100644 --- a/src/main/java/com/petshop/backend/controller/PurchaseOrderController.java +++ b/src/main/java/com/petshop/backend/controller/PurchaseOrderController.java @@ -10,12 +10,15 @@ import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/v1/purchase-orders") - @PreAuthorize("hasRole('ADMIN')") public class PurchaseOrderController { private final PurchaseOrderService purchaseOrderService; + public PurchaseOrderController(PurchaseOrderService purchaseOrderService) { + this.purchaseOrderService = purchaseOrderService; + } + @GetMapping public ResponseEntity> getAllPurchaseOrders(Pageable pageable) { return ResponseEntity.ok(purchaseOrderService.getAllPurchaseOrders(pageable)); diff --git a/src/main/java/com/petshop/backend/controller/RefundController.java b/src/main/java/com/petshop/backend/controller/RefundController.java index 2410f0b5..1086b9af 100644 --- a/src/main/java/com/petshop/backend/controller/RefundController.java +++ b/src/main/java/com/petshop/backend/controller/RefundController.java @@ -10,11 +10,14 @@ import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/v1/sales") - public class RefundController { private final RefundService refundService; + public RefundController(RefundService refundService) { + this.refundService = refundService; + } + @PostMapping("/{saleId}/refunds") public ResponseEntity createRefund( @PathVariable Long saleId, diff --git a/src/main/java/com/petshop/backend/controller/SaleController.java b/src/main/java/com/petshop/backend/controller/SaleController.java index 71599551..0618c317 100644 --- a/src/main/java/com/petshop/backend/controller/SaleController.java +++ b/src/main/java/com/petshop/backend/controller/SaleController.java @@ -12,11 +12,14 @@ import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/v1/sales") - public class SaleController { private final SaleService saleService; + public SaleController(SaleService saleService) { + this.saleService = saleService; + } + @GetMapping public ResponseEntity> getAllSales(Pageable pageable) { return ResponseEntity.ok(saleService.getAllSales(pageable)); diff --git a/src/main/java/com/petshop/backend/controller/ServiceController.java b/src/main/java/com/petshop/backend/controller/ServiceController.java index 979028c9..f9167a0b 100644 --- a/src/main/java/com/petshop/backend/controller/ServiceController.java +++ b/src/main/java/com/petshop/backend/controller/ServiceController.java @@ -13,11 +13,14 @@ import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/v1/services") - public class ServiceController { private final ServiceService serviceService; + public ServiceController(ServiceService serviceService) { + this.serviceService = serviceService; + } + @GetMapping public ResponseEntity> getAllServices( @RequestParam(required = false) String q, diff --git a/src/main/java/com/petshop/backend/controller/SupplierController.java b/src/main/java/com/petshop/backend/controller/SupplierController.java index fd9c4622..255f01dc 100644 --- a/src/main/java/com/petshop/backend/controller/SupplierController.java +++ b/src/main/java/com/petshop/backend/controller/SupplierController.java @@ -14,12 +14,15 @@ import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/v1/suppliers") - @PreAuthorize("hasRole('ADMIN')") public class SupplierController { private final SupplierService supplierService; + public SupplierController(SupplierService supplierService) { + this.supplierService = supplierService; + } + @GetMapping public ResponseEntity> getAllSuppliers( @RequestParam(required = false) String q, diff --git a/src/main/java/com/petshop/backend/controller/UserController.java b/src/main/java/com/petshop/backend/controller/UserController.java index d6719f2b..a43f01f8 100644 --- a/src/main/java/com/petshop/backend/controller/UserController.java +++ b/src/main/java/com/petshop/backend/controller/UserController.java @@ -14,12 +14,15 @@ import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/v1/users") - @PreAuthorize("hasRole('ADMIN')") public class UserController { private final UserService userService; + public UserController(UserService userService) { + this.userService = userService; + } + @GetMapping public ResponseEntity> getAllUsers(Pageable pageable) { return ResponseEntity.ok(userService.getAllUsers(pageable)); diff --git a/src/main/java/com/petshop/backend/dto/adoption/AdoptionRequest.java b/src/main/java/com/petshop/backend/dto/adoption/AdoptionRequest.java index 690ba25b..417ef3a9 100644 --- a/src/main/java/com/petshop/backend/dto/adoption/AdoptionRequest.java +++ b/src/main/java/com/petshop/backend/dto/adoption/AdoptionRequest.java @@ -2,10 +2,9 @@ package com.petshop.backend.dto.adoption; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Positive; - import java.math.BigDecimal; import java.time.LocalDate; - +import java.util.Objects; public class AdoptionRequest { @NotNull(message = "Pet ID is required") @@ -22,4 +21,72 @@ public class AdoptionRequest { private BigDecimal adoptionFee; private String notes; + + public Long getPetId() { + return petId; + } + + public void setPetId(Long petId) { + this.petId = petId; + } + + public Long getCustomerId() { + return customerId; + } + + public void setCustomerId(Long customerId) { + this.customerId = customerId; + } + + public LocalDate getAdoptionDate() { + return adoptionDate; + } + + public void setAdoptionDate(LocalDate adoptionDate) { + this.adoptionDate = adoptionDate; + } + + public BigDecimal getAdoptionFee() { + return adoptionFee; + } + + public void setAdoptionFee(BigDecimal adoptionFee) { + this.adoptionFee = adoptionFee; + } + + public String getNotes() { + return notes; + } + + public void setNotes(String notes) { + this.notes = notes; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + AdoptionRequest that = (AdoptionRequest) o; + return Objects.equals(petId, that.petId) && + Objects.equals(customerId, that.customerId) && + Objects.equals(adoptionDate, that.adoptionDate) && + Objects.equals(adoptionFee, that.adoptionFee) && + Objects.equals(notes, that.notes); + } + + @Override + public int hashCode() { + return Objects.hash(petId, customerId, adoptionDate, adoptionFee, notes); + } + + @Override + public String toString() { + return "AdoptionRequest{" + + "petId=" + petId + + ", customerId=" + customerId + + ", adoptionDate=" + adoptionDate + + ", adoptionFee=" + adoptionFee + + ", notes='" + notes + '\'' + + '}'; + } } diff --git a/src/main/java/com/petshop/backend/dto/adoption/AdoptionResponse.java b/src/main/java/com/petshop/backend/dto/adoption/AdoptionResponse.java index 796a82ff..e0c3faec 100644 --- a/src/main/java/com/petshop/backend/dto/adoption/AdoptionResponse.java +++ b/src/main/java/com/petshop/backend/dto/adoption/AdoptionResponse.java @@ -1,12 +1,9 @@ package com.petshop.backend.dto.adoption; - import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; - - - +import java.util.Objects; public class AdoptionResponse { private Long id; @@ -19,4 +16,129 @@ public class AdoptionResponse { private String notes; private LocalDateTime createdAt; private LocalDateTime updatedAt; + + public AdoptionResponse() { + } + + public AdoptionResponse(Long id, Long petId, String petName, Long customerId, String customerName, LocalDate adoptionDate, BigDecimal adoptionFee, String notes, LocalDateTime createdAt, LocalDateTime updatedAt) { + this.id = id; + this.petId = petId; + this.petName = petName; + this.customerId = customerId; + this.customerName = customerName; + this.adoptionDate = adoptionDate; + this.adoptionFee = adoptionFee; + this.notes = notes; + this.createdAt = createdAt; + this.updatedAt = updatedAt; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getPetId() { + return petId; + } + + public void setPetId(Long petId) { + this.petId = petId; + } + + public String getPetName() { + return petName; + } + + public void setPetName(String petName) { + this.petName = petName; + } + + public Long getCustomerId() { + return customerId; + } + + public void setCustomerId(Long customerId) { + this.customerId = customerId; + } + + public String getCustomerName() { + return customerName; + } + + public void setCustomerName(String customerName) { + this.customerName = customerName; + } + + public LocalDate getAdoptionDate() { + return adoptionDate; + } + + public void setAdoptionDate(LocalDate adoptionDate) { + this.adoptionDate = adoptionDate; + } + + public BigDecimal getAdoptionFee() { + return adoptionFee; + } + + public void setAdoptionFee(BigDecimal adoptionFee) { + this.adoptionFee = adoptionFee; + } + + public String getNotes() { + return notes; + } + + public void setNotes(String notes) { + this.notes = notes; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(LocalDateTime createdAt) { + this.createdAt = createdAt; + } + + public LocalDateTime getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(LocalDateTime updatedAt) { + this.updatedAt = updatedAt; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + AdoptionResponse that = (AdoptionResponse) o; + return Objects.equals(id, that.id) && Objects.equals(petId, that.petId) && Objects.equals(petName, that.petName) && Objects.equals(customerId, that.customerId) && Objects.equals(customerName, that.customerName) && Objects.equals(adoptionDate, that.adoptionDate) && Objects.equals(adoptionFee, that.adoptionFee) && Objects.equals(notes, that.notes) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt); + } + + @Override + public int hashCode() { + return Objects.hash(id, petId, petName, customerId, customerName, adoptionDate, adoptionFee, notes, createdAt, updatedAt); + } + + @Override + public String toString() { + return "AdoptionResponse{" + + "id=" + id + + ", petId=" + petId + + ", petName='" + petName + '\'' + + ", customerId=" + customerId + + ", customerName='" + customerName + '\'' + + ", adoptionDate=" + adoptionDate + + ", adoptionFee=" + adoptionFee + + ", notes='" + notes + '\'' + + ", createdAt=" + createdAt + + ", updatedAt=" + updatedAt + + '}'; + } } diff --git a/src/main/java/com/petshop/backend/dto/analytics/DashboardResponse.java b/src/main/java/com/petshop/backend/dto/analytics/DashboardResponse.java index b7d420f8..263d3fae 100644 --- a/src/main/java/com/petshop/backend/dto/analytics/DashboardResponse.java +++ b/src/main/java/com/petshop/backend/dto/analytics/DashboardResponse.java @@ -1,54 +1,343 @@ package com.petshop.backend.dto.analytics; - import java.math.BigDecimal; import java.util.List; -import java.util.Map; - - - +import java.util.Objects; public class DashboardResponse { private SalesSummary salesSummary; private InventorySummary inventorySummary; private List topProducts; private List dailySales; + + public DashboardResponse() { + } + + public DashboardResponse(SalesSummary salesSummary, InventorySummary inventorySummary, List topProducts, List dailySales) { + this.salesSummary = salesSummary; + this.inventorySummary = inventorySummary; + this.topProducts = topProducts; + this.dailySales = dailySales; + } + + public SalesSummary getSalesSummary() { + return salesSummary; + } + + public void setSalesSummary(SalesSummary salesSummary) { + this.salesSummary = salesSummary; + } + + public InventorySummary getInventorySummary() { + return inventorySummary; + } + + public void setInventorySummary(InventorySummary inventorySummary) { + this.inventorySummary = inventorySummary; + } + + public List getTopProducts() { + return topProducts; + } + + public void setTopProducts(List topProducts) { + this.topProducts = topProducts; + } + + public List getDailySales() { + return dailySales; + } + + public void setDailySales(List dailySales) { + this.dailySales = dailySales; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + DashboardResponse that = (DashboardResponse) o; + return Objects.equals(salesSummary, that.salesSummary) && Objects.equals(inventorySummary, that.inventorySummary) && Objects.equals(topProducts, that.topProducts) && Objects.equals(dailySales, that.dailySales); + } + + @Override + public int hashCode() { + return Objects.hash(salesSummary, inventorySummary, topProducts, dailySales); + } + + @Override + public String toString() { + return "DashboardResponse{" + + "salesSummary=" + salesSummary + + ", inventorySummary=" + inventorySummary + + ", topProducts=" + topProducts + + ", dailySales=" + dailySales + + '}'; + } } - - - class SalesSummary { private BigDecimal totalRevenue; private Long totalSales; private BigDecimal totalRefunds; private Long totalRefundCount; + + public SalesSummary() { + } + + public SalesSummary(BigDecimal totalRevenue, Long totalSales, BigDecimal totalRefunds, Long totalRefundCount) { + this.totalRevenue = totalRevenue; + this.totalSales = totalSales; + this.totalRefunds = totalRefunds; + this.totalRefundCount = totalRefundCount; + } + + public BigDecimal getTotalRevenue() { + return totalRevenue; + } + + public void setTotalRevenue(BigDecimal totalRevenue) { + this.totalRevenue = totalRevenue; + } + + public Long getTotalSales() { + return totalSales; + } + + public void setTotalSales(Long totalSales) { + this.totalSales = totalSales; + } + + public BigDecimal getTotalRefunds() { + return totalRefunds; + } + + public void setTotalRefunds(BigDecimal totalRefunds) { + this.totalRefunds = totalRefunds; + } + + public Long getTotalRefundCount() { + return totalRefundCount; + } + + public void setTotalRefundCount(Long totalRefundCount) { + this.totalRefundCount = totalRefundCount; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + SalesSummary that = (SalesSummary) o; + return Objects.equals(totalRevenue, that.totalRevenue) && Objects.equals(totalSales, that.totalSales) && Objects.equals(totalRefunds, that.totalRefunds) && Objects.equals(totalRefundCount, that.totalRefundCount); + } + + @Override + public int hashCode() { + return Objects.hash(totalRevenue, totalSales, totalRefunds, totalRefundCount); + } + + @Override + public String toString() { + return "SalesSummary{" + + "totalRevenue=" + totalRevenue + + ", totalSales=" + totalSales + + ", totalRefunds=" + totalRefunds + + ", totalRefundCount=" + totalRefundCount + + '}'; + } } - - - class InventorySummary { private Long totalProducts; private Long lowStockProducts; private Long outOfStockProducts; + + public InventorySummary() { + } + + public InventorySummary(Long totalProducts, Long lowStockProducts, Long outOfStockProducts) { + this.totalProducts = totalProducts; + this.lowStockProducts = lowStockProducts; + this.outOfStockProducts = outOfStockProducts; + } + + public Long getTotalProducts() { + return totalProducts; + } + + public void setTotalProducts(Long totalProducts) { + this.totalProducts = totalProducts; + } + + public Long getLowStockProducts() { + return lowStockProducts; + } + + public void setLowStockProducts(Long lowStockProducts) { + this.lowStockProducts = lowStockProducts; + } + + public Long getOutOfStockProducts() { + return outOfStockProducts; + } + + public void setOutOfStockProducts(Long outOfStockProducts) { + this.outOfStockProducts = outOfStockProducts; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + InventorySummary that = (InventorySummary) o; + return Objects.equals(totalProducts, that.totalProducts) && Objects.equals(lowStockProducts, that.lowStockProducts) && Objects.equals(outOfStockProducts, that.outOfStockProducts); + } + + @Override + public int hashCode() { + return Objects.hash(totalProducts, lowStockProducts, outOfStockProducts); + } + + @Override + public String toString() { + return "InventorySummary{" + + "totalProducts=" + totalProducts + + ", lowStockProducts=" + lowStockProducts + + ", outOfStockProducts=" + outOfStockProducts + + '}'; + } } - - - class TopProduct { private Long productId; private String productName; private Long quantitySold; private BigDecimal revenue; + + public TopProduct() { + } + + public TopProduct(Long productId, String productName, Long quantitySold, BigDecimal revenue) { + this.productId = productId; + this.productName = productName; + this.quantitySold = quantitySold; + this.revenue = revenue; + } + + public Long getProductId() { + return productId; + } + + public void setProductId(Long productId) { + this.productId = productId; + } + + public String getProductName() { + return productName; + } + + public void setProductName(String productName) { + this.productName = productName; + } + + public Long getQuantitySold() { + return quantitySold; + } + + public void setQuantitySold(Long quantitySold) { + this.quantitySold = quantitySold; + } + + public BigDecimal getRevenue() { + return revenue; + } + + public void setRevenue(BigDecimal revenue) { + this.revenue = revenue; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + TopProduct that = (TopProduct) o; + return Objects.equals(productId, that.productId) && Objects.equals(productName, that.productName) && Objects.equals(quantitySold, that.quantitySold) && Objects.equals(revenue, that.revenue); + } + + @Override + public int hashCode() { + return Objects.hash(productId, productName, quantitySold, revenue); + } + + @Override + public String toString() { + return "TopProduct{" + + "productId=" + productId + + ", productName='" + productName + '\'' + + ", quantitySold=" + quantitySold + + ", revenue=" + revenue + + '}'; + } } - - - class DailySales { private String date; private BigDecimal revenue; private Long salesCount; + + public DailySales() { + } + + public DailySales(String date, BigDecimal revenue, Long salesCount) { + this.date = date; + this.revenue = revenue; + this.salesCount = salesCount; + } + + public String getDate() { + return date; + } + + public void setDate(String date) { + this.date = date; + } + + public BigDecimal getRevenue() { + return revenue; + } + + public void setRevenue(BigDecimal revenue) { + this.revenue = revenue; + } + + public Long getSalesCount() { + return salesCount; + } + + public void setSalesCount(Long salesCount) { + this.salesCount = salesCount; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + DailySales that = (DailySales) o; + return Objects.equals(date, that.date) && Objects.equals(revenue, that.revenue) && Objects.equals(salesCount, that.salesCount); + } + + @Override + public int hashCode() { + return Objects.hash(date, revenue, salesCount); + } + + @Override + public String toString() { + return "DailySales{" + + "date='" + date + '\'' + + ", revenue=" + revenue + + ", salesCount=" + salesCount + + '}'; + } } diff --git a/src/main/java/com/petshop/backend/dto/appointment/AppointmentRequest.java b/src/main/java/com/petshop/backend/dto/appointment/AppointmentRequest.java index 2e51532e..107e6856 100644 --- a/src/main/java/com/petshop/backend/dto/appointment/AppointmentRequest.java +++ b/src/main/java/com/petshop/backend/dto/appointment/AppointmentRequest.java @@ -3,11 +3,10 @@ package com.petshop.backend.dto.appointment; import com.petshop.backend.entity.Appointment; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; - import java.time.LocalDate; import java.time.LocalTime; import java.util.List; - +import java.util.Objects; public class AppointmentRequest { @NotNull(message = "Customer ID is required") @@ -29,4 +28,92 @@ public class AppointmentRequest { private List petIds; private String notes; + + public Long getCustomerId() { + return customerId; + } + + public void setCustomerId(Long customerId) { + this.customerId = customerId; + } + + public Long getServiceId() { + return serviceId; + } + + public void setServiceId(Long serviceId) { + this.serviceId = serviceId; + } + + public LocalDate getAppointmentDate() { + return appointmentDate; + } + + public void setAppointmentDate(LocalDate appointmentDate) { + this.appointmentDate = appointmentDate; + } + + public LocalTime getAppointmentTime() { + return appointmentTime; + } + + public void setAppointmentTime(LocalTime appointmentTime) { + this.appointmentTime = appointmentTime; + } + + public Appointment.AppointmentStatus getStatus() { + return status; + } + + public void setStatus(Appointment.AppointmentStatus status) { + this.status = status; + } + + public List getPetIds() { + return petIds; + } + + public void setPetIds(List petIds) { + this.petIds = petIds; + } + + public String getNotes() { + return notes; + } + + public void setNotes(String notes) { + this.notes = notes; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + AppointmentRequest that = (AppointmentRequest) o; + return Objects.equals(customerId, that.customerId) && + Objects.equals(serviceId, that.serviceId) && + Objects.equals(appointmentDate, that.appointmentDate) && + Objects.equals(appointmentTime, that.appointmentTime) && + status == that.status && + Objects.equals(petIds, that.petIds) && + Objects.equals(notes, that.notes); + } + + @Override + public int hashCode() { + return Objects.hash(customerId, serviceId, appointmentDate, appointmentTime, status, petIds, notes); + } + + @Override + public String toString() { + return "AppointmentRequest{" + + "customerId=" + customerId + + ", serviceId=" + serviceId + + ", appointmentDate=" + appointmentDate + + ", appointmentTime=" + appointmentTime + + ", status=" + status + + ", petIds=" + petIds + + ", notes='" + notes + '\'' + + '}'; + } } diff --git a/src/main/java/com/petshop/backend/dto/appointment/AppointmentResponse.java b/src/main/java/com/petshop/backend/dto/appointment/AppointmentResponse.java index d2b54f66..b84b4dad 100644 --- a/src/main/java/com/petshop/backend/dto/appointment/AppointmentResponse.java +++ b/src/main/java/com/petshop/backend/dto/appointment/AppointmentResponse.java @@ -1,13 +1,10 @@ package com.petshop.backend.dto.appointment; - import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.util.List; - - - +import java.util.Objects; public class AppointmentResponse { private Long id; @@ -23,4 +20,159 @@ public class AppointmentResponse { private String notes; private LocalDateTime createdAt; private LocalDateTime updatedAt; + + public AppointmentResponse() { + } + + public AppointmentResponse(Long id, Long customerId, String customerName, Long serviceId, String serviceName, LocalDate appointmentDate, LocalTime appointmentTime, String status, List petNames, List petIds, String notes, LocalDateTime createdAt, LocalDateTime updatedAt) { + this.id = id; + this.customerId = customerId; + this.customerName = customerName; + this.serviceId = serviceId; + this.serviceName = serviceName; + this.appointmentDate = appointmentDate; + this.appointmentTime = appointmentTime; + this.status = status; + this.petNames = petNames; + this.petIds = petIds; + this.notes = notes; + this.createdAt = createdAt; + this.updatedAt = updatedAt; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getCustomerId() { + return customerId; + } + + public void setCustomerId(Long customerId) { + this.customerId = customerId; + } + + public String getCustomerName() { + return customerName; + } + + public void setCustomerName(String customerName) { + this.customerName = customerName; + } + + public Long getServiceId() { + return serviceId; + } + + public void setServiceId(Long serviceId) { + this.serviceId = serviceId; + } + + public String getServiceName() { + return serviceName; + } + + public void setServiceName(String serviceName) { + this.serviceName = serviceName; + } + + public LocalDate getAppointmentDate() { + return appointmentDate; + } + + public void setAppointmentDate(LocalDate appointmentDate) { + this.appointmentDate = appointmentDate; + } + + public LocalTime getAppointmentTime() { + return appointmentTime; + } + + public void setAppointmentTime(LocalTime appointmentTime) { + this.appointmentTime = appointmentTime; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public List getPetNames() { + return petNames; + } + + public void setPetNames(List petNames) { + this.petNames = petNames; + } + + public List getPetIds() { + return petIds; + } + + public void setPetIds(List petIds) { + this.petIds = petIds; + } + + public String getNotes() { + return notes; + } + + public void setNotes(String notes) { + this.notes = notes; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(LocalDateTime createdAt) { + this.createdAt = createdAt; + } + + public LocalDateTime getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(LocalDateTime updatedAt) { + this.updatedAt = updatedAt; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + AppointmentResponse that = (AppointmentResponse) o; + return Objects.equals(id, that.id) && Objects.equals(customerId, that.customerId) && Objects.equals(customerName, that.customerName) && Objects.equals(serviceId, that.serviceId) && Objects.equals(serviceName, that.serviceName) && Objects.equals(appointmentDate, that.appointmentDate) && Objects.equals(appointmentTime, that.appointmentTime) && Objects.equals(status, that.status) && Objects.equals(petNames, that.petNames) && Objects.equals(petIds, that.petIds) && Objects.equals(notes, that.notes) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt); + } + + @Override + public int hashCode() { + return Objects.hash(id, customerId, customerName, serviceId, serviceName, appointmentDate, appointmentTime, status, petNames, petIds, notes, createdAt, updatedAt); + } + + @Override + public String toString() { + return "AppointmentResponse{" + + "id=" + id + + ", customerId=" + customerId + + ", customerName='" + customerName + '\'' + + ", serviceId=" + serviceId + + ", serviceName='" + serviceName + '\'' + + ", appointmentDate=" + appointmentDate + + ", appointmentTime=" + appointmentTime + + ", status='" + status + '\'' + + ", petNames=" + petNames + + ", petIds=" + petIds + + ", notes='" + notes + '\'' + + ", createdAt=" + createdAt + + ", updatedAt=" + updatedAt + + '}'; + } } diff --git a/src/main/java/com/petshop/backend/dto/auth/LoginRequest.java b/src/main/java/com/petshop/backend/dto/auth/LoginRequest.java index 3adfd91a..970c4719 100644 --- a/src/main/java/com/petshop/backend/dto/auth/LoginRequest.java +++ b/src/main/java/com/petshop/backend/dto/auth/LoginRequest.java @@ -1,7 +1,7 @@ package com.petshop.backend.dto.auth; import jakarta.validation.constraints.NotBlank; - +import java.util.Objects; public class LoginRequest { @NotBlank(message = "Username is required") @@ -9,4 +9,42 @@ public class LoginRequest { @NotBlank(message = "Password is required") private String password; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + LoginRequest that = (LoginRequest) o; + return Objects.equals(username, that.username) && + Objects.equals(password, that.password); + } + + @Override + public int hashCode() { + return Objects.hash(username, password); + } + + @Override + public String toString() { + return "LoginRequest{" + + "username='" + username + '\'' + + ", password='" + password + '\'' + + '}'; + } } diff --git a/src/main/java/com/petshop/backend/dto/auth/LoginResponse.java b/src/main/java/com/petshop/backend/dto/auth/LoginResponse.java index 67574120..11141df6 100644 --- a/src/main/java/com/petshop/backend/dto/auth/LoginResponse.java +++ b/src/main/java/com/petshop/backend/dto/auth/LoginResponse.java @@ -1,11 +1,75 @@ package com.petshop.backend.dto.auth; - - +import java.util.Objects; public class LoginResponse { private String token; private String username; private String fullName; private String role; + + public LoginResponse() { + } + + public LoginResponse(String token, String username, String fullName, String role) { + this.token = token; + this.username = username; + this.fullName = fullName; + this.role = role; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getFullName() { + return fullName; + } + + public void setFullName(String fullName) { + this.fullName = fullName; + } + + public String getRole() { + return role; + } + + public void setRole(String role) { + this.role = role; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + LoginResponse that = (LoginResponse) o; + return Objects.equals(token, that.token) && Objects.equals(username, that.username) && Objects.equals(fullName, that.fullName) && Objects.equals(role, that.role); + } + + @Override + public int hashCode() { + return Objects.hash(token, username, fullName, role); + } + + @Override + public String toString() { + return "LoginResponse{" + + "token='" + token + '\'' + + ", username='" + username + '\'' + + ", fullName='" + fullName + '\'' + + ", role='" + role + '\'' + + '}'; + } } diff --git a/src/main/java/com/petshop/backend/dto/auth/UserInfoResponse.java b/src/main/java/com/petshop/backend/dto/auth/UserInfoResponse.java index 9e863cb4..a3142040 100644 --- a/src/main/java/com/petshop/backend/dto/auth/UserInfoResponse.java +++ b/src/main/java/com/petshop/backend/dto/auth/UserInfoResponse.java @@ -1,7 +1,6 @@ package com.petshop.backend.dto.auth; - - +import java.util.Objects; public class UserInfoResponse { private Long id; @@ -9,4 +8,79 @@ public class UserInfoResponse { private String fullName; private String email; private String role; + + public UserInfoResponse() { + } + + public UserInfoResponse(Long id, String username, String fullName, String email, String role) { + this.id = id; + this.username = username; + this.fullName = fullName; + this.email = email; + this.role = role; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getFullName() { + return fullName; + } + + public void setFullName(String fullName) { + this.fullName = fullName; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getRole() { + return role; + } + + public void setRole(String role) { + this.role = role; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + UserInfoResponse that = (UserInfoResponse) o; + return Objects.equals(id, that.id) && Objects.equals(username, that.username) && Objects.equals(fullName, that.fullName) && Objects.equals(email, that.email) && Objects.equals(role, that.role); + } + + @Override + public int hashCode() { + return Objects.hash(id, username, fullName, email, role); + } + + @Override + public String toString() { + return "UserInfoResponse{" + + "id=" + id + + ", username='" + username + '\'' + + ", fullName='" + fullName + '\'' + + ", email='" + email + '\'' + + ", role='" + role + '\'' + + '}'; + } } diff --git a/src/main/java/com/petshop/backend/dto/category/CategoryRequest.java b/src/main/java/com/petshop/backend/dto/category/CategoryRequest.java index 19b7d4b4..e73cca59 100644 --- a/src/main/java/com/petshop/backend/dto/category/CategoryRequest.java +++ b/src/main/java/com/petshop/backend/dto/category/CategoryRequest.java @@ -1,11 +1,49 @@ package com.petshop.backend.dto.category; import jakarta.validation.constraints.NotBlank; - +import java.util.Objects; public class CategoryRequest { @NotBlank(message = "Category name is required") private String categoryName; private String categoryDescription; + + public String getCategoryName() { + return categoryName; + } + + public void setCategoryName(String categoryName) { + this.categoryName = categoryName; + } + + public String getCategoryDescription() { + return categoryDescription; + } + + public void setCategoryDescription(String categoryDescription) { + this.categoryDescription = categoryDescription; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + CategoryRequest that = (CategoryRequest) o; + return Objects.equals(categoryName, that.categoryName) && + Objects.equals(categoryDescription, that.categoryDescription); + } + + @Override + public int hashCode() { + return Objects.hash(categoryName, categoryDescription); + } + + @Override + public String toString() { + return "CategoryRequest{" + + "categoryName='" + categoryName + '\'' + + ", categoryDescription='" + categoryDescription + '\'' + + '}'; + } } diff --git a/src/main/java/com/petshop/backend/dto/category/CategoryResponse.java b/src/main/java/com/petshop/backend/dto/category/CategoryResponse.java index 758be129..fe008c16 100644 --- a/src/main/java/com/petshop/backend/dto/category/CategoryResponse.java +++ b/src/main/java/com/petshop/backend/dto/category/CategoryResponse.java @@ -1,10 +1,7 @@ package com.petshop.backend.dto.category; - import java.time.LocalDateTime; - - - +import java.util.Objects; public class CategoryResponse { private Long id; @@ -12,4 +9,79 @@ public class CategoryResponse { private String categoryDescription; private LocalDateTime createdAt; private LocalDateTime updatedAt; + + public CategoryResponse() { + } + + public CategoryResponse(Long id, String categoryName, String categoryDescription, LocalDateTime createdAt, LocalDateTime updatedAt) { + this.id = id; + this.categoryName = categoryName; + this.categoryDescription = categoryDescription; + this.createdAt = createdAt; + this.updatedAt = updatedAt; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getCategoryName() { + return categoryName; + } + + public void setCategoryName(String categoryName) { + this.categoryName = categoryName; + } + + public String getCategoryDescription() { + return categoryDescription; + } + + public void setCategoryDescription(String categoryDescription) { + this.categoryDescription = categoryDescription; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(LocalDateTime createdAt) { + this.createdAt = createdAt; + } + + public LocalDateTime getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(LocalDateTime updatedAt) { + this.updatedAt = updatedAt; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + CategoryResponse that = (CategoryResponse) o; + return Objects.equals(id, that.id) && Objects.equals(categoryName, that.categoryName) && Objects.equals(categoryDescription, that.categoryDescription) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt); + } + + @Override + public int hashCode() { + return Objects.hash(id, categoryName, categoryDescription, createdAt, updatedAt); + } + + @Override + public String toString() { + return "CategoryResponse{" + + "id=" + id + + ", categoryName='" + categoryName + '\'' + + ", categoryDescription='" + categoryDescription + '\'' + + ", createdAt=" + createdAt + + ", updatedAt=" + updatedAt + + '}'; + } } diff --git a/src/main/java/com/petshop/backend/dto/common/BulkDeleteRequest.java b/src/main/java/com/petshop/backend/dto/common/BulkDeleteRequest.java index 99391913..bec920f1 100644 --- a/src/main/java/com/petshop/backend/dto/common/BulkDeleteRequest.java +++ b/src/main/java/com/petshop/backend/dto/common/BulkDeleteRequest.java @@ -1,11 +1,38 @@ package com.petshop.backend.dto.common; import jakarta.validation.constraints.NotEmpty; - import java.util.List; - +import java.util.Objects; public class BulkDeleteRequest { @NotEmpty(message = "IDs list cannot be empty") private List ids; + + public List getIds() { + return ids; + } + + public void setIds(List ids) { + this.ids = ids; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + BulkDeleteRequest that = (BulkDeleteRequest) o; + return Objects.equals(ids, that.ids); + } + + @Override + public int hashCode() { + return Objects.hash(ids); + } + + @Override + public String toString() { + return "BulkDeleteRequest{" + + "ids=" + ids + + '}'; + } } diff --git a/src/main/java/com/petshop/backend/dto/common/DropdownOption.java b/src/main/java/com/petshop/backend/dto/common/DropdownOption.java index 494fa5a7..6ea0d058 100644 --- a/src/main/java/com/petshop/backend/dto/common/DropdownOption.java +++ b/src/main/java/com/petshop/backend/dto/common/DropdownOption.java @@ -1,9 +1,53 @@ package com.petshop.backend.dto.common; - - +import java.util.Objects; public class DropdownOption { private Long id; private String label; + + public DropdownOption() { + } + + public DropdownOption(Long id, String label) { + this.id = id; + this.label = label; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + DropdownOption that = (DropdownOption) o; + return Objects.equals(id, that.id) && Objects.equals(label, that.label); + } + + @Override + public int hashCode() { + return Objects.hash(id, label); + } + + @Override + public String toString() { + return "DropdownOption{" + + "id=" + id + + ", label='" + label + '\'' + + '}'; + } } diff --git a/src/main/java/com/petshop/backend/dto/customer/CustomerRequest.java b/src/main/java/com/petshop/backend/dto/customer/CustomerRequest.java index 5f068b32..464c80b7 100644 --- a/src/main/java/com/petshop/backend/dto/customer/CustomerRequest.java +++ b/src/main/java/com/petshop/backend/dto/customer/CustomerRequest.java @@ -2,7 +2,7 @@ package com.petshop.backend.dto.customer; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; - +import java.util.Objects; public class CustomerRequest { @NotBlank(message = "Customer name is required") @@ -13,4 +13,62 @@ public class CustomerRequest { private String customerPhone; private String customerAddress; + + public String getCustomerName() { + return customerName; + } + + public void setCustomerName(String customerName) { + this.customerName = customerName; + } + + public String getCustomerEmail() { + return customerEmail; + } + + public void setCustomerEmail(String customerEmail) { + this.customerEmail = customerEmail; + } + + public String getCustomerPhone() { + return customerPhone; + } + + public void setCustomerPhone(String customerPhone) { + this.customerPhone = customerPhone; + } + + public String getCustomerAddress() { + return customerAddress; + } + + public void setCustomerAddress(String customerAddress) { + this.customerAddress = customerAddress; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + CustomerRequest that = (CustomerRequest) o; + return Objects.equals(customerName, that.customerName) && + Objects.equals(customerEmail, that.customerEmail) && + Objects.equals(customerPhone, that.customerPhone) && + Objects.equals(customerAddress, that.customerAddress); + } + + @Override + public int hashCode() { + return Objects.hash(customerName, customerEmail, customerPhone, customerAddress); + } + + @Override + public String toString() { + return "CustomerRequest{" + + "customerName='" + customerName + '\'' + + ", customerEmail='" + customerEmail + '\'' + + ", customerPhone='" + customerPhone + '\'' + + ", customerAddress='" + customerAddress + '\'' + + '}'; + } } diff --git a/src/main/java/com/petshop/backend/dto/customer/CustomerResponse.java b/src/main/java/com/petshop/backend/dto/customer/CustomerResponse.java index 6f929bae..3e6be930 100644 --- a/src/main/java/com/petshop/backend/dto/customer/CustomerResponse.java +++ b/src/main/java/com/petshop/backend/dto/customer/CustomerResponse.java @@ -1,10 +1,7 @@ package com.petshop.backend.dto.customer; - import java.time.LocalDateTime; - - - +import java.util.Objects; public class CustomerResponse { private Long id; @@ -14,4 +11,99 @@ public class CustomerResponse { private String customerAddress; private LocalDateTime createdAt; private LocalDateTime updatedAt; + + public CustomerResponse() { + } + + public CustomerResponse(Long id, String customerName, String customerEmail, String customerPhone, String customerAddress, LocalDateTime createdAt, LocalDateTime updatedAt) { + this.id = id; + this.customerName = customerName; + this.customerEmail = customerEmail; + this.customerPhone = customerPhone; + this.customerAddress = customerAddress; + this.createdAt = createdAt; + this.updatedAt = updatedAt; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getCustomerName() { + return customerName; + } + + public void setCustomerName(String customerName) { + this.customerName = customerName; + } + + public String getCustomerEmail() { + return customerEmail; + } + + public void setCustomerEmail(String customerEmail) { + this.customerEmail = customerEmail; + } + + public String getCustomerPhone() { + return customerPhone; + } + + public void setCustomerPhone(String customerPhone) { + this.customerPhone = customerPhone; + } + + public String getCustomerAddress() { + return customerAddress; + } + + public void setCustomerAddress(String customerAddress) { + this.customerAddress = customerAddress; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(LocalDateTime createdAt) { + this.createdAt = createdAt; + } + + public LocalDateTime getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(LocalDateTime updatedAt) { + this.updatedAt = updatedAt; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + CustomerResponse that = (CustomerResponse) o; + return Objects.equals(id, that.id) && Objects.equals(customerName, that.customerName) && Objects.equals(customerEmail, that.customerEmail) && Objects.equals(customerPhone, that.customerPhone) && Objects.equals(customerAddress, that.customerAddress) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt); + } + + @Override + public int hashCode() { + return Objects.hash(id, customerName, customerEmail, customerPhone, customerAddress, createdAt, updatedAt); + } + + @Override + public String toString() { + return "CustomerResponse{" + + "id=" + id + + ", customerName='" + customerName + '\'' + + ", customerEmail='" + customerEmail + '\'' + + ", customerPhone='" + customerPhone + '\'' + + ", customerAddress='" + customerAddress + '\'' + + ", createdAt=" + createdAt + + ", updatedAt=" + updatedAt + + '}'; + } } diff --git a/src/main/java/com/petshop/backend/dto/inventory/InventoryRequest.java b/src/main/java/com/petshop/backend/dto/inventory/InventoryRequest.java index fb4ff6b9..947b7517 100644 --- a/src/main/java/com/petshop/backend/dto/inventory/InventoryRequest.java +++ b/src/main/java/com/petshop/backend/dto/inventory/InventoryRequest.java @@ -2,7 +2,7 @@ package com.petshop.backend.dto.inventory; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.PositiveOrZero; - +import java.util.Objects; public class InventoryRequest { @NotNull(message = "Product ID is required") @@ -17,4 +17,62 @@ public class InventoryRequest { @PositiveOrZero(message = "Reorder level must be zero or positive") private Integer reorderLevel = 10; + + public Long getProductId() { + return productId; + } + + public void setProductId(Long productId) { + this.productId = productId; + } + + public Long getStoreId() { + return storeId; + } + + public void setStoreId(Long storeId) { + this.storeId = storeId; + } + + public Integer getQuantity() { + return quantity; + } + + public void setQuantity(Integer quantity) { + this.quantity = quantity; + } + + public Integer getReorderLevel() { + return reorderLevel; + } + + public void setReorderLevel(Integer reorderLevel) { + this.reorderLevel = reorderLevel; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + InventoryRequest that = (InventoryRequest) o; + return Objects.equals(productId, that.productId) && + Objects.equals(storeId, that.storeId) && + Objects.equals(quantity, that.quantity) && + Objects.equals(reorderLevel, that.reorderLevel); + } + + @Override + public int hashCode() { + return Objects.hash(productId, storeId, quantity, reorderLevel); + } + + @Override + public String toString() { + return "InventoryRequest{" + + "productId=" + productId + + ", storeId=" + storeId + + ", quantity=" + quantity + + ", reorderLevel=" + reorderLevel + + '}'; + } } diff --git a/src/main/java/com/petshop/backend/dto/inventory/InventoryResponse.java b/src/main/java/com/petshop/backend/dto/inventory/InventoryResponse.java index feff7600..cb618959 100644 --- a/src/main/java/com/petshop/backend/dto/inventory/InventoryResponse.java +++ b/src/main/java/com/petshop/backend/dto/inventory/InventoryResponse.java @@ -1,10 +1,7 @@ package com.petshop.backend.dto.inventory; - import java.time.LocalDateTime; - - - +import java.util.Objects; public class InventoryResponse { private Long id; @@ -18,4 +15,139 @@ public class InventoryResponse { private LocalDateTime lastRestocked; private LocalDateTime createdAt; private LocalDateTime updatedAt; + + public InventoryResponse() { + } + + public InventoryResponse(Long id, Long productId, String productName, String categoryName, Long storeId, String storeName, Integer quantity, Integer reorderLevel, LocalDateTime lastRestocked, LocalDateTime createdAt, LocalDateTime updatedAt) { + this.id = id; + this.productId = productId; + this.productName = productName; + this.categoryName = categoryName; + this.storeId = storeId; + this.storeName = storeName; + this.quantity = quantity; + this.reorderLevel = reorderLevel; + this.lastRestocked = lastRestocked; + this.createdAt = createdAt; + this.updatedAt = updatedAt; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getProductId() { + return productId; + } + + public void setProductId(Long productId) { + this.productId = productId; + } + + public String getProductName() { + return productName; + } + + public void setProductName(String productName) { + this.productName = productName; + } + + public String getCategoryName() { + return categoryName; + } + + public void setCategoryName(String categoryName) { + this.categoryName = categoryName; + } + + public Long getStoreId() { + return storeId; + } + + public void setStoreId(Long storeId) { + this.storeId = storeId; + } + + public String getStoreName() { + return storeName; + } + + public void setStoreName(String storeName) { + this.storeName = storeName; + } + + public Integer getQuantity() { + return quantity; + } + + public void setQuantity(Integer quantity) { + this.quantity = quantity; + } + + public Integer getReorderLevel() { + return reorderLevel; + } + + public void setReorderLevel(Integer reorderLevel) { + this.reorderLevel = reorderLevel; + } + + public LocalDateTime getLastRestocked() { + return lastRestocked; + } + + public void setLastRestocked(LocalDateTime lastRestocked) { + this.lastRestocked = lastRestocked; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(LocalDateTime createdAt) { + this.createdAt = createdAt; + } + + public LocalDateTime getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(LocalDateTime updatedAt) { + this.updatedAt = updatedAt; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + InventoryResponse that = (InventoryResponse) o; + return Objects.equals(id, that.id) && Objects.equals(productId, that.productId) && Objects.equals(productName, that.productName) && Objects.equals(categoryName, that.categoryName) && Objects.equals(storeId, that.storeId) && Objects.equals(storeName, that.storeName) && Objects.equals(quantity, that.quantity) && Objects.equals(reorderLevel, that.reorderLevel) && Objects.equals(lastRestocked, that.lastRestocked) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt); + } + + @Override + public int hashCode() { + return Objects.hash(id, productId, productName, categoryName, storeId, storeName, quantity, reorderLevel, lastRestocked, createdAt, updatedAt); + } + + @Override + public String toString() { + return "InventoryResponse{" + + "id=" + id + + ", productId=" + productId + + ", productName='" + productName + '\'' + + ", categoryName='" + categoryName + '\'' + + ", storeId=" + storeId + + ", storeName='" + storeName + '\'' + + ", quantity=" + quantity + + ", reorderLevel=" + reorderLevel + + ", lastRestocked=" + lastRestocked + + ", createdAt=" + createdAt + + ", updatedAt=" + updatedAt + + '}'; + } } diff --git a/src/main/java/com/petshop/backend/dto/pet/PetRequest.java b/src/main/java/com/petshop/backend/dto/pet/PetRequest.java index 202dc6e7..aeb0f0db 100644 --- a/src/main/java/com/petshop/backend/dto/pet/PetRequest.java +++ b/src/main/java/com/petshop/backend/dto/pet/PetRequest.java @@ -4,9 +4,8 @@ import com.petshop.backend.entity.Pet; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Positive; - import java.math.BigDecimal; - +import java.util.Objects; public class PetRequest { @NotBlank(message = "Pet name is required") @@ -24,4 +23,82 @@ public class PetRequest { private Pet.PetStatus petStatus; private BigDecimal petPrice; + + public String getPetName() { + return petName; + } + + public void setPetName(String petName) { + this.petName = petName; + } + + public String getPetSpecies() { + return petSpecies; + } + + public void setPetSpecies(String petSpecies) { + this.petSpecies = petSpecies; + } + + public String getPetBreed() { + return petBreed; + } + + public void setPetBreed(String petBreed) { + this.petBreed = petBreed; + } + + public Integer getPetAge() { + return petAge; + } + + public void setPetAge(Integer petAge) { + this.petAge = petAge; + } + + public Pet.PetStatus getPetStatus() { + return petStatus; + } + + public void setPetStatus(Pet.PetStatus petStatus) { + this.petStatus = petStatus; + } + + public BigDecimal getPetPrice() { + return petPrice; + } + + public void setPetPrice(BigDecimal petPrice) { + this.petPrice = petPrice; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + PetRequest that = (PetRequest) o; + return Objects.equals(petName, that.petName) && + Objects.equals(petSpecies, that.petSpecies) && + Objects.equals(petBreed, that.petBreed) && + Objects.equals(petAge, that.petAge) && + petStatus == that.petStatus && + Objects.equals(petPrice, that.petPrice); + } + + @Override + public int hashCode() { + return Objects.hash(petName, petSpecies, petBreed, petAge, petStatus, petPrice); + } + + @Override + public String toString() { + return "PetRequest{" + + "petName='" + petName + '\'' + + ", petSpecies='" + petSpecies + '\'' + + ", petBreed='" + petBreed + '\'' + + ", petAge=" + petAge + + ", petStatus=" + petStatus + + ", petPrice=" + petPrice + + '}'; + } } diff --git a/src/main/java/com/petshop/backend/dto/pet/PetResponse.java b/src/main/java/com/petshop/backend/dto/pet/PetResponse.java index 9409b0ad..18e31381 100644 --- a/src/main/java/com/petshop/backend/dto/pet/PetResponse.java +++ b/src/main/java/com/petshop/backend/dto/pet/PetResponse.java @@ -1,11 +1,8 @@ package com.petshop.backend.dto.pet; - import java.math.BigDecimal; import java.time.LocalDateTime; - - - +import java.util.Objects; public class PetResponse { private Long id; @@ -17,4 +14,119 @@ public class PetResponse { private BigDecimal petPrice; private LocalDateTime createdAt; private LocalDateTime updatedAt; + + public PetResponse() { + } + + public PetResponse(Long id, String petName, String petSpecies, String petBreed, Integer petAge, String petStatus, BigDecimal petPrice, LocalDateTime createdAt, LocalDateTime updatedAt) { + this.id = id; + this.petName = petName; + this.petSpecies = petSpecies; + this.petBreed = petBreed; + this.petAge = petAge; + this.petStatus = petStatus; + this.petPrice = petPrice; + this.createdAt = createdAt; + this.updatedAt = updatedAt; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getPetName() { + return petName; + } + + public void setPetName(String petName) { + this.petName = petName; + } + + public String getPetSpecies() { + return petSpecies; + } + + public void setPetSpecies(String petSpecies) { + this.petSpecies = petSpecies; + } + + public String getPetBreed() { + return petBreed; + } + + public void setPetBreed(String petBreed) { + this.petBreed = petBreed; + } + + public Integer getPetAge() { + return petAge; + } + + public void setPetAge(Integer petAge) { + this.petAge = petAge; + } + + public String getPetStatus() { + return petStatus; + } + + public void setPetStatus(String petStatus) { + this.petStatus = petStatus; + } + + public BigDecimal getPetPrice() { + return petPrice; + } + + public void setPetPrice(BigDecimal petPrice) { + this.petPrice = petPrice; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(LocalDateTime createdAt) { + this.createdAt = createdAt; + } + + public LocalDateTime getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(LocalDateTime updatedAt) { + this.updatedAt = updatedAt; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + PetResponse that = (PetResponse) o; + return Objects.equals(id, that.id) && Objects.equals(petName, that.petName) && Objects.equals(petSpecies, that.petSpecies) && Objects.equals(petBreed, that.petBreed) && Objects.equals(petAge, that.petAge) && Objects.equals(petStatus, that.petStatus) && Objects.equals(petPrice, that.petPrice) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt); + } + + @Override + public int hashCode() { + return Objects.hash(id, petName, petSpecies, petBreed, petAge, petStatus, petPrice, createdAt, updatedAt); + } + + @Override + public String toString() { + return "PetResponse{" + + "id=" + id + + ", petName='" + petName + '\'' + + ", petSpecies='" + petSpecies + '\'' + + ", petBreed='" + petBreed + '\'' + + ", petAge=" + petAge + + ", petStatus='" + petStatus + '\'' + + ", petPrice=" + petPrice + + ", createdAt=" + createdAt + + ", updatedAt=" + updatedAt + + '}'; + } } diff --git a/src/main/java/com/petshop/backend/dto/product/ProductRequest.java b/src/main/java/com/petshop/backend/dto/product/ProductRequest.java index c784513d..e4f68fd7 100644 --- a/src/main/java/com/petshop/backend/dto/product/ProductRequest.java +++ b/src/main/java/com/petshop/backend/dto/product/ProductRequest.java @@ -3,9 +3,8 @@ package com.petshop.backend.dto.product; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Positive; - import java.math.BigDecimal; - +import java.util.Objects; public class ProductRequest { @NotBlank(message = "Product name is required") @@ -21,4 +20,72 @@ public class ProductRequest { private BigDecimal productPrice; private Boolean active = true; + + public String getProductName() { + return productName; + } + + public void setProductName(String productName) { + this.productName = productName; + } + + public Long getCategoryId() { + return categoryId; + } + + public void setCategoryId(Long categoryId) { + this.categoryId = categoryId; + } + + public String getProductDescription() { + return productDescription; + } + + public void setProductDescription(String productDescription) { + this.productDescription = productDescription; + } + + public BigDecimal getProductPrice() { + return productPrice; + } + + public void setProductPrice(BigDecimal productPrice) { + this.productPrice = productPrice; + } + + public Boolean getActive() { + return active; + } + + public void setActive(Boolean active) { + this.active = active; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ProductRequest that = (ProductRequest) o; + return Objects.equals(productName, that.productName) && + Objects.equals(categoryId, that.categoryId) && + Objects.equals(productDescription, that.productDescription) && + Objects.equals(productPrice, that.productPrice) && + Objects.equals(active, that.active); + } + + @Override + public int hashCode() { + return Objects.hash(productName, categoryId, productDescription, productPrice, active); + } + + @Override + public String toString() { + return "ProductRequest{" + + "productName='" + productName + '\'' + + ", categoryId=" + categoryId + + ", productDescription='" + productDescription + '\'' + + ", productPrice=" + productPrice + + ", active=" + active + + '}'; + } } diff --git a/src/main/java/com/petshop/backend/dto/product/ProductResponse.java b/src/main/java/com/petshop/backend/dto/product/ProductResponse.java index ec6b3ae9..d54632c4 100644 --- a/src/main/java/com/petshop/backend/dto/product/ProductResponse.java +++ b/src/main/java/com/petshop/backend/dto/product/ProductResponse.java @@ -1,11 +1,8 @@ package com.petshop.backend.dto.product; - import java.math.BigDecimal; import java.time.LocalDateTime; - - - +import java.util.Objects; public class ProductResponse { private Long id; @@ -17,4 +14,119 @@ public class ProductResponse { private Boolean active; private LocalDateTime createdAt; private LocalDateTime updatedAt; + + public ProductResponse() { + } + + public ProductResponse(Long id, String productName, Long categoryId, String categoryName, String productDescription, BigDecimal productPrice, Boolean active, LocalDateTime createdAt, LocalDateTime updatedAt) { + this.id = id; + this.productName = productName; + this.categoryId = categoryId; + this.categoryName = categoryName; + this.productDescription = productDescription; + this.productPrice = productPrice; + this.active = active; + this.createdAt = createdAt; + this.updatedAt = updatedAt; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getProductName() { + return productName; + } + + public void setProductName(String productName) { + this.productName = productName; + } + + public Long getCategoryId() { + return categoryId; + } + + public void setCategoryId(Long categoryId) { + this.categoryId = categoryId; + } + + public String getCategoryName() { + return categoryName; + } + + public void setCategoryName(String categoryName) { + this.categoryName = categoryName; + } + + public String getProductDescription() { + return productDescription; + } + + public void setProductDescription(String productDescription) { + this.productDescription = productDescription; + } + + public BigDecimal getProductPrice() { + return productPrice; + } + + public void setProductPrice(BigDecimal productPrice) { + this.productPrice = productPrice; + } + + public Boolean getActive() { + return active; + } + + public void setActive(Boolean active) { + this.active = active; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(LocalDateTime createdAt) { + this.createdAt = createdAt; + } + + public LocalDateTime getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(LocalDateTime updatedAt) { + this.updatedAt = updatedAt; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ProductResponse that = (ProductResponse) o; + return Objects.equals(id, that.id) && Objects.equals(productName, that.productName) && Objects.equals(categoryId, that.categoryId) && Objects.equals(categoryName, that.categoryName) && Objects.equals(productDescription, that.productDescription) && Objects.equals(productPrice, that.productPrice) && Objects.equals(active, that.active) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt); + } + + @Override + public int hashCode() { + return Objects.hash(id, productName, categoryId, categoryName, productDescription, productPrice, active, createdAt, updatedAt); + } + + @Override + public String toString() { + return "ProductResponse{" + + "id=" + id + + ", productName='" + productName + '\'' + + ", categoryId=" + categoryId + + ", categoryName='" + categoryName + '\'' + + ", productDescription='" + productDescription + '\'' + + ", productPrice=" + productPrice + + ", active=" + active + + ", createdAt=" + createdAt + + ", updatedAt=" + updatedAt + + '}'; + } } diff --git a/src/main/java/com/petshop/backend/dto/productsupplier/BulkDeleteProductSupplierRequest.java b/src/main/java/com/petshop/backend/dto/productsupplier/BulkDeleteProductSupplierRequest.java index 97704bd8..7eb8a3cc 100644 --- a/src/main/java/com/petshop/backend/dto/productsupplier/BulkDeleteProductSupplierRequest.java +++ b/src/main/java/com/petshop/backend/dto/productsupplier/BulkDeleteProductSupplierRequest.java @@ -1,11 +1,38 @@ package com.petshop.backend.dto.productsupplier; import jakarta.validation.constraints.NotEmpty; - import java.util.List; - +import java.util.Objects; public class BulkDeleteProductSupplierRequest { @NotEmpty(message = "Keys list cannot be empty") private List keys; + + public List getKeys() { + return keys; + } + + public void setKeys(List keys) { + this.keys = keys; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + BulkDeleteProductSupplierRequest that = (BulkDeleteProductSupplierRequest) o; + return Objects.equals(keys, that.keys); + } + + @Override + public int hashCode() { + return Objects.hash(keys); + } + + @Override + public String toString() { + return "BulkDeleteProductSupplierRequest{" + + "keys=" + keys + + '}'; + } } diff --git a/src/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierKey.java b/src/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierKey.java index ca1c0c96..557dbeda 100644 --- a/src/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierKey.java +++ b/src/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierKey.java @@ -1,12 +1,54 @@ package com.petshop.backend.dto.productsupplier; -import jakarta.validation.constraints.NotEmpty; - -import java.util.List; - +import java.util.Objects; public class ProductSupplierKey { private Long productId; private Long supplierId; + + public ProductSupplierKey() { + } + + public ProductSupplierKey(Long productId, Long supplierId) { + this.productId = productId; + this.supplierId = supplierId; + } + + public Long getProductId() { + return productId; + } + + public void setProductId(Long productId) { + this.productId = productId; + } + + public Long getSupplierId() { + return supplierId; + } + + public void setSupplierId(Long supplierId) { + this.supplierId = supplierId; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ProductSupplierKey that = (ProductSupplierKey) o; + return Objects.equals(productId, that.productId) && Objects.equals(supplierId, that.supplierId); + } + + @Override + public int hashCode() { + return Objects.hash(productId, supplierId); + } + + @Override + public String toString() { + return "ProductSupplierKey{" + + "productId=" + productId + + ", supplierId=" + supplierId + + '}'; + } } diff --git a/src/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierRequest.java b/src/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierRequest.java index 7e0ef87a..7854b37e 100644 --- a/src/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierRequest.java +++ b/src/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierRequest.java @@ -3,9 +3,8 @@ package com.petshop.backend.dto.productsupplier; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Positive; import jakarta.validation.constraints.PositiveOrZero; - import java.math.BigDecimal; - +import java.util.Objects; public class ProductSupplierRequest { @NotNull(message = "Product ID is required") @@ -22,4 +21,72 @@ public class ProductSupplierRequest { private Integer leadTimeDays; private Boolean isPreferred = false; + + public Long getProductId() { + return productId; + } + + public void setProductId(Long productId) { + this.productId = productId; + } + + public Long getSupplierId() { + return supplierId; + } + + public void setSupplierId(Long supplierId) { + this.supplierId = supplierId; + } + + public BigDecimal getCostPrice() { + return costPrice; + } + + public void setCostPrice(BigDecimal costPrice) { + this.costPrice = costPrice; + } + + public Integer getLeadTimeDays() { + return leadTimeDays; + } + + public void setLeadTimeDays(Integer leadTimeDays) { + this.leadTimeDays = leadTimeDays; + } + + public Boolean getIsPreferred() { + return isPreferred; + } + + public void setIsPreferred(Boolean isPreferred) { + this.isPreferred = isPreferred; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ProductSupplierRequest that = (ProductSupplierRequest) o; + return Objects.equals(productId, that.productId) && + Objects.equals(supplierId, that.supplierId) && + Objects.equals(costPrice, that.costPrice) && + Objects.equals(leadTimeDays, that.leadTimeDays) && + Objects.equals(isPreferred, that.isPreferred); + } + + @Override + public int hashCode() { + return Objects.hash(productId, supplierId, costPrice, leadTimeDays, isPreferred); + } + + @Override + public String toString() { + return "ProductSupplierRequest{" + + "productId=" + productId + + ", supplierId=" + supplierId + + ", costPrice=" + costPrice + + ", leadTimeDays=" + leadTimeDays + + ", isPreferred=" + isPreferred + + '}'; + } } diff --git a/src/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierResponse.java b/src/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierResponse.java index 49535076..44cafe3c 100644 --- a/src/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierResponse.java +++ b/src/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierResponse.java @@ -1,11 +1,8 @@ package com.petshop.backend.dto.productsupplier; - import java.math.BigDecimal; import java.time.LocalDateTime; - - - +import java.util.Objects; public class ProductSupplierResponse { private Long productId; @@ -17,4 +14,119 @@ public class ProductSupplierResponse { private Boolean isPreferred; private LocalDateTime createdAt; private LocalDateTime updatedAt; + + public ProductSupplierResponse() { + } + + public ProductSupplierResponse(Long productId, String productName, Long supplierId, String supplierName, BigDecimal costPrice, Integer leadTimeDays, Boolean isPreferred, LocalDateTime createdAt, LocalDateTime updatedAt) { + this.productId = productId; + this.productName = productName; + this.supplierId = supplierId; + this.supplierName = supplierName; + this.costPrice = costPrice; + this.leadTimeDays = leadTimeDays; + this.isPreferred = isPreferred; + this.createdAt = createdAt; + this.updatedAt = updatedAt; + } + + public Long getProductId() { + return productId; + } + + public void setProductId(Long productId) { + this.productId = productId; + } + + public String getProductName() { + return productName; + } + + public void setProductName(String productName) { + this.productName = productName; + } + + public Long getSupplierId() { + return supplierId; + } + + public void setSupplierId(Long supplierId) { + this.supplierId = supplierId; + } + + public String getSupplierName() { + return supplierName; + } + + public void setSupplierName(String supplierName) { + this.supplierName = supplierName; + } + + public BigDecimal getCostPrice() { + return costPrice; + } + + public void setCostPrice(BigDecimal costPrice) { + this.costPrice = costPrice; + } + + public Integer getLeadTimeDays() { + return leadTimeDays; + } + + public void setLeadTimeDays(Integer leadTimeDays) { + this.leadTimeDays = leadTimeDays; + } + + public Boolean getIsPreferred() { + return isPreferred; + } + + public void setIsPreferred(Boolean isPreferred) { + this.isPreferred = isPreferred; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(LocalDateTime createdAt) { + this.createdAt = createdAt; + } + + public LocalDateTime getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(LocalDateTime updatedAt) { + this.updatedAt = updatedAt; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ProductSupplierResponse that = (ProductSupplierResponse) o; + return Objects.equals(productId, that.productId) && Objects.equals(productName, that.productName) && Objects.equals(supplierId, that.supplierId) && Objects.equals(supplierName, that.supplierName) && Objects.equals(costPrice, that.costPrice) && Objects.equals(leadTimeDays, that.leadTimeDays) && Objects.equals(isPreferred, that.isPreferred) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt); + } + + @Override + public int hashCode() { + return Objects.hash(productId, productName, supplierId, supplierName, costPrice, leadTimeDays, isPreferred, createdAt, updatedAt); + } + + @Override + public String toString() { + return "ProductSupplierResponse{" + + "productId=" + productId + + ", productName='" + productName + '\'' + + ", supplierId=" + supplierId + + ", supplierName='" + supplierName + '\'' + + ", costPrice=" + costPrice + + ", leadTimeDays=" + leadTimeDays + + ", isPreferred=" + isPreferred + + ", createdAt=" + createdAt + + ", updatedAt=" + updatedAt + + '}'; + } } diff --git a/src/main/java/com/petshop/backend/dto/purchaseorder/PurchaseOrderResponse.java b/src/main/java/com/petshop/backend/dto/purchaseorder/PurchaseOrderResponse.java index d6f214c2..403aa274 100644 --- a/src/main/java/com/petshop/backend/dto/purchaseorder/PurchaseOrderResponse.java +++ b/src/main/java/com/petshop/backend/dto/purchaseorder/PurchaseOrderResponse.java @@ -1,13 +1,10 @@ package com.petshop.backend.dto.purchaseorder; - import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.List; - - - +import java.util.Objects; public class PurchaseOrderResponse { private Long id; @@ -22,9 +19,141 @@ public class PurchaseOrderResponse { private LocalDateTime createdAt; private LocalDateTime updatedAt; - - - + public PurchaseOrderResponse() { + } + + public PurchaseOrderResponse(Long id, Long supplierId, String supplierName, LocalDate orderDate, LocalDate expectedDelivery, String status, BigDecimal totalAmount, String notes, List items, LocalDateTime createdAt, LocalDateTime updatedAt) { + this.id = id; + this.supplierId = supplierId; + this.supplierName = supplierName; + this.orderDate = orderDate; + this.expectedDelivery = expectedDelivery; + this.status = status; + this.totalAmount = totalAmount; + this.notes = notes; + this.items = items; + this.createdAt = createdAt; + this.updatedAt = updatedAt; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getSupplierId() { + return supplierId; + } + + public void setSupplierId(Long supplierId) { + this.supplierId = supplierId; + } + + public String getSupplierName() { + return supplierName; + } + + public void setSupplierName(String supplierName) { + this.supplierName = supplierName; + } + + public LocalDate getOrderDate() { + return orderDate; + } + + public void setOrderDate(LocalDate orderDate) { + this.orderDate = orderDate; + } + + public LocalDate getExpectedDelivery() { + return expectedDelivery; + } + + public void setExpectedDelivery(LocalDate expectedDelivery) { + this.expectedDelivery = expectedDelivery; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public BigDecimal getTotalAmount() { + return totalAmount; + } + + public void setTotalAmount(BigDecimal totalAmount) { + this.totalAmount = totalAmount; + } + + public String getNotes() { + return notes; + } + + public void setNotes(String notes) { + this.notes = notes; + } + + public List getItems() { + return items; + } + + public void setItems(List items) { + this.items = items; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(LocalDateTime createdAt) { + this.createdAt = createdAt; + } + + public LocalDateTime getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(LocalDateTime updatedAt) { + this.updatedAt = updatedAt; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + PurchaseOrderResponse that = (PurchaseOrderResponse) o; + return Objects.equals(id, that.id) && Objects.equals(supplierId, that.supplierId) && Objects.equals(supplierName, that.supplierName) && Objects.equals(orderDate, that.orderDate) && Objects.equals(expectedDelivery, that.expectedDelivery) && Objects.equals(status, that.status) && Objects.equals(totalAmount, that.totalAmount) && Objects.equals(notes, that.notes) && Objects.equals(items, that.items) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt); + } + + @Override + public int hashCode() { + return Objects.hash(id, supplierId, supplierName, orderDate, expectedDelivery, status, totalAmount, notes, items, createdAt, updatedAt); + } + + @Override + public String toString() { + return "PurchaseOrderResponse{" + + "id=" + id + + ", supplierId=" + supplierId + + ", supplierName='" + supplierName + '\'' + + ", orderDate=" + orderDate + + ", expectedDelivery=" + expectedDelivery + + ", status='" + status + '\'' + + ", totalAmount=" + totalAmount + + ", notes='" + notes + '\'' + + ", items=" + items + + ", createdAt=" + createdAt + + ", updatedAt=" + updatedAt + + '}'; + } + public static class PurchaseOrderItemResponse { private Long id; private Long productId; @@ -32,5 +161,90 @@ public class PurchaseOrderResponse { private Integer quantity; private BigDecimal unitCost; private BigDecimal subtotal; + + public PurchaseOrderItemResponse() { + } + + public PurchaseOrderItemResponse(Long id, Long productId, String productName, Integer quantity, BigDecimal unitCost, BigDecimal subtotal) { + this.id = id; + this.productId = productId; + this.productName = productName; + this.quantity = quantity; + this.unitCost = unitCost; + this.subtotal = subtotal; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getProductId() { + return productId; + } + + public void setProductId(Long productId) { + this.productId = productId; + } + + public String getProductName() { + return productName; + } + + public void setProductName(String productName) { + this.productName = productName; + } + + public Integer getQuantity() { + return quantity; + } + + public void setQuantity(Integer quantity) { + this.quantity = quantity; + } + + public BigDecimal getUnitCost() { + return unitCost; + } + + public void setUnitCost(BigDecimal unitCost) { + this.unitCost = unitCost; + } + + public BigDecimal getSubtotal() { + return subtotal; + } + + public void setSubtotal(BigDecimal subtotal) { + this.subtotal = subtotal; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + PurchaseOrderItemResponse that = (PurchaseOrderItemResponse) o; + return Objects.equals(id, that.id) && Objects.equals(productId, that.productId) && Objects.equals(productName, that.productName) && Objects.equals(quantity, that.quantity) && Objects.equals(unitCost, that.unitCost) && Objects.equals(subtotal, that.subtotal); + } + + @Override + public int hashCode() { + return Objects.hash(id, productId, productName, quantity, unitCost, subtotal); + } + + @Override + public String toString() { + return "PurchaseOrderItemResponse{" + + "id=" + id + + ", productId=" + productId + + ", productName='" + productName + '\'' + + ", quantity=" + quantity + + ", unitCost=" + unitCost + + ", subtotal=" + subtotal + + '}'; + } } } diff --git a/src/main/java/com/petshop/backend/dto/refund/RefundItemRequest.java b/src/main/java/com/petshop/backend/dto/refund/RefundItemRequest.java index 96ed87f8..5c28c3f3 100644 --- a/src/main/java/com/petshop/backend/dto/refund/RefundItemRequest.java +++ b/src/main/java/com/petshop/backend/dto/refund/RefundItemRequest.java @@ -2,7 +2,7 @@ package com.petshop.backend.dto.refund; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Positive; - +import java.util.Objects; public class RefundItemRequest { @NotNull(message = "Sale item ID is required") @@ -11,4 +11,42 @@ public class RefundItemRequest { @NotNull(message = "Quantity is required") @Positive(message = "Quantity must be positive") private Integer quantity; + + public Long getSaleItemId() { + return saleItemId; + } + + public void setSaleItemId(Long saleItemId) { + this.saleItemId = saleItemId; + } + + public Integer getQuantity() { + return quantity; + } + + public void setQuantity(Integer quantity) { + this.quantity = quantity; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + RefundItemRequest that = (RefundItemRequest) o; + return Objects.equals(saleItemId, that.saleItemId) && + Objects.equals(quantity, that.quantity); + } + + @Override + public int hashCode() { + return Objects.hash(saleItemId, quantity); + } + + @Override + public String toString() { + return "RefundItemRequest{" + + "saleItemId=" + saleItemId + + ", quantity=" + quantity + + '}'; + } } diff --git a/src/main/java/com/petshop/backend/dto/refund/RefundRequest.java b/src/main/java/com/petshop/backend/dto/refund/RefundRequest.java index 677c3439..154e838f 100644 --- a/src/main/java/com/petshop/backend/dto/refund/RefundRequest.java +++ b/src/main/java/com/petshop/backend/dto/refund/RefundRequest.java @@ -2,11 +2,8 @@ package com.petshop.backend.dto.refund; import jakarta.validation.Valid; import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Positive; - import java.util.List; - +import java.util.Objects; public class RefundRequest { @NotEmpty(message = "At least one item is required") @@ -14,4 +11,42 @@ public class RefundRequest { private List items; private String refundReason; + + public List getItems() { + return items; + } + + public void setItems(List items) { + this.items = items; + } + + public String getRefundReason() { + return refundReason; + } + + public void setRefundReason(String refundReason) { + this.refundReason = refundReason; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + RefundRequest that = (RefundRequest) o; + return Objects.equals(items, that.items) && + Objects.equals(refundReason, that.refundReason); + } + + @Override + public int hashCode() { + return Objects.hash(items, refundReason); + } + + @Override + public String toString() { + return "RefundRequest{" + + "items=" + items + + ", refundReason='" + refundReason + '\'' + + '}'; + } } diff --git a/src/main/java/com/petshop/backend/dto/refund/RefundResponse.java b/src/main/java/com/petshop/backend/dto/refund/RefundResponse.java index 2f031b65..a502fb25 100644 --- a/src/main/java/com/petshop/backend/dto/refund/RefundResponse.java +++ b/src/main/java/com/petshop/backend/dto/refund/RefundResponse.java @@ -1,12 +1,9 @@ package com.petshop.backend.dto.refund; - import java.math.BigDecimal; import java.time.LocalDateTime; import java.util.List; - - - +import java.util.Objects; public class RefundResponse { private Long id; @@ -19,9 +16,121 @@ public class RefundResponse { private List items; private LocalDateTime createdAt; - - - + public RefundResponse() { + } + + public RefundResponse(Long id, Long saleId, LocalDateTime refundDate, BigDecimal refundAmount, String refundReason, Long processedBy, String processedByName, List items, LocalDateTime createdAt) { + this.id = id; + this.saleId = saleId; + this.refundDate = refundDate; + this.refundAmount = refundAmount; + this.refundReason = refundReason; + this.processedBy = processedBy; + this.processedByName = processedByName; + this.items = items; + this.createdAt = createdAt; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getSaleId() { + return saleId; + } + + public void setSaleId(Long saleId) { + this.saleId = saleId; + } + + public LocalDateTime getRefundDate() { + return refundDate; + } + + public void setRefundDate(LocalDateTime refundDate) { + this.refundDate = refundDate; + } + + public BigDecimal getRefundAmount() { + return refundAmount; + } + + public void setRefundAmount(BigDecimal refundAmount) { + this.refundAmount = refundAmount; + } + + public String getRefundReason() { + return refundReason; + } + + public void setRefundReason(String refundReason) { + this.refundReason = refundReason; + } + + public Long getProcessedBy() { + return processedBy; + } + + public void setProcessedBy(Long processedBy) { + this.processedBy = processedBy; + } + + public String getProcessedByName() { + return processedByName; + } + + public void setProcessedByName(String processedByName) { + this.processedByName = processedByName; + } + + public List getItems() { + return items; + } + + public void setItems(List items) { + this.items = items; + } + + 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; + RefundResponse that = (RefundResponse) o; + return Objects.equals(id, that.id) && Objects.equals(saleId, that.saleId) && Objects.equals(refundDate, that.refundDate) && Objects.equals(refundAmount, that.refundAmount) && Objects.equals(refundReason, that.refundReason) && Objects.equals(processedBy, that.processedBy) && Objects.equals(processedByName, that.processedByName) && Objects.equals(items, that.items) && Objects.equals(createdAt, that.createdAt); + } + + @Override + public int hashCode() { + return Objects.hash(id, saleId, refundDate, refundAmount, refundReason, processedBy, processedByName, items, createdAt); + } + + @Override + public String toString() { + return "RefundResponse{" + + "id=" + id + + ", saleId=" + saleId + + ", refundDate=" + refundDate + + ", refundAmount=" + refundAmount + + ", refundReason='" + refundReason + '\'' + + ", processedBy=" + processedBy + + ", processedByName='" + processedByName + '\'' + + ", items=" + items + + ", createdAt=" + createdAt + + '}'; + } + public static class RefundItemResponse { private Long id; private Long saleItemId; @@ -29,5 +138,90 @@ public class RefundResponse { private String productName; private Integer quantity; private BigDecimal refundAmount; + + public RefundItemResponse() { + } + + public RefundItemResponse(Long id, Long saleItemId, Long productId, String productName, Integer quantity, BigDecimal refundAmount) { + this.id = id; + this.saleItemId = saleItemId; + this.productId = productId; + this.productName = productName; + this.quantity = quantity; + this.refundAmount = refundAmount; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getSaleItemId() { + return saleItemId; + } + + public void setSaleItemId(Long saleItemId) { + this.saleItemId = saleItemId; + } + + public Long getProductId() { + return productId; + } + + public void setProductId(Long productId) { + this.productId = productId; + } + + public String getProductName() { + return productName; + } + + public void setProductName(String productName) { + this.productName = productName; + } + + public Integer getQuantity() { + return quantity; + } + + public void setQuantity(Integer quantity) { + this.quantity = quantity; + } + + public BigDecimal getRefundAmount() { + return refundAmount; + } + + public void setRefundAmount(BigDecimal refundAmount) { + this.refundAmount = refundAmount; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + RefundItemResponse that = (RefundItemResponse) o; + return Objects.equals(id, that.id) && Objects.equals(saleItemId, that.saleItemId) && Objects.equals(productId, that.productId) && Objects.equals(productName, that.productName) && Objects.equals(quantity, that.quantity) && Objects.equals(refundAmount, that.refundAmount); + } + + @Override + public int hashCode() { + return Objects.hash(id, saleItemId, productId, productName, quantity, refundAmount); + } + + @Override + public String toString() { + return "RefundItemResponse{" + + "id=" + id + + ", saleItemId=" + saleItemId + + ", productId=" + productId + + ", productName='" + productName + '\'' + + ", quantity=" + quantity + + ", refundAmount=" + refundAmount + + '}'; + } } } diff --git a/src/main/java/com/petshop/backend/dto/sale/SaleItemRequest.java b/src/main/java/com/petshop/backend/dto/sale/SaleItemRequest.java index 0722fc77..985aa0c4 100644 --- a/src/main/java/com/petshop/backend/dto/sale/SaleItemRequest.java +++ b/src/main/java/com/petshop/backend/dto/sale/SaleItemRequest.java @@ -2,7 +2,7 @@ package com.petshop.backend.dto.sale; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Positive; - +import java.util.Objects; public class SaleItemRequest { @NotNull(message = "Product ID is required") @@ -11,4 +11,42 @@ public class SaleItemRequest { @NotNull(message = "Quantity is required") @Positive(message = "Quantity must be positive") private Integer quantity; + + public Long getProductId() { + return productId; + } + + public void setProductId(Long productId) { + this.productId = productId; + } + + public Integer getQuantity() { + return quantity; + } + + public void setQuantity(Integer quantity) { + this.quantity = quantity; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + SaleItemRequest that = (SaleItemRequest) o; + return Objects.equals(productId, that.productId) && + Objects.equals(quantity, that.quantity); + } + + @Override + public int hashCode() { + return Objects.hash(productId, quantity); + } + + @Override + public String toString() { + return "SaleItemRequest{" + + "productId=" + productId + + ", quantity=" + quantity + + '}'; + } } diff --git a/src/main/java/com/petshop/backend/dto/sale/SaleRequest.java b/src/main/java/com/petshop/backend/dto/sale/SaleRequest.java index 26b22952..bece02f5 100644 --- a/src/main/java/com/petshop/backend/dto/sale/SaleRequest.java +++ b/src/main/java/com/petshop/backend/dto/sale/SaleRequest.java @@ -3,11 +3,9 @@ package com.petshop.backend.dto.sale; import jakarta.validation.Valid; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Positive; - import java.math.BigDecimal; import java.util.List; - +import java.util.Objects; public class SaleRequest { private Long customerId; @@ -24,4 +22,82 @@ public class SaleRequest { private List items; private String notes; + + public Long getCustomerId() { + return customerId; + } + + public void setCustomerId(Long customerId) { + this.customerId = customerId; + } + + public Long getStoreId() { + return storeId; + } + + public void setStoreId(Long storeId) { + this.storeId = storeId; + } + + public String getPaymentMethod() { + return paymentMethod; + } + + public void setPaymentMethod(String paymentMethod) { + this.paymentMethod = paymentMethod; + } + + public BigDecimal getTax() { + return tax; + } + + public void setTax(BigDecimal tax) { + this.tax = tax; + } + + public List getItems() { + return items; + } + + public void setItems(List items) { + this.items = items; + } + + public String getNotes() { + return notes; + } + + public void setNotes(String notes) { + this.notes = notes; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + SaleRequest that = (SaleRequest) o; + return Objects.equals(customerId, that.customerId) && + Objects.equals(storeId, that.storeId) && + Objects.equals(paymentMethod, that.paymentMethod) && + Objects.equals(tax, that.tax) && + Objects.equals(items, that.items) && + Objects.equals(notes, that.notes); + } + + @Override + public int hashCode() { + return Objects.hash(customerId, storeId, paymentMethod, tax, items, notes); + } + + @Override + public String toString() { + return "SaleRequest{" + + "customerId=" + customerId + + ", storeId=" + storeId + + ", paymentMethod='" + paymentMethod + '\'' + + ", tax=" + tax + + ", items=" + items + + ", notes='" + notes + '\'' + + '}'; + } } diff --git a/src/main/java/com/petshop/backend/dto/sale/SaleResponse.java b/src/main/java/com/petshop/backend/dto/sale/SaleResponse.java index d1cf1c45..b8634308 100644 --- a/src/main/java/com/petshop/backend/dto/sale/SaleResponse.java +++ b/src/main/java/com/petshop/backend/dto/sale/SaleResponse.java @@ -1,12 +1,9 @@ package com.petshop.backend.dto.sale; - import java.math.BigDecimal; import java.time.LocalDateTime; import java.util.List; - - - +import java.util.Objects; public class SaleResponse { private Long id; @@ -25,9 +22,181 @@ public class SaleResponse { private List items; private LocalDateTime createdAt; - - - + public SaleResponse() { + } + + public SaleResponse(Long id, LocalDateTime saleDate, Long employeeId, String employeeName, Long customerId, String customerName, Long storeId, String storeName, BigDecimal subtotal, BigDecimal tax, BigDecimal total, String paymentMethod, String notes, List items, LocalDateTime createdAt) { + this.id = id; + this.saleDate = saleDate; + this.employeeId = employeeId; + this.employeeName = employeeName; + this.customerId = customerId; + this.customerName = customerName; + this.storeId = storeId; + this.storeName = storeName; + this.subtotal = subtotal; + this.tax = tax; + this.total = total; + this.paymentMethod = paymentMethod; + this.notes = notes; + this.items = items; + this.createdAt = createdAt; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public LocalDateTime getSaleDate() { + return saleDate; + } + + public void setSaleDate(LocalDateTime saleDate) { + this.saleDate = saleDate; + } + + public Long getEmployeeId() { + return employeeId; + } + + public void setEmployeeId(Long employeeId) { + this.employeeId = employeeId; + } + + public String getEmployeeName() { + return employeeName; + } + + public void setEmployeeName(String employeeName) { + this.employeeName = employeeName; + } + + public Long getCustomerId() { + return customerId; + } + + public void setCustomerId(Long customerId) { + this.customerId = customerId; + } + + public String getCustomerName() { + return customerName; + } + + public void setCustomerName(String customerName) { + this.customerName = customerName; + } + + public Long getStoreId() { + return storeId; + } + + public void setStoreId(Long storeId) { + this.storeId = storeId; + } + + public String getStoreName() { + return storeName; + } + + public void setStoreName(String storeName) { + this.storeName = storeName; + } + + public BigDecimal getSubtotal() { + return subtotal; + } + + public void setSubtotal(BigDecimal subtotal) { + this.subtotal = subtotal; + } + + public BigDecimal getTax() { + return tax; + } + + public void setTax(BigDecimal tax) { + this.tax = tax; + } + + public BigDecimal getTotal() { + return total; + } + + public void setTotal(BigDecimal total) { + this.total = total; + } + + public String getPaymentMethod() { + return paymentMethod; + } + + public void setPaymentMethod(String paymentMethod) { + this.paymentMethod = paymentMethod; + } + + public String getNotes() { + return notes; + } + + public void setNotes(String notes) { + this.notes = notes; + } + + public List getItems() { + return items; + } + + public void setItems(List items) { + this.items = items; + } + + 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; + SaleResponse that = (SaleResponse) o; + return Objects.equals(id, that.id) && Objects.equals(saleDate, that.saleDate) && Objects.equals(employeeId, that.employeeId) && Objects.equals(employeeName, that.employeeName) && Objects.equals(customerId, that.customerId) && Objects.equals(customerName, that.customerName) && Objects.equals(storeId, that.storeId) && Objects.equals(storeName, that.storeName) && Objects.equals(subtotal, that.subtotal) && Objects.equals(tax, that.tax) && Objects.equals(total, that.total) && Objects.equals(paymentMethod, that.paymentMethod) && Objects.equals(notes, that.notes) && Objects.equals(items, that.items) && Objects.equals(createdAt, that.createdAt); + } + + @Override + public int hashCode() { + return Objects.hash(id, saleDate, employeeId, employeeName, customerId, customerName, storeId, storeName, subtotal, tax, total, paymentMethod, notes, items, createdAt); + } + + @Override + public String toString() { + return "SaleResponse{" + + "id=" + id + + ", saleDate=" + saleDate + + ", employeeId=" + employeeId + + ", employeeName='" + employeeName + '\'' + + ", customerId=" + customerId + + ", customerName='" + customerName + '\'' + + ", storeId=" + storeId + + ", storeName='" + storeName + '\'' + + ", subtotal=" + subtotal + + ", tax=" + tax + + ", total=" + total + + ", paymentMethod='" + paymentMethod + '\'' + + ", notes='" + notes + '\'' + + ", items=" + items + + ", createdAt=" + createdAt + + '}'; + } + public static class SaleItemResponse { private Long id; private Long productId; @@ -35,5 +204,90 @@ public class SaleResponse { private Integer quantity; private BigDecimal unitPrice; private BigDecimal subtotal; + + public SaleItemResponse() { + } + + public SaleItemResponse(Long id, Long productId, String productName, Integer quantity, BigDecimal unitPrice, BigDecimal subtotal) { + this.id = id; + this.productId = productId; + this.productName = productName; + this.quantity = quantity; + this.unitPrice = unitPrice; + this.subtotal = subtotal; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getProductId() { + return productId; + } + + public void setProductId(Long productId) { + this.productId = productId; + } + + public String getProductName() { + return productName; + } + + public void setProductName(String productName) { + this.productName = productName; + } + + public Integer getQuantity() { + return quantity; + } + + public void setQuantity(Integer quantity) { + this.quantity = quantity; + } + + public BigDecimal getUnitPrice() { + return unitPrice; + } + + public void setUnitPrice(BigDecimal unitPrice) { + this.unitPrice = unitPrice; + } + + public BigDecimal getSubtotal() { + return subtotal; + } + + public void setSubtotal(BigDecimal subtotal) { + this.subtotal = subtotal; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + SaleItemResponse that = (SaleItemResponse) o; + return Objects.equals(id, that.id) && Objects.equals(productId, that.productId) && Objects.equals(productName, that.productName) && Objects.equals(quantity, that.quantity) && Objects.equals(unitPrice, that.unitPrice) && Objects.equals(subtotal, that.subtotal); + } + + @Override + public int hashCode() { + return Objects.hash(id, productId, productName, quantity, unitPrice, subtotal); + } + + @Override + public String toString() { + return "SaleItemResponse{" + + "id=" + id + + ", productId=" + productId + + ", productName='" + productName + '\'' + + ", quantity=" + quantity + + ", unitPrice=" + unitPrice + + ", subtotal=" + subtotal + + '}'; + } } } diff --git a/src/main/java/com/petshop/backend/dto/service/ServiceRequest.java b/src/main/java/com/petshop/backend/dto/service/ServiceRequest.java index b5c6bbce..123d704e 100644 --- a/src/main/java/com/petshop/backend/dto/service/ServiceRequest.java +++ b/src/main/java/com/petshop/backend/dto/service/ServiceRequest.java @@ -3,9 +3,8 @@ package com.petshop.backend.dto.service; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Positive; - import java.math.BigDecimal; - +import java.util.Objects; public class ServiceRequest { @NotBlank(message = "Service name is required") @@ -21,4 +20,72 @@ public class ServiceRequest { private Integer serviceDurationMinutes; private Boolean active = true; + + public String getServiceName() { + return serviceName; + } + + public void setServiceName(String serviceName) { + this.serviceName = serviceName; + } + + public String getServiceDescription() { + return serviceDescription; + } + + public void setServiceDescription(String serviceDescription) { + this.serviceDescription = serviceDescription; + } + + public BigDecimal getServicePrice() { + return servicePrice; + } + + public void setServicePrice(BigDecimal servicePrice) { + this.servicePrice = servicePrice; + } + + public Integer getServiceDurationMinutes() { + return serviceDurationMinutes; + } + + public void setServiceDurationMinutes(Integer serviceDurationMinutes) { + this.serviceDurationMinutes = serviceDurationMinutes; + } + + public Boolean getActive() { + return active; + } + + public void setActive(Boolean active) { + this.active = active; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ServiceRequest that = (ServiceRequest) o; + return Objects.equals(serviceName, that.serviceName) && + Objects.equals(serviceDescription, that.serviceDescription) && + Objects.equals(servicePrice, that.servicePrice) && + Objects.equals(serviceDurationMinutes, that.serviceDurationMinutes) && + Objects.equals(active, that.active); + } + + @Override + public int hashCode() { + return Objects.hash(serviceName, serviceDescription, servicePrice, serviceDurationMinutes, active); + } + + @Override + public String toString() { + return "ServiceRequest{" + + "serviceName='" + serviceName + '\'' + + ", serviceDescription='" + serviceDescription + '\'' + + ", servicePrice=" + servicePrice + + ", serviceDurationMinutes=" + serviceDurationMinutes + + ", active=" + active + + '}'; + } } diff --git a/src/main/java/com/petshop/backend/dto/service/ServiceResponse.java b/src/main/java/com/petshop/backend/dto/service/ServiceResponse.java index 992d8fdc..fd7e12ad 100644 --- a/src/main/java/com/petshop/backend/dto/service/ServiceResponse.java +++ b/src/main/java/com/petshop/backend/dto/service/ServiceResponse.java @@ -1,11 +1,8 @@ package com.petshop.backend.dto.service; - import java.math.BigDecimal; import java.time.LocalDateTime; - - - +import java.util.Objects; public class ServiceResponse { private Long id; @@ -16,4 +13,109 @@ public class ServiceResponse { private Boolean active; private LocalDateTime createdAt; private LocalDateTime updatedAt; + + public ServiceResponse() { + } + + public ServiceResponse(Long id, String serviceName, String serviceDescription, BigDecimal servicePrice, Integer serviceDurationMinutes, Boolean active, LocalDateTime createdAt, LocalDateTime updatedAt) { + this.id = id; + this.serviceName = serviceName; + this.serviceDescription = serviceDescription; + this.servicePrice = servicePrice; + this.serviceDurationMinutes = serviceDurationMinutes; + this.active = active; + this.createdAt = createdAt; + this.updatedAt = updatedAt; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getServiceName() { + return serviceName; + } + + public void setServiceName(String serviceName) { + this.serviceName = serviceName; + } + + public String getServiceDescription() { + return serviceDescription; + } + + public void setServiceDescription(String serviceDescription) { + this.serviceDescription = serviceDescription; + } + + public BigDecimal getServicePrice() { + return servicePrice; + } + + public void setServicePrice(BigDecimal servicePrice) { + this.servicePrice = servicePrice; + } + + public Integer getServiceDurationMinutes() { + return serviceDurationMinutes; + } + + public void setServiceDurationMinutes(Integer serviceDurationMinutes) { + this.serviceDurationMinutes = serviceDurationMinutes; + } + + public Boolean getActive() { + return active; + } + + public void setActive(Boolean active) { + this.active = active; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(LocalDateTime createdAt) { + this.createdAt = createdAt; + } + + public LocalDateTime getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(LocalDateTime updatedAt) { + this.updatedAt = updatedAt; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ServiceResponse that = (ServiceResponse) o; + return Objects.equals(id, that.id) && Objects.equals(serviceName, that.serviceName) && Objects.equals(serviceDescription, that.serviceDescription) && Objects.equals(servicePrice, that.servicePrice) && Objects.equals(serviceDurationMinutes, that.serviceDurationMinutes) && Objects.equals(active, that.active) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt); + } + + @Override + public int hashCode() { + return Objects.hash(id, serviceName, serviceDescription, servicePrice, serviceDurationMinutes, active, createdAt, updatedAt); + } + + @Override + public String toString() { + return "ServiceResponse{" + + "id=" + id + + ", serviceName='" + serviceName + '\'' + + ", serviceDescription='" + serviceDescription + '\'' + + ", servicePrice=" + servicePrice + + ", serviceDurationMinutes=" + serviceDurationMinutes + + ", active=" + active + + ", createdAt=" + createdAt + + ", updatedAt=" + updatedAt + + '}'; + } } diff --git a/src/main/java/com/petshop/backend/dto/supplier/SupplierRequest.java b/src/main/java/com/petshop/backend/dto/supplier/SupplierRequest.java index 36081cf2..99fbe478 100644 --- a/src/main/java/com/petshop/backend/dto/supplier/SupplierRequest.java +++ b/src/main/java/com/petshop/backend/dto/supplier/SupplierRequest.java @@ -2,7 +2,7 @@ package com.petshop.backend.dto.supplier; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; - +import java.util.Objects; public class SupplierRequest { @NotBlank(message = "Supplier name is required") @@ -16,4 +16,82 @@ public class SupplierRequest { private String supplierPhone; private String supplierAddress; private Boolean active = true; + + public String getSupplierName() { + return supplierName; + } + + public void setSupplierName(String supplierName) { + this.supplierName = supplierName; + } + + public String getSupplierContact() { + return supplierContact; + } + + public void setSupplierContact(String supplierContact) { + this.supplierContact = supplierContact; + } + + public String getSupplierEmail() { + return supplierEmail; + } + + public void setSupplierEmail(String supplierEmail) { + this.supplierEmail = supplierEmail; + } + + public String getSupplierPhone() { + return supplierPhone; + } + + public void setSupplierPhone(String supplierPhone) { + this.supplierPhone = supplierPhone; + } + + public String getSupplierAddress() { + return supplierAddress; + } + + public void setSupplierAddress(String supplierAddress) { + this.supplierAddress = supplierAddress; + } + + public Boolean getActive() { + return active; + } + + public void setActive(Boolean active) { + this.active = active; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + SupplierRequest that = (SupplierRequest) o; + return Objects.equals(supplierName, that.supplierName) && + Objects.equals(supplierContact, that.supplierContact) && + Objects.equals(supplierEmail, that.supplierEmail) && + Objects.equals(supplierPhone, that.supplierPhone) && + Objects.equals(supplierAddress, that.supplierAddress) && + Objects.equals(active, that.active); + } + + @Override + public int hashCode() { + return Objects.hash(supplierName, supplierContact, supplierEmail, supplierPhone, supplierAddress, active); + } + + @Override + public String toString() { + return "SupplierRequest{" + + "supplierName='" + supplierName + '\'' + + ", supplierContact='" + supplierContact + '\'' + + ", supplierEmail='" + supplierEmail + '\'' + + ", supplierPhone='" + supplierPhone + '\'' + + ", supplierAddress='" + supplierAddress + '\'' + + ", active=" + active + + '}'; + } } diff --git a/src/main/java/com/petshop/backend/dto/supplier/SupplierResponse.java b/src/main/java/com/petshop/backend/dto/supplier/SupplierResponse.java index 8af255d1..348b7f81 100644 --- a/src/main/java/com/petshop/backend/dto/supplier/SupplierResponse.java +++ b/src/main/java/com/petshop/backend/dto/supplier/SupplierResponse.java @@ -1,10 +1,7 @@ package com.petshop.backend.dto.supplier; - import java.time.LocalDateTime; - - - +import java.util.Objects; public class SupplierResponse { private Long id; @@ -16,4 +13,119 @@ public class SupplierResponse { private Boolean active; private LocalDateTime createdAt; private LocalDateTime updatedAt; + + public SupplierResponse() { + } + + public SupplierResponse(Long id, String supplierName, String supplierContact, String supplierEmail, String supplierPhone, String supplierAddress, Boolean active, LocalDateTime createdAt, LocalDateTime updatedAt) { + this.id = id; + this.supplierName = supplierName; + this.supplierContact = supplierContact; + this.supplierEmail = supplierEmail; + this.supplierPhone = supplierPhone; + this.supplierAddress = supplierAddress; + this.active = active; + this.createdAt = createdAt; + this.updatedAt = updatedAt; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getSupplierName() { + return supplierName; + } + + public void setSupplierName(String supplierName) { + this.supplierName = supplierName; + } + + public String getSupplierContact() { + return supplierContact; + } + + public void setSupplierContact(String supplierContact) { + this.supplierContact = supplierContact; + } + + public String getSupplierEmail() { + return supplierEmail; + } + + public void setSupplierEmail(String supplierEmail) { + this.supplierEmail = supplierEmail; + } + + public String getSupplierPhone() { + return supplierPhone; + } + + public void setSupplierPhone(String supplierPhone) { + this.supplierPhone = supplierPhone; + } + + public String getSupplierAddress() { + return supplierAddress; + } + + public void setSupplierAddress(String supplierAddress) { + this.supplierAddress = supplierAddress; + } + + public Boolean getActive() { + return active; + } + + public void setActive(Boolean active) { + this.active = active; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(LocalDateTime createdAt) { + this.createdAt = createdAt; + } + + public LocalDateTime getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(LocalDateTime updatedAt) { + this.updatedAt = updatedAt; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + SupplierResponse that = (SupplierResponse) o; + return Objects.equals(id, that.id) && Objects.equals(supplierName, that.supplierName) && Objects.equals(supplierContact, that.supplierContact) && Objects.equals(supplierEmail, that.supplierEmail) && Objects.equals(supplierPhone, that.supplierPhone) && Objects.equals(supplierAddress, that.supplierAddress) && Objects.equals(active, that.active) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt); + } + + @Override + public int hashCode() { + return Objects.hash(id, supplierName, supplierContact, supplierEmail, supplierPhone, supplierAddress, active, createdAt, updatedAt); + } + + @Override + public String toString() { + return "SupplierResponse{" + + "id=" + id + + ", supplierName='" + supplierName + '\'' + + ", supplierContact='" + supplierContact + '\'' + + ", supplierEmail='" + supplierEmail + '\'' + + ", supplierPhone='" + supplierPhone + '\'' + + ", supplierAddress='" + supplierAddress + '\'' + + ", active=" + active + + ", createdAt=" + createdAt + + ", updatedAt=" + updatedAt + + '}'; + } } diff --git a/src/main/java/com/petshop/backend/dto/user/UserRequest.java b/src/main/java/com/petshop/backend/dto/user/UserRequest.java index ca68354b..72b1dbfb 100644 --- a/src/main/java/com/petshop/backend/dto/user/UserRequest.java +++ b/src/main/java/com/petshop/backend/dto/user/UserRequest.java @@ -5,7 +5,7 @@ import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; - +import java.util.Objects; public class UserRequest { @NotBlank(message = "Username is required") @@ -25,4 +25,82 @@ public class UserRequest { private User.Role role; private Boolean active = true; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getFullName() { + return fullName; + } + + public void setFullName(String fullName) { + this.fullName = fullName; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public User.Role getRole() { + return role; + } + + public void setRole(User.Role role) { + this.role = role; + } + + public Boolean getActive() { + return active; + } + + public void setActive(Boolean active) { + this.active = active; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + UserRequest that = (UserRequest) o; + return Objects.equals(username, that.username) && + Objects.equals(password, that.password) && + Objects.equals(fullName, that.fullName) && + Objects.equals(email, that.email) && + role == that.role && + Objects.equals(active, that.active); + } + + @Override + public int hashCode() { + return Objects.hash(username, password, fullName, email, role, active); + } + + @Override + public String toString() { + return "UserRequest{" + + "username='" + username + '\'' + + ", password='" + password + '\'' + + ", fullName='" + fullName + '\'' + + ", email='" + email + '\'' + + ", role=" + role + + ", active=" + active + + '}'; + } } diff --git a/src/main/java/com/petshop/backend/dto/user/UserResponse.java b/src/main/java/com/petshop/backend/dto/user/UserResponse.java index 0fac71c9..8b383366 100644 --- a/src/main/java/com/petshop/backend/dto/user/UserResponse.java +++ b/src/main/java/com/petshop/backend/dto/user/UserResponse.java @@ -1,10 +1,7 @@ package com.petshop.backend.dto.user; - import java.time.LocalDateTime; - - - +import java.util.Objects; public class UserResponse { private Long id; @@ -15,4 +12,109 @@ public class UserResponse { private Boolean active; private LocalDateTime createdAt; private LocalDateTime updatedAt; + + public UserResponse() { + } + + public UserResponse(Long id, String username, String fullName, String email, String role, Boolean active, LocalDateTime createdAt, LocalDateTime updatedAt) { + this.id = id; + this.username = username; + this.fullName = fullName; + this.email = email; + this.role = role; + this.active = active; + this.createdAt = createdAt; + this.updatedAt = updatedAt; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getFullName() { + return fullName; + } + + public void setFullName(String fullName) { + this.fullName = fullName; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getRole() { + return role; + } + + public void setRole(String role) { + this.role = role; + } + + public Boolean getActive() { + return active; + } + + public void setActive(Boolean active) { + this.active = active; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(LocalDateTime createdAt) { + this.createdAt = createdAt; + } + + public LocalDateTime getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(LocalDateTime updatedAt) { + this.updatedAt = updatedAt; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + UserResponse that = (UserResponse) o; + return Objects.equals(id, that.id) && Objects.equals(username, that.username) && Objects.equals(fullName, that.fullName) && Objects.equals(email, that.email) && Objects.equals(role, that.role) && Objects.equals(active, that.active) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt); + } + + @Override + public int hashCode() { + return Objects.hash(id, username, fullName, email, role, active, createdAt, updatedAt); + } + + @Override + public String toString() { + return "UserResponse{" + + "id=" + id + + ", username='" + username + '\'' + + ", fullName='" + fullName + '\'' + + ", email='" + email + '\'' + + ", role='" + role + '\'' + + ", active=" + active + + ", createdAt=" + createdAt + + ", updatedAt=" + updatedAt + + '}'; + } } diff --git a/src/main/java/com/petshop/backend/entity/Adoption.java b/src/main/java/com/petshop/backend/entity/Adoption.java index 51366e27..da005d5f 100644 --- a/src/main/java/com/petshop/backend/entity/Adoption.java +++ b/src/main/java/com/petshop/backend/entity/Adoption.java @@ -7,12 +7,10 @@ import org.hibernate.annotations.UpdateTimestamp; import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; +import java.util.Objects; @Entity @Table(name = "adoptions") - - - public class Adoption { @Id @@ -43,4 +41,109 @@ public class Adoption { @UpdateTimestamp @Column(name = "updated_at") private LocalDateTime updatedAt; + + public Adoption() { + } + + public Adoption(Long id, Pet pet, Customer customer, LocalDate adoptionDate, BigDecimal adoptionFee, String notes, LocalDateTime createdAt, LocalDateTime updatedAt) { + this.id = id; + this.pet = pet; + this.customer = customer; + this.adoptionDate = adoptionDate; + this.adoptionFee = adoptionFee; + this.notes = notes; + this.createdAt = createdAt; + this.updatedAt = updatedAt; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Pet getPet() { + return pet; + } + + public void setPet(Pet pet) { + this.pet = pet; + } + + public Customer getCustomer() { + return customer; + } + + public void setCustomer(Customer customer) { + this.customer = customer; + } + + public LocalDate getAdoptionDate() { + return adoptionDate; + } + + public void setAdoptionDate(LocalDate adoptionDate) { + this.adoptionDate = adoptionDate; + } + + public BigDecimal getAdoptionFee() { + return adoptionFee; + } + + public void setAdoptionFee(BigDecimal adoptionFee) { + this.adoptionFee = adoptionFee; + } + + public String getNotes() { + return notes; + } + + public void setNotes(String notes) { + this.notes = notes; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(LocalDateTime createdAt) { + this.createdAt = createdAt; + } + + public LocalDateTime getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(LocalDateTime updatedAt) { + this.updatedAt = updatedAt; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Adoption adoption = (Adoption) o; + return Objects.equals(id, adoption.id); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } + + @Override + public String toString() { + return "Adoption{" + + "id=" + id + + ", pet=" + pet + + ", customer=" + customer + + ", adoptionDate=" + adoptionDate + + ", adoptionFee=" + adoptionFee + + ", notes='" + notes + '\'' + + ", createdAt=" + createdAt + + ", updatedAt=" + updatedAt + + '}'; + } } diff --git a/src/main/java/com/petshop/backend/entity/Appointment.java b/src/main/java/com/petshop/backend/entity/Appointment.java index 60b6d1c0..358c137b 100644 --- a/src/main/java/com/petshop/backend/entity/Appointment.java +++ b/src/main/java/com/petshop/backend/entity/Appointment.java @@ -8,13 +8,11 @@ import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.util.HashSet; +import java.util.Objects; import java.util.Set; @Entity @Table(name = "appointments") - - - public class Appointment { @Id @@ -61,4 +59,129 @@ public class Appointment { public enum AppointmentStatus { Scheduled, Completed, Cancelled } + + public Appointment() { + } + + public Appointment(Long id, Customer customer, Service service, LocalDate appointmentDate, LocalTime appointmentTime, AppointmentStatus status, String notes, Set pets, LocalDateTime createdAt, LocalDateTime updatedAt) { + this.id = id; + this.customer = customer; + this.service = service; + this.appointmentDate = appointmentDate; + this.appointmentTime = appointmentTime; + this.status = status; + this.notes = notes; + this.pets = pets; + this.createdAt = createdAt; + this.updatedAt = updatedAt; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Customer getCustomer() { + return customer; + } + + public void setCustomer(Customer customer) { + this.customer = customer; + } + + public Service getService() { + return service; + } + + public void setService(Service service) { + this.service = service; + } + + public LocalDate getAppointmentDate() { + return appointmentDate; + } + + public void setAppointmentDate(LocalDate appointmentDate) { + this.appointmentDate = appointmentDate; + } + + public LocalTime getAppointmentTime() { + return appointmentTime; + } + + public void setAppointmentTime(LocalTime appointmentTime) { + this.appointmentTime = appointmentTime; + } + + public AppointmentStatus getStatus() { + return status; + } + + public void setStatus(AppointmentStatus status) { + this.status = status; + } + + public String getNotes() { + return notes; + } + + public void setNotes(String notes) { + this.notes = notes; + } + + public Set getPets() { + return pets; + } + + public void setPets(Set pets) { + this.pets = pets; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(LocalDateTime createdAt) { + this.createdAt = createdAt; + } + + public LocalDateTime getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(LocalDateTime updatedAt) { + this.updatedAt = updatedAt; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Appointment that = (Appointment) o; + return Objects.equals(id, that.id); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } + + @Override + public String toString() { + return "Appointment{" + + "id=" + id + + ", customer=" + customer + + ", service=" + service + + ", appointmentDate=" + appointmentDate + + ", appointmentTime=" + appointmentTime + + ", status=" + status + + ", notes='" + notes + '\'' + + ", pets=" + pets + + ", createdAt=" + createdAt + + ", updatedAt=" + updatedAt + + '}'; + } } diff --git a/src/main/java/com/petshop/backend/entity/Category.java b/src/main/java/com/petshop/backend/entity/Category.java index 7380c6d3..47a9963a 100644 --- a/src/main/java/com/petshop/backend/entity/Category.java +++ b/src/main/java/com/petshop/backend/entity/Category.java @@ -5,12 +5,10 @@ import org.hibernate.annotations.CreationTimestamp; import org.hibernate.annotations.UpdateTimestamp; import java.time.LocalDateTime; +import java.util.Objects; @Entity @Table(name = "categories") - - - public class Category { @Id @@ -30,4 +28,79 @@ public class Category { @UpdateTimestamp @Column(name = "updated_at") private LocalDateTime updatedAt; + + public Category() { + } + + public Category(Long id, String categoryName, String categoryDescription, LocalDateTime createdAt, LocalDateTime updatedAt) { + this.id = id; + this.categoryName = categoryName; + this.categoryDescription = categoryDescription; + this.createdAt = createdAt; + this.updatedAt = updatedAt; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getCategoryName() { + return categoryName; + } + + public void setCategoryName(String categoryName) { + this.categoryName = categoryName; + } + + public String getCategoryDescription() { + return categoryDescription; + } + + public void setCategoryDescription(String categoryDescription) { + this.categoryDescription = categoryDescription; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(LocalDateTime createdAt) { + this.createdAt = createdAt; + } + + public LocalDateTime getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(LocalDateTime updatedAt) { + this.updatedAt = updatedAt; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Category category = (Category) o; + return Objects.equals(id, category.id); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } + + @Override + public String toString() { + return "Category{" + + "id=" + id + + ", categoryName='" + categoryName + '\'' + + ", categoryDescription='" + categoryDescription + '\'' + + ", createdAt=" + createdAt + + ", updatedAt=" + updatedAt + + '}'; + } } diff --git a/src/main/java/com/petshop/backend/entity/Customer.java b/src/main/java/com/petshop/backend/entity/Customer.java index 496cd705..b0b319a6 100644 --- a/src/main/java/com/petshop/backend/entity/Customer.java +++ b/src/main/java/com/petshop/backend/entity/Customer.java @@ -5,12 +5,10 @@ import org.hibernate.annotations.CreationTimestamp; import org.hibernate.annotations.UpdateTimestamp; import java.time.LocalDateTime; +import java.util.Objects; @Entity @Table(name = "customers") - - - public class Customer { @Id @@ -36,4 +34,99 @@ public class Customer { @UpdateTimestamp @Column(name = "updated_at") private LocalDateTime updatedAt; + + public Customer() { + } + + public Customer(Long id, String customerName, String customerEmail, String customerPhone, String customerAddress, LocalDateTime createdAt, LocalDateTime updatedAt) { + this.id = id; + this.customerName = customerName; + this.customerEmail = customerEmail; + this.customerPhone = customerPhone; + this.customerAddress = customerAddress; + this.createdAt = createdAt; + this.updatedAt = updatedAt; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getCustomerName() { + return customerName; + } + + public void setCustomerName(String customerName) { + this.customerName = customerName; + } + + public String getCustomerEmail() { + return customerEmail; + } + + public void setCustomerEmail(String customerEmail) { + this.customerEmail = customerEmail; + } + + public String getCustomerPhone() { + return customerPhone; + } + + public void setCustomerPhone(String customerPhone) { + this.customerPhone = customerPhone; + } + + public String getCustomerAddress() { + return customerAddress; + } + + public void setCustomerAddress(String customerAddress) { + this.customerAddress = customerAddress; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(LocalDateTime createdAt) { + this.createdAt = createdAt; + } + + public LocalDateTime getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(LocalDateTime updatedAt) { + this.updatedAt = updatedAt; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Customer customer = (Customer) o; + return Objects.equals(id, customer.id); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } + + @Override + public String toString() { + return "Customer{" + + "id=" + id + + ", customerName='" + customerName + '\'' + + ", customerEmail='" + customerEmail + '\'' + + ", customerPhone='" + customerPhone + '\'' + + ", customerAddress='" + customerAddress + '\'' + + ", createdAt=" + createdAt + + ", updatedAt=" + updatedAt + + '}'; + } } diff --git a/src/main/java/com/petshop/backend/entity/Inventory.java b/src/main/java/com/petshop/backend/entity/Inventory.java index bdcd42ef..4e67b106 100644 --- a/src/main/java/com/petshop/backend/entity/Inventory.java +++ b/src/main/java/com/petshop/backend/entity/Inventory.java @@ -5,14 +5,12 @@ import org.hibernate.annotations.CreationTimestamp; import org.hibernate.annotations.UpdateTimestamp; import java.time.LocalDateTime; +import java.util.Objects; @Entity @Table(name = "inventory", uniqueConstraints = { @UniqueConstraint(name = "unique_product_store", columnNames = {"product_id", "store_id"}) }) - - - public class Inventory { @Id @@ -43,4 +41,109 @@ public class Inventory { @UpdateTimestamp @Column(name = "updated_at") private LocalDateTime updatedAt; + + public Inventory() { + } + + public Inventory(Long id, Product product, Store store, Integer quantity, Integer reorderLevel, LocalDateTime lastRestocked, LocalDateTime createdAt, LocalDateTime updatedAt) { + this.id = id; + this.product = product; + this.store = store; + this.quantity = quantity; + this.reorderLevel = reorderLevel; + this.lastRestocked = lastRestocked; + this.createdAt = createdAt; + this.updatedAt = updatedAt; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Product getProduct() { + return product; + } + + public void setProduct(Product product) { + this.product = product; + } + + public Store getStore() { + return store; + } + + public void setStore(Store store) { + this.store = store; + } + + public Integer getQuantity() { + return quantity; + } + + public void setQuantity(Integer quantity) { + this.quantity = quantity; + } + + public Integer getReorderLevel() { + return reorderLevel; + } + + public void setReorderLevel(Integer reorderLevel) { + this.reorderLevel = reorderLevel; + } + + public LocalDateTime getLastRestocked() { + return lastRestocked; + } + + public void setLastRestocked(LocalDateTime lastRestocked) { + this.lastRestocked = lastRestocked; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(LocalDateTime createdAt) { + this.createdAt = createdAt; + } + + public LocalDateTime getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(LocalDateTime updatedAt) { + this.updatedAt = updatedAt; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Inventory inventory = (Inventory) o; + return Objects.equals(id, inventory.id); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } + + @Override + public String toString() { + return "Inventory{" + + "id=" + id + + ", product=" + product + + ", store=" + store + + ", quantity=" + quantity + + ", reorderLevel=" + reorderLevel + + ", lastRestocked=" + lastRestocked + + ", createdAt=" + createdAt + + ", updatedAt=" + updatedAt + + '}'; + } } diff --git a/src/main/java/com/petshop/backend/entity/Pet.java b/src/main/java/com/petshop/backend/entity/Pet.java index 8e0587ba..0cd1c707 100644 --- a/src/main/java/com/petshop/backend/entity/Pet.java +++ b/src/main/java/com/petshop/backend/entity/Pet.java @@ -6,12 +6,10 @@ import org.hibernate.annotations.UpdateTimestamp; import java.math.BigDecimal; import java.time.LocalDateTime; +import java.util.Objects; @Entity @Table(name = "pets") - - - public class Pet { @Id @@ -48,4 +46,119 @@ public class Pet { public enum PetStatus { Available, Adopted, Under_Care } + + public Pet() { + } + + public Pet(Long id, String petName, String petSpecies, String petBreed, Integer petAge, PetStatus petStatus, BigDecimal petPrice, LocalDateTime createdAt, LocalDateTime updatedAt) { + this.id = id; + this.petName = petName; + this.petSpecies = petSpecies; + this.petBreed = petBreed; + this.petAge = petAge; + this.petStatus = petStatus; + this.petPrice = petPrice; + this.createdAt = createdAt; + this.updatedAt = updatedAt; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getPetName() { + return petName; + } + + public void setPetName(String petName) { + this.petName = petName; + } + + public String getPetSpecies() { + return petSpecies; + } + + public void setPetSpecies(String petSpecies) { + this.petSpecies = petSpecies; + } + + public String getPetBreed() { + return petBreed; + } + + public void setPetBreed(String petBreed) { + this.petBreed = petBreed; + } + + public Integer getPetAge() { + return petAge; + } + + public void setPetAge(Integer petAge) { + this.petAge = petAge; + } + + public PetStatus getPetStatus() { + return petStatus; + } + + public void setPetStatus(PetStatus petStatus) { + this.petStatus = petStatus; + } + + public BigDecimal getPetPrice() { + return petPrice; + } + + public void setPetPrice(BigDecimal petPrice) { + this.petPrice = petPrice; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(LocalDateTime createdAt) { + this.createdAt = createdAt; + } + + public LocalDateTime getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(LocalDateTime updatedAt) { + this.updatedAt = updatedAt; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Pet pet = (Pet) o; + return Objects.equals(id, pet.id); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } + + @Override + public String toString() { + return "Pet{" + + "id=" + id + + ", petName='" + petName + '\'' + + ", petSpecies='" + petSpecies + '\'' + + ", petBreed='" + petBreed + '\'' + + ", petAge=" + petAge + + ", petStatus=" + petStatus + + ", petPrice=" + petPrice + + ", createdAt=" + createdAt + + ", updatedAt=" + updatedAt + + '}'; + } } diff --git a/src/main/java/com/petshop/backend/entity/Product.java b/src/main/java/com/petshop/backend/entity/Product.java index 49ebf645..2a3e0dd9 100644 --- a/src/main/java/com/petshop/backend/entity/Product.java +++ b/src/main/java/com/petshop/backend/entity/Product.java @@ -6,12 +6,10 @@ import org.hibernate.annotations.UpdateTimestamp; import java.math.BigDecimal; import java.time.LocalDateTime; +import java.util.Objects; @Entity @Table(name = "products") - - - public class Product { @Id @@ -41,4 +39,109 @@ public class Product { @UpdateTimestamp @Column(name = "updated_at") private LocalDateTime updatedAt; + + public Product() { + } + + public Product(Long id, String productName, Category category, String productDescription, BigDecimal productPrice, Boolean active, LocalDateTime createdAt, LocalDateTime updatedAt) { + this.id = id; + this.productName = productName; + this.category = category; + this.productDescription = productDescription; + this.productPrice = productPrice; + this.active = active; + this.createdAt = createdAt; + this.updatedAt = updatedAt; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getProductName() { + return productName; + } + + public void setProductName(String productName) { + this.productName = productName; + } + + public Category getCategory() { + return category; + } + + public void setCategory(Category category) { + this.category = category; + } + + public String getProductDescription() { + return productDescription; + } + + public void setProductDescription(String productDescription) { + this.productDescription = productDescription; + } + + public BigDecimal getProductPrice() { + return productPrice; + } + + public void setProductPrice(BigDecimal productPrice) { + this.productPrice = productPrice; + } + + public Boolean getActive() { + return active; + } + + public void setActive(Boolean active) { + this.active = active; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(LocalDateTime createdAt) { + this.createdAt = createdAt; + } + + public LocalDateTime getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(LocalDateTime updatedAt) { + this.updatedAt = updatedAt; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Product product = (Product) o; + return Objects.equals(id, product.id); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } + + @Override + public String toString() { + return "Product{" + + "id=" + id + + ", productName='" + productName + '\'' + + ", category=" + category + + ", productDescription='" + productDescription + '\'' + + ", productPrice=" + productPrice + + ", active=" + active + + ", createdAt=" + createdAt + + ", updatedAt=" + updatedAt + + '}'; + } } diff --git a/src/main/java/com/petshop/backend/entity/ProductSupplier.java b/src/main/java/com/petshop/backend/entity/ProductSupplier.java index bb157386..b9758404 100644 --- a/src/main/java/com/petshop/backend/entity/ProductSupplier.java +++ b/src/main/java/com/petshop/backend/entity/ProductSupplier.java @@ -7,12 +7,10 @@ import org.hibernate.annotations.UpdateTimestamp; import java.io.Serializable; import java.math.BigDecimal; import java.time.LocalDateTime; +import java.util.Objects; @Entity @Table(name = "product_suppliers") - - - @IdClass(ProductSupplier.ProductSupplierId.class) public class ProductSupplier { @@ -43,11 +41,148 @@ public class ProductSupplier { @Column(name = "updated_at") private LocalDateTime updatedAt; - - - + public ProductSupplier() { + } + + public ProductSupplier(Product product, Supplier supplier, BigDecimal costPrice, Integer leadTimeDays, Boolean isPreferred, LocalDateTime createdAt, LocalDateTime updatedAt) { + this.product = product; + this.supplier = supplier; + this.costPrice = costPrice; + this.leadTimeDays = leadTimeDays; + this.isPreferred = isPreferred; + this.createdAt = createdAt; + this.updatedAt = updatedAt; + } + + public Product getProduct() { + return product; + } + + public void setProduct(Product product) { + this.product = product; + } + + public Supplier getSupplier() { + return supplier; + } + + public void setSupplier(Supplier supplier) { + this.supplier = supplier; + } + + public BigDecimal getCostPrice() { + return costPrice; + } + + public void setCostPrice(BigDecimal costPrice) { + this.costPrice = costPrice; + } + + public Integer getLeadTimeDays() { + return leadTimeDays; + } + + public void setLeadTimeDays(Integer leadTimeDays) { + this.leadTimeDays = leadTimeDays; + } + + public Boolean getIsPreferred() { + return isPreferred; + } + + public void setIsPreferred(Boolean isPreferred) { + this.isPreferred = isPreferred; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(LocalDateTime createdAt) { + this.createdAt = createdAt; + } + + public LocalDateTime getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(LocalDateTime updatedAt) { + this.updatedAt = updatedAt; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ProductSupplier that = (ProductSupplier) o; + return Objects.equals(product, that.product) && Objects.equals(supplier, that.supplier); + } + + @Override + public int hashCode() { + return Objects.hash(product, supplier); + } + + @Override + public String toString() { + return "ProductSupplier{" + + "product=" + product + + ", supplier=" + supplier + + ", costPrice=" + costPrice + + ", leadTimeDays=" + leadTimeDays + + ", isPreferred=" + isPreferred + + ", createdAt=" + createdAt + + ", updatedAt=" + updatedAt + + '}'; + } + public static class ProductSupplierId implements Serializable { private Long product; private Long supplier; + + public ProductSupplierId() { + } + + public ProductSupplierId(Long product, Long supplier) { + this.product = product; + this.supplier = supplier; + } + + public Long getProduct() { + return product; + } + + public void setProduct(Long product) { + this.product = product; + } + + public Long getSupplier() { + return supplier; + } + + public void setSupplier(Long supplier) { + this.supplier = supplier; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ProductSupplierId that = (ProductSupplierId) o; + return Objects.equals(product, that.product) && Objects.equals(supplier, that.supplier); + } + + @Override + public int hashCode() { + return Objects.hash(product, supplier); + } + + @Override + public String toString() { + return "ProductSupplierId{" + + "product=" + product + + ", supplier=" + supplier + + '}'; + } } } diff --git a/src/main/java/com/petshop/backend/entity/PurchaseOrder.java b/src/main/java/com/petshop/backend/entity/PurchaseOrder.java index 3112a1aa..3a63683f 100644 --- a/src/main/java/com/petshop/backend/entity/PurchaseOrder.java +++ b/src/main/java/com/petshop/backend/entity/PurchaseOrder.java @@ -9,12 +9,10 @@ import java.time.LocalDate; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; +import java.util.Objects; @Entity @Table(name = "purchase_orders") - - - public class PurchaseOrder { @Id @@ -55,4 +53,129 @@ public class PurchaseOrder { public enum OrderStatus { Pending, Delivered, Cancelled } + + public PurchaseOrder() { + } + + public PurchaseOrder(Long id, Supplier supplier, LocalDate orderDate, LocalDate expectedDelivery, OrderStatus status, BigDecimal totalAmount, String notes, List items, LocalDateTime createdAt, LocalDateTime updatedAt) { + this.id = id; + this.supplier = supplier; + this.orderDate = orderDate; + this.expectedDelivery = expectedDelivery; + this.status = status; + this.totalAmount = totalAmount; + this.notes = notes; + this.items = items; + this.createdAt = createdAt; + this.updatedAt = updatedAt; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Supplier getSupplier() { + return supplier; + } + + public void setSupplier(Supplier supplier) { + this.supplier = supplier; + } + + public LocalDate getOrderDate() { + return orderDate; + } + + public void setOrderDate(LocalDate orderDate) { + this.orderDate = orderDate; + } + + public LocalDate getExpectedDelivery() { + return expectedDelivery; + } + + public void setExpectedDelivery(LocalDate expectedDelivery) { + this.expectedDelivery = expectedDelivery; + } + + public OrderStatus getStatus() { + return status; + } + + public void setStatus(OrderStatus status) { + this.status = status; + } + + public BigDecimal getTotalAmount() { + return totalAmount; + } + + public void setTotalAmount(BigDecimal totalAmount) { + this.totalAmount = totalAmount; + } + + public String getNotes() { + return notes; + } + + public void setNotes(String notes) { + this.notes = notes; + } + + public List getItems() { + return items; + } + + public void setItems(List items) { + this.items = items; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(LocalDateTime createdAt) { + this.createdAt = createdAt; + } + + public LocalDateTime getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(LocalDateTime updatedAt) { + this.updatedAt = updatedAt; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + PurchaseOrder that = (PurchaseOrder) o; + return Objects.equals(id, that.id); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } + + @Override + public String toString() { + return "PurchaseOrder{" + + "id=" + id + + ", supplier=" + supplier + + ", orderDate=" + orderDate + + ", expectedDelivery=" + expectedDelivery + + ", status=" + status + + ", totalAmount=" + totalAmount + + ", notes='" + notes + '\'' + + ", items=" + items + + ", createdAt=" + createdAt + + ", updatedAt=" + updatedAt + + '}'; + } } diff --git a/src/main/java/com/petshop/backend/entity/PurchaseOrderItem.java b/src/main/java/com/petshop/backend/entity/PurchaseOrderItem.java index f6485616..6278d3f9 100644 --- a/src/main/java/com/petshop/backend/entity/PurchaseOrderItem.java +++ b/src/main/java/com/petshop/backend/entity/PurchaseOrderItem.java @@ -3,12 +3,10 @@ package com.petshop.backend.entity; import jakarta.persistence.*; import java.math.BigDecimal; +import java.util.Objects; @Entity @Table(name = "purchase_order_items") - - - public class PurchaseOrderItem { @Id @@ -31,4 +29,89 @@ public class PurchaseOrderItem { @Column(nullable = false, precision = 10, scale = 2) private BigDecimal subtotal; + + public PurchaseOrderItem() { + } + + public PurchaseOrderItem(Long id, PurchaseOrder purchaseOrder, Product product, Integer quantity, BigDecimal unitCost, BigDecimal subtotal) { + this.id = id; + this.purchaseOrder = purchaseOrder; + this.product = product; + this.quantity = quantity; + this.unitCost = unitCost; + this.subtotal = subtotal; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public PurchaseOrder getPurchaseOrder() { + return purchaseOrder; + } + + public void setPurchaseOrder(PurchaseOrder purchaseOrder) { + this.purchaseOrder = purchaseOrder; + } + + public Product getProduct() { + return product; + } + + public void setProduct(Product product) { + this.product = product; + } + + public Integer getQuantity() { + return quantity; + } + + public void setQuantity(Integer quantity) { + this.quantity = quantity; + } + + public BigDecimal getUnitCost() { + return unitCost; + } + + public void setUnitCost(BigDecimal unitCost) { + this.unitCost = unitCost; + } + + public BigDecimal getSubtotal() { + return subtotal; + } + + public void setSubtotal(BigDecimal subtotal) { + this.subtotal = subtotal; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + PurchaseOrderItem that = (PurchaseOrderItem) o; + return Objects.equals(id, that.id); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } + + @Override + public String toString() { + return "PurchaseOrderItem{" + + "id=" + id + + ", purchaseOrder=" + purchaseOrder + + ", product=" + product + + ", quantity=" + quantity + + ", unitCost=" + unitCost + + ", subtotal=" + subtotal + + '}'; + } } diff --git a/src/main/java/com/petshop/backend/entity/Refund.java b/src/main/java/com/petshop/backend/entity/Refund.java index 48297ec1..8fe89c9a 100644 --- a/src/main/java/com/petshop/backend/entity/Refund.java +++ b/src/main/java/com/petshop/backend/entity/Refund.java @@ -7,12 +7,10 @@ import java.math.BigDecimal; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; +import java.util.Objects; @Entity @Table(name = "refunds") - - - public class Refund { @Id @@ -42,4 +40,109 @@ public class Refund { @CreationTimestamp @Column(name = "created_at", updatable = false) private LocalDateTime createdAt; + + public Refund() { + } + + public Refund(Long id, Sale sale, LocalDateTime refundDate, BigDecimal refundAmount, String refundReason, User processedBy, List items, LocalDateTime createdAt) { + this.id = id; + this.sale = sale; + this.refundDate = refundDate; + this.refundAmount = refundAmount; + this.refundReason = refundReason; + this.processedBy = processedBy; + this.items = items; + this.createdAt = createdAt; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Sale getSale() { + return sale; + } + + public void setSale(Sale sale) { + this.sale = sale; + } + + public LocalDateTime getRefundDate() { + return refundDate; + } + + public void setRefundDate(LocalDateTime refundDate) { + this.refundDate = refundDate; + } + + public BigDecimal getRefundAmount() { + return refundAmount; + } + + public void setRefundAmount(BigDecimal refundAmount) { + this.refundAmount = refundAmount; + } + + public String getRefundReason() { + return refundReason; + } + + public void setRefundReason(String refundReason) { + this.refundReason = refundReason; + } + + public User getProcessedBy() { + return processedBy; + } + + public void setProcessedBy(User processedBy) { + this.processedBy = processedBy; + } + + public List getItems() { + return items; + } + + public void setItems(List items) { + this.items = items; + } + + 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; + Refund refund = (Refund) o; + return Objects.equals(id, refund.id); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } + + @Override + public String toString() { + return "Refund{" + + "id=" + id + + ", sale=" + sale + + ", refundDate=" + refundDate + + ", refundAmount=" + refundAmount + + ", refundReason='" + refundReason + '\'' + + ", processedBy=" + processedBy + + ", items=" + items + + ", createdAt=" + createdAt + + '}'; + } } diff --git a/src/main/java/com/petshop/backend/entity/RefundItem.java b/src/main/java/com/petshop/backend/entity/RefundItem.java index 59826733..1724679a 100644 --- a/src/main/java/com/petshop/backend/entity/RefundItem.java +++ b/src/main/java/com/petshop/backend/entity/RefundItem.java @@ -3,12 +3,10 @@ package com.petshop.backend.entity; import jakarta.persistence.*; import java.math.BigDecimal; +import java.util.Objects; @Entity @Table(name = "refund_items") - - - public class RefundItem { @Id @@ -28,4 +26,79 @@ public class RefundItem { @Column(name = "refund_amount", nullable = false, precision = 10, scale = 2) private BigDecimal refundAmount; + + public RefundItem() { + } + + public RefundItem(Long id, Refund refund, SaleItem saleItem, Integer quantity, BigDecimal refundAmount) { + this.id = id; + this.refund = refund; + this.saleItem = saleItem; + this.quantity = quantity; + this.refundAmount = refundAmount; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Refund getRefund() { + return refund; + } + + public void setRefund(Refund refund) { + this.refund = refund; + } + + public SaleItem getSaleItem() { + return saleItem; + } + + public void setSaleItem(SaleItem saleItem) { + this.saleItem = saleItem; + } + + public Integer getQuantity() { + return quantity; + } + + public void setQuantity(Integer quantity) { + this.quantity = quantity; + } + + public BigDecimal getRefundAmount() { + return refundAmount; + } + + public void setRefundAmount(BigDecimal refundAmount) { + this.refundAmount = refundAmount; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + RefundItem that = (RefundItem) o; + return Objects.equals(id, that.id); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } + + @Override + public String toString() { + return "RefundItem{" + + "id=" + id + + ", refund=" + refund + + ", saleItem=" + saleItem + + ", quantity=" + quantity + + ", refundAmount=" + refundAmount + + '}'; + } } diff --git a/src/main/java/com/petshop/backend/entity/Sale.java b/src/main/java/com/petshop/backend/entity/Sale.java index 29ccff01..fc1e8765 100644 --- a/src/main/java/com/petshop/backend/entity/Sale.java +++ b/src/main/java/com/petshop/backend/entity/Sale.java @@ -7,12 +7,10 @@ import java.math.BigDecimal; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; +import java.util.Objects; @Entity @Table(name = "sales") - - - public class Sale { @Id @@ -55,4 +53,149 @@ public class Sale { @CreationTimestamp @Column(name = "created_at", updatable = false) private LocalDateTime createdAt; + + public Sale() { + } + + public Sale(Long id, LocalDateTime saleDate, User employee, Customer customer, Store store, BigDecimal subtotal, BigDecimal tax, BigDecimal total, String paymentMethod, String notes, List items, LocalDateTime createdAt) { + this.id = id; + this.saleDate = saleDate; + this.employee = employee; + this.customer = customer; + this.store = store; + this.subtotal = subtotal; + this.tax = tax; + this.total = total; + this.paymentMethod = paymentMethod; + this.notes = notes; + this.items = items; + this.createdAt = createdAt; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public LocalDateTime getSaleDate() { + return saleDate; + } + + public void setSaleDate(LocalDateTime saleDate) { + this.saleDate = saleDate; + } + + public User getEmployee() { + return employee; + } + + public void setEmployee(User employee) { + this.employee = employee; + } + + public Customer getCustomer() { + return customer; + } + + public void setCustomer(Customer customer) { + this.customer = customer; + } + + public Store getStore() { + return store; + } + + public void setStore(Store store) { + this.store = store; + } + + public BigDecimal getSubtotal() { + return subtotal; + } + + public void setSubtotal(BigDecimal subtotal) { + this.subtotal = subtotal; + } + + public BigDecimal getTax() { + return tax; + } + + public void setTax(BigDecimal tax) { + this.tax = tax; + } + + public BigDecimal getTotal() { + return total; + } + + public void setTotal(BigDecimal total) { + this.total = total; + } + + public String getPaymentMethod() { + return paymentMethod; + } + + public void setPaymentMethod(String paymentMethod) { + this.paymentMethod = paymentMethod; + } + + public String getNotes() { + return notes; + } + + public void setNotes(String notes) { + this.notes = notes; + } + + public List getItems() { + return items; + } + + public void setItems(List items) { + this.items = items; + } + + 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; + Sale sale = (Sale) o; + return Objects.equals(id, sale.id); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } + + @Override + public String toString() { + return "Sale{" + + "id=" + id + + ", saleDate=" + saleDate + + ", employee=" + employee + + ", customer=" + customer + + ", store=" + store + + ", subtotal=" + subtotal + + ", tax=" + tax + + ", total=" + total + + ", paymentMethod='" + paymentMethod + '\'' + + ", notes='" + notes + '\'' + + ", items=" + items + + ", createdAt=" + createdAt + + '}'; + } } diff --git a/src/main/java/com/petshop/backend/entity/SaleItem.java b/src/main/java/com/petshop/backend/entity/SaleItem.java index c5f183d9..5b883f4c 100644 --- a/src/main/java/com/petshop/backend/entity/SaleItem.java +++ b/src/main/java/com/petshop/backend/entity/SaleItem.java @@ -3,12 +3,10 @@ package com.petshop.backend.entity; import jakarta.persistence.*; import java.math.BigDecimal; +import java.util.Objects; @Entity @Table(name = "sale_items") - - - public class SaleItem { @Id @@ -31,4 +29,89 @@ public class SaleItem { @Column(nullable = false, precision = 10, scale = 2) private BigDecimal subtotal; + + public SaleItem() { + } + + public SaleItem(Long id, Sale sale, Product product, Integer quantity, BigDecimal unitPrice, BigDecimal subtotal) { + this.id = id; + this.sale = sale; + this.product = product; + this.quantity = quantity; + this.unitPrice = unitPrice; + this.subtotal = subtotal; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Sale getSale() { + return sale; + } + + public void setSale(Sale sale) { + this.sale = sale; + } + + public Product getProduct() { + return product; + } + + public void setProduct(Product product) { + this.product = product; + } + + public Integer getQuantity() { + return quantity; + } + + public void setQuantity(Integer quantity) { + this.quantity = quantity; + } + + public BigDecimal getUnitPrice() { + return unitPrice; + } + + public void setUnitPrice(BigDecimal unitPrice) { + this.unitPrice = unitPrice; + } + + public BigDecimal getSubtotal() { + return subtotal; + } + + public void setSubtotal(BigDecimal subtotal) { + this.subtotal = subtotal; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + SaleItem saleItem = (SaleItem) o; + return Objects.equals(id, saleItem.id); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } + + @Override + public String toString() { + return "SaleItem{" + + "id=" + id + + ", sale=" + sale + + ", product=" + product + + ", quantity=" + quantity + + ", unitPrice=" + unitPrice + + ", subtotal=" + subtotal + + '}'; + } } diff --git a/src/main/java/com/petshop/backend/entity/Service.java b/src/main/java/com/petshop/backend/entity/Service.java index 7c293e78..e3148cd9 100644 --- a/src/main/java/com/petshop/backend/entity/Service.java +++ b/src/main/java/com/petshop/backend/entity/Service.java @@ -6,12 +6,10 @@ import org.hibernate.annotations.UpdateTimestamp; import java.math.BigDecimal; import java.time.LocalDateTime; +import java.util.Objects; @Entity @Table(name = "services") - - - public class Service { @Id @@ -40,4 +38,109 @@ public class Service { @UpdateTimestamp @Column(name = "updated_at") private LocalDateTime updatedAt; + + public Service() { + } + + public Service(Long id, String serviceName, String serviceDescription, BigDecimal servicePrice, Integer serviceDurationMinutes, Boolean active, LocalDateTime createdAt, LocalDateTime updatedAt) { + this.id = id; + this.serviceName = serviceName; + this.serviceDescription = serviceDescription; + this.servicePrice = servicePrice; + this.serviceDurationMinutes = serviceDurationMinutes; + this.active = active; + this.createdAt = createdAt; + this.updatedAt = updatedAt; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getServiceName() { + return serviceName; + } + + public void setServiceName(String serviceName) { + this.serviceName = serviceName; + } + + public String getServiceDescription() { + return serviceDescription; + } + + public void setServiceDescription(String serviceDescription) { + this.serviceDescription = serviceDescription; + } + + public BigDecimal getServicePrice() { + return servicePrice; + } + + public void setServicePrice(BigDecimal servicePrice) { + this.servicePrice = servicePrice; + } + + public Integer getServiceDurationMinutes() { + return serviceDurationMinutes; + } + + public void setServiceDurationMinutes(Integer serviceDurationMinutes) { + this.serviceDurationMinutes = serviceDurationMinutes; + } + + public Boolean getActive() { + return active; + } + + public void setActive(Boolean active) { + this.active = active; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(LocalDateTime createdAt) { + this.createdAt = createdAt; + } + + public LocalDateTime getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(LocalDateTime updatedAt) { + this.updatedAt = updatedAt; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Service service = (Service) o; + return Objects.equals(id, service.id); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } + + @Override + public String toString() { + return "Service{" + + "id=" + id + + ", serviceName='" + serviceName + '\'' + + ", serviceDescription='" + serviceDescription + '\'' + + ", servicePrice=" + servicePrice + + ", serviceDurationMinutes=" + serviceDurationMinutes + + ", active=" + active + + ", createdAt=" + createdAt + + ", updatedAt=" + updatedAt + + '}'; + } } diff --git a/src/main/java/com/petshop/backend/entity/Store.java b/src/main/java/com/petshop/backend/entity/Store.java index 8f9befe2..35861c87 100644 --- a/src/main/java/com/petshop/backend/entity/Store.java +++ b/src/main/java/com/petshop/backend/entity/Store.java @@ -4,12 +4,10 @@ import jakarta.persistence.*; import org.hibernate.annotations.CreationTimestamp; import java.time.LocalDateTime; +import java.util.Objects; @Entity @Table(name = "stores") - - - public class Store { @Id @@ -25,4 +23,69 @@ public class Store { @CreationTimestamp @Column(name = "created_at", updatable = false) private LocalDateTime createdAt; + + public Store() { + } + + public Store(Long id, String storeName, String storeLocation, LocalDateTime createdAt) { + this.id = id; + this.storeName = storeName; + this.storeLocation = storeLocation; + this.createdAt = createdAt; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getStoreName() { + return storeName; + } + + public void setStoreName(String storeName) { + this.storeName = storeName; + } + + public String getStoreLocation() { + return storeLocation; + } + + public void setStoreLocation(String storeLocation) { + this.storeLocation = storeLocation; + } + + 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; + Store store = (Store) o; + return Objects.equals(id, store.id); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } + + @Override + public String toString() { + return "Store{" + + "id=" + id + + ", storeName='" + storeName + '\'' + + ", storeLocation='" + storeLocation + '\'' + + ", createdAt=" + createdAt + + '}'; + } } diff --git a/src/main/java/com/petshop/backend/entity/Supplier.java b/src/main/java/com/petshop/backend/entity/Supplier.java index 3aa5f66d..c0279738 100644 --- a/src/main/java/com/petshop/backend/entity/Supplier.java +++ b/src/main/java/com/petshop/backend/entity/Supplier.java @@ -5,12 +5,10 @@ import org.hibernate.annotations.CreationTimestamp; import org.hibernate.annotations.UpdateTimestamp; import java.time.LocalDateTime; +import java.util.Objects; @Entity @Table(name = "suppliers") - - - public class Supplier { @Id @@ -42,4 +40,119 @@ public class Supplier { @UpdateTimestamp @Column(name = "updated_at") private LocalDateTime updatedAt; + + public Supplier() { + } + + public Supplier(Long id, String supplierName, String supplierContact, String supplierEmail, String supplierPhone, String supplierAddress, Boolean active, LocalDateTime createdAt, LocalDateTime updatedAt) { + this.id = id; + this.supplierName = supplierName; + this.supplierContact = supplierContact; + this.supplierEmail = supplierEmail; + this.supplierPhone = supplierPhone; + this.supplierAddress = supplierAddress; + this.active = active; + this.createdAt = createdAt; + this.updatedAt = updatedAt; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getSupplierName() { + return supplierName; + } + + public void setSupplierName(String supplierName) { + this.supplierName = supplierName; + } + + public String getSupplierContact() { + return supplierContact; + } + + public void setSupplierContact(String supplierContact) { + this.supplierContact = supplierContact; + } + + public String getSupplierEmail() { + return supplierEmail; + } + + public void setSupplierEmail(String supplierEmail) { + this.supplierEmail = supplierEmail; + } + + public String getSupplierPhone() { + return supplierPhone; + } + + public void setSupplierPhone(String supplierPhone) { + this.supplierPhone = supplierPhone; + } + + public String getSupplierAddress() { + return supplierAddress; + } + + public void setSupplierAddress(String supplierAddress) { + this.supplierAddress = supplierAddress; + } + + public Boolean getActive() { + return active; + } + + public void setActive(Boolean active) { + this.active = active; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(LocalDateTime createdAt) { + this.createdAt = createdAt; + } + + public LocalDateTime getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(LocalDateTime updatedAt) { + this.updatedAt = updatedAt; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Supplier supplier = (Supplier) o; + return Objects.equals(id, supplier.id); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } + + @Override + public String toString() { + return "Supplier{" + + "id=" + id + + ", supplierName='" + supplierName + '\'' + + ", supplierContact='" + supplierContact + '\'' + + ", supplierEmail='" + supplierEmail + '\'' + + ", supplierPhone='" + supplierPhone + '\'' + + ", supplierAddress='" + supplierAddress + '\'' + + ", active=" + active + + ", createdAt=" + createdAt + + ", updatedAt=" + updatedAt + + '}'; + } } diff --git a/src/main/java/com/petshop/backend/entity/User.java b/src/main/java/com/petshop/backend/entity/User.java index 22674b64..7d6901a8 100644 --- a/src/main/java/com/petshop/backend/entity/User.java +++ b/src/main/java/com/petshop/backend/entity/User.java @@ -5,12 +5,10 @@ import org.hibernate.annotations.CreationTimestamp; import org.hibernate.annotations.UpdateTimestamp; import java.time.LocalDateTime; +import java.util.Objects; @Entity @Table(name = "users") - - - public class User { @Id @@ -47,4 +45,119 @@ public class User { public enum Role { STAFF, ADMIN } + + public User() { + } + + public User(Long id, String username, String password, String fullName, String email, Role role, Boolean active, LocalDateTime createdAt, LocalDateTime updatedAt) { + this.id = id; + this.username = username; + this.password = password; + this.fullName = fullName; + this.email = email; + this.role = role; + this.active = active; + this.createdAt = createdAt; + this.updatedAt = updatedAt; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getFullName() { + return fullName; + } + + public void setFullName(String fullName) { + this.fullName = fullName; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public Role getRole() { + return role; + } + + public void setRole(Role role) { + this.role = role; + } + + public Boolean getActive() { + return active; + } + + public void setActive(Boolean active) { + this.active = active; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(LocalDateTime createdAt) { + this.createdAt = createdAt; + } + + public LocalDateTime getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(LocalDateTime updatedAt) { + this.updatedAt = updatedAt; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + User user = (User) o; + return Objects.equals(id, user.id); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } + + @Override + public String toString() { + return "User{" + + "id=" + id + + ", username='" + username + '\'' + + ", password='" + password + '\'' + + ", fullName='" + fullName + '\'' + + ", email='" + email + '\'' + + ", role=" + role + + ", active=" + active + + ", createdAt=" + createdAt + + ", updatedAt=" + updatedAt + + '}'; + } } diff --git a/src/main/java/com/petshop/backend/security/JwtAuthenticationFilter.java b/src/main/java/com/petshop/backend/security/JwtAuthenticationFilter.java index 9ef6610b..02e967aa 100644 --- a/src/main/java/com/petshop/backend/security/JwtAuthenticationFilter.java +++ b/src/main/java/com/petshop/backend/security/JwtAuthenticationFilter.java @@ -16,12 +16,16 @@ import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; @Component - public class JwtAuthenticationFilter extends OncePerRequestFilter { private final JwtUtil jwtUtil; private final UserDetailsService userDetailsService; + public JwtAuthenticationFilter(JwtUtil jwtUtil, UserDetailsService userDetailsService) { + this.jwtUtil = jwtUtil; + this.userDetailsService = userDetailsService; + } + @Override protected void doFilterInternal( @NonNull HttpServletRequest request, diff --git a/src/main/java/com/petshop/backend/security/SecurityConfig.java b/src/main/java/com/petshop/backend/security/SecurityConfig.java index 573281ec..dadf9d93 100644 --- a/src/main/java/com/petshop/backend/security/SecurityConfig.java +++ b/src/main/java/com/petshop/backend/security/SecurityConfig.java @@ -21,12 +21,16 @@ import org.springframework.security.web.authentication.UsernamePasswordAuthentic @Configuration @EnableWebSecurity @EnableMethodSecurity - public class SecurityConfig { private final JwtAuthenticationFilter jwtAuthFilter; private final UserDetailsService userDetailsService; + public SecurityConfig(JwtAuthenticationFilter jwtAuthFilter, UserDetailsService userDetailsService) { + this.jwtAuthFilter = jwtAuthFilter; + this.userDetailsService = userDetailsService; + } + @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http diff --git a/src/main/java/com/petshop/backend/security/UserDetailsServiceImpl.java b/src/main/java/com/petshop/backend/security/UserDetailsServiceImpl.java index 09dd2d5b..d2bdcfff 100644 --- a/src/main/java/com/petshop/backend/security/UserDetailsServiceImpl.java +++ b/src/main/java/com/petshop/backend/security/UserDetailsServiceImpl.java @@ -11,11 +11,14 @@ import org.springframework.stereotype.Service; import java.util.Collections; @Service - public class UserDetailsServiceImpl implements UserDetailsService { private final UserRepository userRepository; + public UserDetailsServiceImpl(UserRepository userRepository) { + this.userRepository = userRepository; + } + @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userRepository.findByUsername(username) diff --git a/src/main/java/com/petshop/backend/service/AdoptionService.java b/src/main/java/com/petshop/backend/service/AdoptionService.java index 089bc164..021e058a 100644 --- a/src/main/java/com/petshop/backend/service/AdoptionService.java +++ b/src/main/java/com/petshop/backend/service/AdoptionService.java @@ -16,13 +16,18 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service - public class AdoptionService { private final AdoptionRepository adoptionRepository; private final PetRepository petRepository; private final CustomerRepository customerRepository; + public AdoptionService(AdoptionRepository adoptionRepository, PetRepository petRepository, CustomerRepository customerRepository) { + this.adoptionRepository = adoptionRepository; + this.petRepository = petRepository; + this.customerRepository = customerRepository; + } + public Page getAllAdoptions(Pageable pageable) { return adoptionRepository.findAll(pageable).map(this::mapToResponse); } diff --git a/src/main/java/com/petshop/backend/service/AppointmentService.java b/src/main/java/com/petshop/backend/service/AppointmentService.java index fcd7eaed..5244c5f1 100644 --- a/src/main/java/com/petshop/backend/service/AppointmentService.java +++ b/src/main/java/com/petshop/backend/service/AppointmentService.java @@ -22,7 +22,6 @@ import java.util.Set; import java.util.stream.Collectors; @Service - public class AppointmentService { private final AppointmentRepository appointmentRepository; @@ -30,6 +29,13 @@ public class AppointmentService { private final ServiceRepository serviceRepository; private final PetRepository petRepository; + public AppointmentService(AppointmentRepository appointmentRepository, CustomerRepository customerRepository, ServiceRepository serviceRepository, PetRepository petRepository) { + this.appointmentRepository = appointmentRepository; + this.customerRepository = customerRepository; + this.serviceRepository = serviceRepository; + this.petRepository = petRepository; + } + public Page getAllAppointments(Pageable pageable) { return appointmentRepository.findAll(pageable).map(this::mapToResponse); } diff --git a/src/main/java/com/petshop/backend/service/CategoryService.java b/src/main/java/com/petshop/backend/service/CategoryService.java index f8fdf7e8..b575a8eb 100644 --- a/src/main/java/com/petshop/backend/service/CategoryService.java +++ b/src/main/java/com/petshop/backend/service/CategoryService.java @@ -12,11 +12,14 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service - public class CategoryService { private final CategoryRepository categoryRepository; + public CategoryService(CategoryRepository categoryRepository) { + this.categoryRepository = categoryRepository; + } + public Page getAllCategories(String query, Pageable pageable) { Page categories; if (query != null && !query.trim().isEmpty()) { diff --git a/src/main/java/com/petshop/backend/service/CustomerService.java b/src/main/java/com/petshop/backend/service/CustomerService.java index 608aa535..7b826a72 100644 --- a/src/main/java/com/petshop/backend/service/CustomerService.java +++ b/src/main/java/com/petshop/backend/service/CustomerService.java @@ -12,11 +12,14 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service - public class CustomerService { private final CustomerRepository customerRepository; + public CustomerService(CustomerRepository customerRepository) { + this.customerRepository = customerRepository; + } + public Page getAllCustomers(String query, Pageable pageable) { Page customers; if (query != null && !query.trim().isEmpty()) { diff --git a/src/main/java/com/petshop/backend/service/InventoryService.java b/src/main/java/com/petshop/backend/service/InventoryService.java index d2a4395d..4aa368a2 100644 --- a/src/main/java/com/petshop/backend/service/InventoryService.java +++ b/src/main/java/com/petshop/backend/service/InventoryService.java @@ -18,13 +18,18 @@ import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; @Service - public class InventoryService { private final InventoryRepository inventoryRepository; private final ProductRepository productRepository; private final StoreRepository storeRepository; + public InventoryService(InventoryRepository inventoryRepository, ProductRepository productRepository, StoreRepository storeRepository) { + this.inventoryRepository = inventoryRepository; + this.productRepository = productRepository; + this.storeRepository = storeRepository; + } + public Page getAllInventory(Pageable pageable) { return inventoryRepository.findAll(pageable).map(this::mapToResponse); } diff --git a/src/main/java/com/petshop/backend/service/PetService.java b/src/main/java/com/petshop/backend/service/PetService.java index 0509f346..3c40a161 100644 --- a/src/main/java/com/petshop/backend/service/PetService.java +++ b/src/main/java/com/petshop/backend/service/PetService.java @@ -12,11 +12,14 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service - public class PetService { private final PetRepository petRepository; + public PetService(PetRepository petRepository) { + this.petRepository = petRepository; + } + public Page getAllPets(String query, Pageable pageable) { Page pets; if (query != null && !query.trim().isEmpty()) { diff --git a/src/main/java/com/petshop/backend/service/ProductService.java b/src/main/java/com/petshop/backend/service/ProductService.java index 2b7646de..423249a5 100644 --- a/src/main/java/com/petshop/backend/service/ProductService.java +++ b/src/main/java/com/petshop/backend/service/ProductService.java @@ -14,12 +14,16 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service - public class ProductService { private final ProductRepository productRepository; private final CategoryRepository categoryRepository; + public ProductService(ProductRepository productRepository, CategoryRepository categoryRepository) { + this.productRepository = productRepository; + this.categoryRepository = categoryRepository; + } + public Page getAllProducts(String query, Pageable pageable) { Page products; if (query != null && !query.trim().isEmpty()) { diff --git a/src/main/java/com/petshop/backend/service/ProductSupplierService.java b/src/main/java/com/petshop/backend/service/ProductSupplierService.java index c93c5899..3b87ef61 100644 --- a/src/main/java/com/petshop/backend/service/ProductSupplierService.java +++ b/src/main/java/com/petshop/backend/service/ProductSupplierService.java @@ -16,13 +16,18 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service - public class ProductSupplierService { private final ProductSupplierRepository productSupplierRepository; private final ProductRepository productRepository; private final SupplierRepository supplierRepository; + public ProductSupplierService(ProductSupplierRepository productSupplierRepository, ProductRepository productRepository, SupplierRepository supplierRepository) { + this.productSupplierRepository = productSupplierRepository; + this.productRepository = productRepository; + this.supplierRepository = supplierRepository; + } + public Page getAllProductSuppliers(Pageable pageable) { return productSupplierRepository.findAll(pageable).map(this::mapToResponse); } diff --git a/src/main/java/com/petshop/backend/service/PurchaseOrderService.java b/src/main/java/com/petshop/backend/service/PurchaseOrderService.java index d3ecd74a..d87b2f6f 100644 --- a/src/main/java/com/petshop/backend/service/PurchaseOrderService.java +++ b/src/main/java/com/petshop/backend/service/PurchaseOrderService.java @@ -14,11 +14,14 @@ import java.util.List; import java.util.stream.Collectors; @Service - public class PurchaseOrderService { private final PurchaseOrderRepository purchaseOrderRepository; + public PurchaseOrderService(PurchaseOrderRepository purchaseOrderRepository) { + this.purchaseOrderRepository = purchaseOrderRepository; + } + public Page getAllPurchaseOrders(Pageable pageable) { return purchaseOrderRepository.findAll(pageable).map(this::mapToResponse); } diff --git a/src/main/java/com/petshop/backend/service/RefundService.java b/src/main/java/com/petshop/backend/service/RefundService.java index 1758d732..ae378996 100644 --- a/src/main/java/com/petshop/backend/service/RefundService.java +++ b/src/main/java/com/petshop/backend/service/RefundService.java @@ -16,7 +16,6 @@ import java.util.ArrayList; import java.util.List; @Service - public class RefundService { private final RefundRepository refundRepository; @@ -25,6 +24,14 @@ public class RefundService { private final InventoryRepository inventoryRepository; private final UserRepository userRepository; + public RefundService(RefundRepository refundRepository, SaleRepository saleRepository, SaleItemRepository saleItemRepository, InventoryRepository inventoryRepository, UserRepository userRepository) { + this.refundRepository = refundRepository; + this.saleRepository = saleRepository; + this.saleItemRepository = saleItemRepository; + this.inventoryRepository = inventoryRepository; + this.userRepository = userRepository; + } + @Transactional public RefundResponse createRefund(Long saleId, RefundRequest request) { String username = SecurityContextHolder.getContext().getAuthentication().getName(); diff --git a/src/main/java/com/petshop/backend/service/SaleService.java b/src/main/java/com/petshop/backend/service/SaleService.java index d74b6fa8..b5db6959 100644 --- a/src/main/java/com/petshop/backend/service/SaleService.java +++ b/src/main/java/com/petshop/backend/service/SaleService.java @@ -18,7 +18,6 @@ import java.util.ArrayList; import java.util.List; @Service - public class SaleService { private final SaleRepository saleRepository; @@ -28,6 +27,15 @@ public class SaleService { private final InventoryRepository inventoryRepository; private final UserRepository userRepository; + public SaleService(SaleRepository saleRepository, ProductRepository productRepository, CustomerRepository customerRepository, StoreRepository storeRepository, InventoryRepository inventoryRepository, UserRepository userRepository) { + this.saleRepository = saleRepository; + this.productRepository = productRepository; + this.customerRepository = customerRepository; + this.storeRepository = storeRepository; + this.inventoryRepository = inventoryRepository; + this.userRepository = userRepository; + } + public Page getAllSales(Pageable pageable) { return saleRepository.findAll(pageable).map(this::mapToResponse); } diff --git a/src/main/java/com/petshop/backend/service/ServiceService.java b/src/main/java/com/petshop/backend/service/ServiceService.java index e01b2092..72e5662f 100644 --- a/src/main/java/com/petshop/backend/service/ServiceService.java +++ b/src/main/java/com/petshop/backend/service/ServiceService.java @@ -11,11 +11,14 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service - public class ServiceService { private final ServiceRepository serviceRepository; + public ServiceService(ServiceRepository serviceRepository) { + this.serviceRepository = serviceRepository; + } + public Page getAllServices(String query, Pageable pageable) { Page services; if (query != null && !query.trim().isEmpty()) { diff --git a/src/main/java/com/petshop/backend/service/SupplierService.java b/src/main/java/com/petshop/backend/service/SupplierService.java index 91daaa35..c7cdefaa 100644 --- a/src/main/java/com/petshop/backend/service/SupplierService.java +++ b/src/main/java/com/petshop/backend/service/SupplierService.java @@ -12,11 +12,14 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service - public class SupplierService { private final SupplierRepository supplierRepository; + public SupplierService(SupplierRepository supplierRepository) { + this.supplierRepository = supplierRepository; + } + public Page getAllSuppliers(String query, Pageable pageable) { Page suppliers; if (query != null && !query.trim().isEmpty()) { diff --git a/src/main/java/com/petshop/backend/service/UserService.java b/src/main/java/com/petshop/backend/service/UserService.java index e9e7c3e9..1183ea2f 100644 --- a/src/main/java/com/petshop/backend/service/UserService.java +++ b/src/main/java/com/petshop/backend/service/UserService.java @@ -13,12 +13,16 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service - public class UserService { private final UserRepository userRepository; private final PasswordEncoder passwordEncoder; + public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder) { + this.userRepository = userRepository; + this.passwordEncoder = passwordEncoder; + } + public Page getAllUsers(Pageable pageable) { return userRepository.findAll(pageable).map(this::mapToResponse); } From bdfc592821f306018466ebd220a0120bee331009 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Thu, 5 Mar 2026 08:13:37 -0700 Subject: [PATCH 15/84] Add Postman collection and update JDK to 25 --- petshop-api.postman_collection.json | 2549 +++++++++++++++++++++++++++ 1 file changed, 2549 insertions(+) create mode 100644 petshop-api.postman_collection.json diff --git a/petshop-api.postman_collection.json b/petshop-api.postman_collection.json new file mode 100644 index 00000000..a2537681 --- /dev/null +++ b/petshop-api.postman_collection.json @@ -0,0 +1,2549 @@ +{ + "info": { + "name": "PetShop Unified API v1 (RBAC + Dropdowns + Chat)", + "_postman_id": "4d2d4e10-4338-4b85-a8ef-aa9db2ed75be", + "description": "Unified /api/v1 endpoints for Desktop, Android, and Website.\n\nVariables:\n- baseUrl (example http://localhost:8080)\n- staffToken (set by staff/admin login)\n- customerToken (set by customer login)\n\nNotes:\n- Login test scripts assume response contains {\"token\":\"...\"}.\n- Bulk delete uses DELETE with JSON body {ids:[...]}.", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "variable": [ + { + "key": "baseUrl", + "value": "http://localhost:8080" + }, + { + "key": "staffToken", + "value": "" + }, + { + "key": "customerToken", + "value": "" + }, + { + "key": "petId", + "value": "1" + }, + { + "key": "adoptionId", + "value": "1" + }, + { + "key": "appointmentId", + "value": "1" + }, + { + "key": "serviceId", + "value": "1" + }, + { + "key": "prodId", + "value": "1" + }, + { + "key": "categoryId", + "value": "1" + }, + { + "key": "supId", + "value": "1" + }, + { + "key": "inventoryId", + "value": "1" + }, + { + "key": "saleId", + "value": "1" + }, + { + "key": "purchaseOrderId", + "value": "1" + }, + { + "key": "userId", + "value": "1" + }, + { + "key": "employeeId", + "value": "1" + }, + { + "key": "roomId", + "value": "1" + }, + { + "key": "storeId", + "value": "1" + } + ], + "item": [ + { + "name": "Auth", + "item": [ + { + "name": "Register Customer", + "request": { + "method": "POST", + "url": "{{baseUrl}}/api/v1/auth/register", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"firstName\": \"Chris\",\n \"lastName\": \"Ng\",\n \"email\": \"chris.ng@example.com\",\n \"phone\": \"555-0120\",\n \"password\": \"ChangeMe123\"\n}", + "options": { + "raw": { + "language": "json" + } + } + } + } + }, + { + "name": "Login (Staff/Admin) -> sets staffToken", + "request": { + "method": "POST", + "url": "{{baseUrl}}/api/v1/auth/login", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"username\": \"admin\",\n \"password\": \"admin\"\n}", + "options": { + "raw": { + "language": "json" + } + } + } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "try {", + " const json = pm.response.json();", + " if (json && json.token) pm.collectionVariables.set('staffToken', json.token);", + "} catch (e) {}" + ] + } + } + ] + }, + { + "name": "Login (Customer) -> sets customerToken", + "request": { + "method": "POST", + "url": "{{baseUrl}}/api/v1/auth/login", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "description": "If login uses username instead of email, adjust request body.", + "body": { + "mode": "raw", + "raw": "{\n \"email\": \"chris.ng@example.com\",\n \"password\": \"ChangeMe123\"\n}", + "options": { + "raw": { + "language": "json" + } + } + } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "try {", + " const json = pm.response.json();", + " if (json && json.token) pm.collectionVariables.set('customerToken', json.token);", + "} catch (e) {}" + ] + } + } + ] + }, + { + "name": "Logout (Staff/Admin)", + "request": { + "method": "POST", + "url": "{{baseUrl}}/api/v1/auth/logout", + "header": [], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + } + } + }, + { + "name": "Logout (Customer)", + "request": { + "method": "POST", + "url": "{{baseUrl}}/api/v1/auth/logout", + "header": [], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{customerToken}}", + "type": "string" + } + ] + } + } + }, + { + "name": "Auth Me (Staff/Admin)", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/auth/me", + "header": [], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + } + } + }, + { + "name": "Auth Me (Customer)", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/auth/me", + "header": [], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{customerToken}}", + "type": "string" + } + ] + } + } + } + ] + }, + { + "name": "My Account (/me)", + "item": [ + { + "name": "Get My Profile (Staff/Admin)", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/me", + "header": [], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + } + } + }, + { + "name": "Update My Profile (Staff/Admin)", + "request": { + "method": "PUT", + "url": "{{baseUrl}}/api/v1/me", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + }, + "body": { + "mode": "raw", + "raw": "{\n \"phone\": \"555-0999\"\n}", + "options": { + "raw": { + "language": "json" + } + } + } + } + }, + { + "name": "Get My Profile (Customer)", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/me", + "header": [], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{customerToken}}", + "type": "string" + } + ] + } + } + }, + { + "name": "Update My Profile (Customer)", + "request": { + "method": "PUT", + "url": "{{baseUrl}}/api/v1/me", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{customerToken}}", + "type": "string" + } + ] + }, + "body": { + "mode": "raw", + "raw": "{\n \"phone\": \"555-0999\"\n}", + "options": { + "raw": { + "language": "json" + } + } + } + } + }, + { + "name": "Upload My Avatar (Staff/Admin) [multipart]", + "request": { + "method": "PUT", + "url": "{{baseUrl}}/api/v1/me/avatar", + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + }, + "header": [], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "file", + "type": "file", + "src": "" + } + ] + }, + "description": "Uploads avatar image for the logged-in staff/admin. Public access via GET /api/v1/staff/{employeeId}/avatar." + } + }, + { + "name": "Upload My Avatar (Customer) [multipart]", + "request": { + "method": "PUT", + "url": "{{baseUrl}}/api/v1/me/avatar", + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{customerToken}}", + "type": "string" + } + ] + }, + "header": [], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "file", + "type": "file", + "src": "" + } + ] + }, + "description": "Optional if customer avatars are supported." + } + } + ] + }, + { + "name": "Dropdowns (lightweight id+name)", + "item": [ + { + "name": "Dropdown - Pets (staff)", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/dropdowns/pets", + "header": [], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + } + } + }, + { + "name": "Dropdown - Customers (staff)", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/dropdowns/customers", + "header": [], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + } + } + }, + { + "name": "Dropdown - Services (staff)", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/dropdowns/services", + "header": [], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + } + } + }, + { + "name": "Dropdown - Products (staff)", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/dropdowns/products", + "header": [], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + } + } + }, + { + "name": "Dropdown - Suppliers (admin)", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/dropdowns/suppliers", + "header": [], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + } + } + }, + { + "name": "Dropdown - Categories (staff)", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/dropdowns/categories", + "header": [], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + } + } + }, + { + "name": "Dropdown - Stores (staff)", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/dropdowns/stores", + "header": [], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + } + } + }, + { + "name": "Dropdown - Services (customer)", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/dropdowns/services", + "header": [], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{customerToken}}", + "type": "string" + } + ] + } + } + }, + { + "name": "Dropdown - Stores (customer)", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/dropdowns/stores", + "header": [], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{customerToken}}", + "type": "string" + } + ] + } + } + } + ] + }, + { + "name": "Resources (Staff)", + "item": [ + { + "name": "Pets", + "item": [ + { + "name": "List/Search", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/pets?q=&page=0&size=50&sort=id,desc", + "header": [], + "description": "Returns UI-ready rows (joined fields included).", + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + } + } + }, + { + "name": "Create", + "request": { + "method": "POST", + "url": "{{baseUrl}}/api/v1/pets", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + }, + "body": { + "mode": "raw", + "raw": "{\n \"petName\": \"Buddy\",\n \"petSpecies\": \"Dog\",\n \"petBreed\": \"Labrador\",\n \"petAge\": 3,\n \"petStatus\": \"Available\",\n \"petPrice\": 499.99\n}", + "options": { + "raw": { + "language": "json" + } + } + } + } + }, + { + "name": "Get One", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/pets/{{petId}}", + "header": [], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + } + } + }, + { + "name": "Update (PUT)", + "request": { + "method": "PUT", + "url": "{{baseUrl}}/api/v1/pets/{{petId}}", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + }, + "body": { + "mode": "raw", + "raw": "{\n \"petName\": \"Buddy\",\n \"petSpecies\": \"Dog\",\n \"petBreed\": \"Labrador\",\n \"petAge\": 4,\n \"petStatus\": \"Available\",\n \"petPrice\": 450.0\n}", + "options": { + "raw": { + "language": "json" + } + } + } + } + }, + { + "name": "Delete One", + "request": { + "method": "DELETE", + "url": "{{baseUrl}}/api/v1/pets/{{petId}}", + "header": [], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + } + } + }, + { + "name": "Bulk Delete (DELETE body)", + "request": { + "method": "DELETE", + "url": "{{baseUrl}}/api/v1/pets", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "description": "Bulk delete with JSON body.", + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + }, + "body": { + "mode": "raw", + "raw": "{\n \"ids\": [\n 1,\n 2,\n 3\n ]\n}", + "options": { + "raw": { + "language": "json" + } + } + } + } + } + ] + }, + { + "name": "Adoptions", + "item": [ + { + "name": "List/Search", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/adoptions?q=&page=0&size=50&sort=id,desc", + "header": [], + "description": "Returns UI-ready rows (joined fields included).", + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + } + } + }, + { + "name": "Create", + "request": { + "method": "POST", + "url": "{{baseUrl}}/api/v1/adoptions", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + }, + "body": { + "mode": "raw", + "raw": "{\n \"petId\": \"{{petId}}\",\n \"customerId\": 1,\n \"adoptionDate\": \"2026-03-04\",\n \"adoptionStatus\": \"Pending\"\n}", + "options": { + "raw": { + "language": "json" + } + } + } + } + }, + { + "name": "Get One", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/adoptions/{{adoptionId}}", + "header": [], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + } + } + }, + { + "name": "Update (PUT)", + "request": { + "method": "PUT", + "url": "{{baseUrl}}/api/v1/adoptions/{{adoptionId}}", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + }, + "body": { + "mode": "raw", + "raw": "{\n \"petId\": \"{{petId}}\",\n \"customerId\": 1,\n \"adoptionDate\": \"2026-03-04\",\n \"adoptionStatus\": \"Approved\"\n}", + "options": { + "raw": { + "language": "json" + } + } + } + } + }, + { + "name": "Delete One", + "request": { + "method": "DELETE", + "url": "{{baseUrl}}/api/v1/adoptions/{{adoptionId}}", + "header": [], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + } + } + }, + { + "name": "Bulk Delete (DELETE body)", + "request": { + "method": "DELETE", + "url": "{{baseUrl}}/api/v1/adoptions", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "description": "Bulk delete with JSON body.", + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + }, + "body": { + "mode": "raw", + "raw": "{\n \"ids\": [\n 1,\n 2,\n 3\n ]\n}", + "options": { + "raw": { + "language": "json" + } + } + } + } + } + ] + }, + { + "name": "Appointments", + "item": [ + { + "name": "List/Search", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/appointments?q=&page=0&size=50&sort=id,desc", + "header": [], + "description": "Returns UI-ready rows (joined fields included).", + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + } + } + }, + { + "name": "Create", + "request": { + "method": "POST", + "url": "{{baseUrl}}/api/v1/appointments", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + }, + "body": { + "mode": "raw", + "raw": "{\n \"serviceId\": \"{{serviceId}}\",\n \"customerId\": 1,\n \"appointmentDate\": \"2026-03-04\",\n \"appointmentTime\": \"14:30:00\",\n \"appointmentStatus\": \"Booked\",\n \"petIds\": [\n \"{{petId}}\"\n ]\n}", + "options": { + "raw": { + "language": "json" + } + } + } + } + }, + { + "name": "Get One", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/appointments/{{appointmentId}}", + "header": [], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + } + } + }, + { + "name": "Update (PUT)", + "request": { + "method": "PUT", + "url": "{{baseUrl}}/api/v1/appointments/{{appointmentId}}", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + }, + "body": { + "mode": "raw", + "raw": "{\n \"serviceId\": \"{{serviceId}}\",\n \"customerId\": 1,\n \"appointmentDate\": \"2026-03-05\",\n \"appointmentTime\": \"15:00:00\",\n \"appointmentStatus\": \"Booked\",\n \"petIds\": [\n \"{{petId}}\"\n ]\n}", + "options": { + "raw": { + "language": "json" + } + } + } + } + }, + { + "name": "Delete One", + "request": { + "method": "DELETE", + "url": "{{baseUrl}}/api/v1/appointments/{{appointmentId}}", + "header": [], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + } + } + }, + { + "name": "Bulk Delete (DELETE body)", + "request": { + "method": "DELETE", + "url": "{{baseUrl}}/api/v1/appointments", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "description": "Bulk delete with JSON body.", + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + }, + "body": { + "mode": "raw", + "raw": "{\n \"ids\": [\n 1,\n 2,\n 3\n ]\n}", + "options": { + "raw": { + "language": "json" + } + } + } + } + } + ] + }, + { + "name": "Appointments - Availability", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/appointments/availability?storeId={{storeId}}&serviceId={{serviceId}}&date=2026-03-04", + "header": [], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + } + } + }, + { + "name": "Services", + "item": [ + { + "name": "List/Search", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/services?q=&page=0&size=50&sort=id,desc", + "header": [], + "description": "Returns UI-ready rows (joined fields included).", + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + } + } + }, + { + "name": "Create", + "request": { + "method": "POST", + "url": "{{baseUrl}}/api/v1/services", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + }, + "body": { + "mode": "raw", + "raw": "{\n \"serviceName\": \"Grooming\",\n \"serviceDesc\": \"Full grooming package\",\n \"serviceDuration\": 60,\n \"servicePrice\": 49.99\n}", + "options": { + "raw": { + "language": "json" + } + } + } + } + }, + { + "name": "Get One", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/services/{{serviceId}}", + "header": [], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + } + } + }, + { + "name": "Update (PUT)", + "request": { + "method": "PUT", + "url": "{{baseUrl}}/api/v1/services/{{serviceId}}", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + }, + "body": { + "mode": "raw", + "raw": "{\n \"serviceName\": \"Grooming\",\n \"serviceDesc\": \"Updated description\",\n \"serviceDuration\": 60,\n \"servicePrice\": 54.99\n}", + "options": { + "raw": { + "language": "json" + } + } + } + } + }, + { + "name": "Delete One", + "request": { + "method": "DELETE", + "url": "{{baseUrl}}/api/v1/services/{{serviceId}}", + "header": [], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + } + } + }, + { + "name": "Bulk Delete (DELETE body)", + "request": { + "method": "DELETE", + "url": "{{baseUrl}}/api/v1/services", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "description": "Bulk delete with JSON body.", + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + }, + "body": { + "mode": "raw", + "raw": "{\n \"ids\": [\n 1,\n 2,\n 3\n ]\n}", + "options": { + "raw": { + "language": "json" + } + } + } + } + } + ] + }, + { + "name": "Products", + "item": [ + { + "name": "List/Search", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/products?q=&page=0&size=50&sort=id,desc", + "header": [], + "description": "Returns UI-ready rows (joined fields included).", + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + } + } + }, + { + "name": "Create", + "request": { + "method": "POST", + "url": "{{baseUrl}}/api/v1/products", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + }, + "body": { + "mode": "raw", + "raw": "{\n \"prodName\": \"Dog Food\",\n \"prodPrice\": 19.99,\n \"prodDesc\": \"Large bag\",\n \"categoryId\": \"{{categoryId}}\"\n}", + "options": { + "raw": { + "language": "json" + } + } + } + } + }, + { + "name": "Get One", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/products/{{prodId}}", + "header": [], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + } + } + }, + { + "name": "Update (PUT)", + "request": { + "method": "PUT", + "url": "{{baseUrl}}/api/v1/products/{{prodId}}", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + }, + "body": { + "mode": "raw", + "raw": "{\n \"prodName\": \"Dog Food\",\n \"prodPrice\": 21.99,\n \"prodDesc\": \"Large bag\",\n \"categoryId\": \"{{categoryId}}\"\n}", + "options": { + "raw": { + "language": "json" + } + } + } + } + }, + { + "name": "Delete One", + "request": { + "method": "DELETE", + "url": "{{baseUrl}}/api/v1/products/{{prodId}}", + "header": [], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + } + } + }, + { + "name": "Bulk Delete (DELETE body)", + "request": { + "method": "DELETE", + "url": "{{baseUrl}}/api/v1/products", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "description": "Bulk delete with JSON body.", + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + }, + "body": { + "mode": "raw", + "raw": "{\n \"ids\": [\n 1,\n 2,\n 3\n ]\n}", + "options": { + "raw": { + "language": "json" + } + } + } + } + } + ] + }, + { + "name": "Categories", + "item": [ + { + "name": "List/Search", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/categories?q=&page=0&size=50&sort=id,desc", + "header": [], + "description": "Returns UI-ready rows (joined fields included).", + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + } + } + }, + { + "name": "Create", + "request": { + "method": "POST", + "url": "{{baseUrl}}/api/v1/categories", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + }, + "body": { + "mode": "raw", + "raw": "{\n \"categoryName\": \"Food\",\n \"categoryType\": \"PRODUCT\"\n}", + "options": { + "raw": { + "language": "json" + } + } + } + } + }, + { + "name": "Get One", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/categories/{{categoryId}}", + "header": [], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + } + } + }, + { + "name": "Update (PUT)", + "request": { + "method": "PUT", + "url": "{{baseUrl}}/api/v1/categories/{{categoryId}}", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + }, + "body": { + "mode": "raw", + "raw": "{\n \"categoryName\": \"Food & Treats\",\n \"categoryType\": \"PRODUCT\"\n}", + "options": { + "raw": { + "language": "json" + } + } + } + } + }, + { + "name": "Delete One", + "request": { + "method": "DELETE", + "url": "{{baseUrl}}/api/v1/categories/{{categoryId}}", + "header": [], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + } + } + }, + { + "name": "Bulk Delete (DELETE body)", + "request": { + "method": "DELETE", + "url": "{{baseUrl}}/api/v1/categories", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "description": "Bulk delete with JSON body.", + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + }, + "body": { + "mode": "raw", + "raw": "{\n \"ids\": [\n 1,\n 2,\n 3\n ]\n}", + "options": { + "raw": { + "language": "json" + } + } + } + } + } + ] + } + ] + }, + { + "name": "Admin-only Resources", + "item": [ + { + "name": "Inventory", + "item": [ + { + "name": "List/Search", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/inventory?q=&page=0&size=50&sort=id,desc", + "header": [], + "description": "Returns UI-ready rows (joined fields included).", + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + } + } + }, + { + "name": "Create", + "request": { + "method": "POST", + "url": "{{baseUrl}}/api/v1/inventory", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + }, + "body": { + "mode": "raw", + "raw": "{\n \"prodId\": \"{{prodId}}\",\n \"quantity\": 25\n}", + "options": { + "raw": { + "language": "json" + } + } + } + } + }, + { + "name": "Get One", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/inventory/{{inventoryId}}", + "header": [], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + } + } + }, + { + "name": "Update (PUT)", + "request": { + "method": "PUT", + "url": "{{baseUrl}}/api/v1/inventory/{{inventoryId}}", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + }, + "body": { + "mode": "raw", + "raw": "{\n \"prodId\": \"{{prodId}}\",\n \"quantity\": 30\n}", + "options": { + "raw": { + "language": "json" + } + } + } + } + }, + { + "name": "Delete One", + "request": { + "method": "DELETE", + "url": "{{baseUrl}}/api/v1/inventory/{{inventoryId}}", + "header": [], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + } + } + }, + { + "name": "Bulk Delete (DELETE body)", + "request": { + "method": "DELETE", + "url": "{{baseUrl}}/api/v1/inventory", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "description": "Bulk delete with JSON body.", + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + }, + "body": { + "mode": "raw", + "raw": "{\n \"ids\": [\n 1,\n 2,\n 3\n ]\n}", + "options": { + "raw": { + "language": "json" + } + } + } + } + } + ] + }, + { + "name": "Suppliers", + "item": [ + { + "name": "List/Search", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/suppliers?q=&page=0&size=50&sort=id,desc", + "header": [], + "description": "Returns UI-ready rows (joined fields included).", + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + } + } + }, + { + "name": "Create", + "request": { + "method": "POST", + "url": "{{baseUrl}}/api/v1/suppliers", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + }, + "body": { + "mode": "raw", + "raw": "{\n \"supName\": \"Acme Supplies\",\n \"supPhone\": \"555-0100\",\n \"supEmail\": \"sales@acme.example\",\n \"supAddress\": \"123 Main St\"\n}", + "options": { + "raw": { + "language": "json" + } + } + } + } + }, + { + "name": "Get One", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/suppliers/{{supId}}", + "header": [], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + } + } + }, + { + "name": "Update (PUT)", + "request": { + "method": "PUT", + "url": "{{baseUrl}}/api/v1/suppliers/{{supId}}", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + }, + "body": { + "mode": "raw", + "raw": "{\n \"supName\": \"Acme Supplies\",\n \"supPhone\": \"555-0100\",\n \"supEmail\": \"support@acme.example\",\n \"supAddress\": \"123 Main St\"\n}", + "options": { + "raw": { + "language": "json" + } + } + } + } + }, + { + "name": "Delete One", + "request": { + "method": "DELETE", + "url": "{{baseUrl}}/api/v1/suppliers/{{supId}}", + "header": [], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + } + } + }, + { + "name": "Bulk Delete (DELETE body)", + "request": { + "method": "DELETE", + "url": "{{baseUrl}}/api/v1/suppliers", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "description": "Bulk delete with JSON body.", + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + }, + "body": { + "mode": "raw", + "raw": "{\n \"ids\": [\n 1,\n 2,\n 3\n ]\n}", + "options": { + "raw": { + "language": "json" + } + } + } + } + } + ] + }, + { + "name": "Product Suppliers (Costing)", + "item": [ + { + "name": "List/Search", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/product-suppliers?q=&page=0&size=50", + "header": [], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + } + } + }, + { + "name": "Create", + "request": { + "method": "POST", + "url": "{{baseUrl}}/api/v1/product-suppliers", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + }, + "body": { + "mode": "raw", + "raw": "{\n \"prodId\": \"{{prodId}}\",\n \"supId\": \"{{supId}}\",\n \"cost\": 12.34\n}", + "options": { + "raw": { + "language": "json" + } + } + } + } + }, + { + "name": "Update (composite)", + "request": { + "method": "PUT", + "url": "{{baseUrl}}/api/v1/product-suppliers", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "description": "Update by composite key (prodId, supId).", + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + }, + "body": { + "mode": "raw", + "raw": "{\n \"prodId\": \"{{prodId}}\",\n \"supId\": \"{{supId}}\",\n \"cost\": 13.0\n}", + "options": { + "raw": { + "language": "json" + } + } + } + } + }, + { + "name": "Bulk Delete (keys)", + "request": { + "method": "DELETE", + "url": "{{baseUrl}}/api/v1/product-suppliers", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + }, + "body": { + "mode": "raw", + "raw": "{\n \"keys\": [\n {\n \"prodId\": 1,\n \"supId\": 2\n },\n {\n \"prodId\": 3,\n \"supId\": 4\n }\n ]\n}", + "options": { + "raw": { + "language": "json" + } + } + } + } + } + ] + }, + { + "name": "Purchase Orders (read)", + "item": [ + { + "name": "List/Search", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/purchase-orders?q=&page=0&size=50", + "header": [], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + } + } + }, + { + "name": "Get One", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/purchase-orders/{{purchaseOrderId}}", + "header": [], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + } + } + } + ] + }, + { + "name": "Users (Staff Accounts)", + "item": [ + { + "name": "List/Search", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/users?q=&page=0&size=50", + "header": [], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + } + } + }, + { + "name": "Create", + "request": { + "method": "POST", + "url": "{{baseUrl}}/api/v1/users", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "description": "Admin-only.", + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + }, + "body": { + "mode": "raw", + "raw": "{\n \"firstName\": \"Sam\",\n \"lastName\": \"Lee\",\n \"email\": \"sam.lee@example.com\",\n \"phone\": \"555-0110\",\n \"username\": \"samlee\",\n \"password\": \"ChangeMe123\",\n \"role\": \"STAFF\",\n \"storeIds\": [\n \"{{storeId}}\"\n ]\n}", + "options": { + "raw": { + "language": "json" + } + } + } + } + }, + { + "name": "Update (PUT)", + "request": { + "method": "PUT", + "url": "{{baseUrl}}/api/v1/users/{{userId}}", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + }, + "body": { + "mode": "raw", + "raw": "{\n \"phone\": \"555-0111\",\n \"role\": \"STAFF\"\n}", + "options": { + "raw": { + "language": "json" + } + } + } + } + }, + { + "name": "Delete One", + "request": { + "method": "DELETE", + "url": "{{baseUrl}}/api/v1/users/{{userId}}", + "header": [], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + } + } + }, + { + "name": "Bulk Delete", + "request": { + "method": "DELETE", + "url": "{{baseUrl}}/api/v1/users", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + }, + "body": { + "mode": "raw", + "raw": "{\n \"ids\": [\n 1,\n 2,\n 3\n ]\n}", + "options": { + "raw": { + "language": "json" + } + } + } + } + }, + { + "name": "Upload Avatar for User (Admin) [multipart]", + "request": { + "method": "PUT", + "url": "{{baseUrl}}/api/v1/users/{{userId}}/avatar", + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + }, + "header": [], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "file", + "type": "file", + "src": "" + } + ] + }, + "description": "Admin-only." + } + } + ] + } + ] + }, + { + "name": "Sales + Refunds (Staff)", + "item": [ + { + "name": "Sales - List/Search", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/sales?q=&page=0&size=50", + "header": [], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + } + } + }, + { + "name": "Sales - Create (Checkout)", + "request": { + "method": "POST", + "url": "{{baseUrl}}/api/v1/sales", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + }, + "body": { + "mode": "raw", + "raw": "{\n \"paymentMethod\": \"Cash\",\n \"storeId\": \"{{storeId}}\",\n \"items\": [\n {\n \"prodId\": \"{{prodId}}\",\n \"quantity\": 2\n }\n ]\n}", + "options": { + "raw": { + "language": "json" + } + } + } + } + }, + { + "name": "Sales - Get Detail", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/sales/{{saleId}}", + "header": [], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + } + } + }, + { + "name": "Sales - Refund", + "request": { + "method": "POST", + "url": "{{baseUrl}}/api/v1/sales/{{saleId}}/refunds", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + }, + "body": { + "mode": "raw", + "raw": "{\n \"paymentMethod\": \"Card\",\n \"storeId\": \"{{storeId}}\",\n \"items\": [\n {\n \"prodId\": \"{{prodId}}\",\n \"quantity\": 1\n }\n ]\n}", + "options": { + "raw": { + "language": "json" + } + } + } + } + } + ] + }, + { + "name": "Analytics (Admin)", + "item": [ + { + "name": "Dashboard", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/analytics/dashboard?days=30&top=10", + "header": [], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + } + } + } + ] + }, + { + "name": "Chat (REST) + WebSocket (/ws)", + "item": [ + { + "name": "Chat - Create Room (Customer)", + "request": { + "method": "POST", + "url": "{{baseUrl}}/api/v1/chat/rooms", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{customerToken}}", + "type": "string" + } + ] + }, + "body": { + "mode": "raw", + "raw": "{\n \"topic\": \"Support\"\n}", + "options": { + "raw": { + "language": "json" + } + } + } + } + }, + { + "name": "Chat - List Rooms (Customer)", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/chat/rooms", + "header": [], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{customerToken}}", + "type": "string" + } + ] + } + } + }, + { + "name": "Chat - List Rooms (Staff/Admin)", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/chat/rooms", + "header": [], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + } + } + }, + { + "name": "Chat - Room Messages (Customer)", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/chat/rooms/{{roomId}}/messages?limit=50", + "header": [], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{customerToken}}", + "type": "string" + } + ] + } + } + }, + { + "name": "Chat - Room Messages (Staff/Admin)", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/chat/rooms/{{roomId}}/messages?limit=50", + "header": [], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + } + } + }, + { + "name": "Chat - Close Room (Staff/Admin)", + "request": { + "method": "POST", + "url": "{{baseUrl}}/api/v1/chat/rooms/{{roomId}}/close", + "header": [], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{staffToken}}", + "type": "string" + } + ] + } + } + }, + { + "name": "WebSocket Notes", + "request": { + "method": "GET", + "url": "{{baseUrl}}/ws", + "header": [], + "description": "WebSocket endpoint for live chat." + } + } + ] + }, + { + "name": "Public Assets", + "item": [ + { + "name": "Get Staff Avatar (public)", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/staff/{{employeeId}}/avatar", + "header": [], + "description": "Public endpoint. Accessible without auth." + } + } + ] + }, + { + "name": "Customer Browse (optional)", + "item": [ + { + "name": "Pets - List (customer)", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/pets?q=&page=0&size=50", + "header": [], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{customerToken}}", + "type": "string" + } + ] + } + } + }, + { + "name": "Pets - Detail (customer)", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/pets/{{petId}}", + "header": [], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{customerToken}}", + "type": "string" + } + ] + } + } + }, + { + "name": "Services - List (customer)", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/services?q=&page=0&size=50", + "header": [], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{customerToken}}", + "type": "string" + } + ] + } + } + }, + { + "name": "Stores - List (customer)", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/stores?q=&page=0&size=50", + "header": [], + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{customerToken}}", + "type": "string" + } + ] + } + } + } + ] + } + ] +} \ No newline at end of file From d7fb057e64fcc77e57c29fda5b24a6050b19acc6 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Thu, 5 Mar 2026 08:44:49 -0700 Subject: [PATCH 16/84] Match Postman API contract for desktop app - Change bulk delete from POST /bulk-delete to DELETE with body - Add search parameter support (q) to all list endpoints - Add customer registration endpoint - Add stores listing endpoint - Add analytics dashboard endpoint (admin only) - Update appointment availability to include storeId - Add CUSTOMER role to User entity - Implement search across all repositories --- .../controller/AdoptionController.java | 8 +- .../controller/AnalyticsController.java | 26 ++++ .../controller/AppointmentController.java | 20 ++- .../backend/controller/AuthController.java | 40 ++++- .../controller/CategoryController.java | 2 +- .../controller/InventoryController.java | 8 +- .../backend/controller/PetController.java | 2 +- .../backend/controller/ProductController.java | 2 +- .../controller/ProductSupplierController.java | 8 +- .../controller/PurchaseOrderController.java | 6 +- .../backend/controller/SaleController.java | 6 +- .../backend/controller/ServiceController.java | 2 +- .../backend/controller/StoreController.java | 26 ++++ .../controller/SupplierController.java | 2 +- .../backend/controller/UserController.java | 8 +- .../dto/analytics/DashboardResponse.java | 10 +- .../backend/dto/auth/RegisterRequest.java | 91 +++++++++++ .../backend/dto/store/StoreResponse.java | 76 ++++++++++ .../java/com/petshop/backend/entity/User.java | 2 +- .../repository/AdoptionRepository.java | 9 ++ .../repository/AppointmentRepository.java | 11 ++ .../repository/InventoryRepository.java | 7 + .../repository/ProductSupplierRepository.java | 9 ++ .../repository/PurchaseOrderRepository.java | 9 ++ .../backend/repository/SaleRepository.java | 10 ++ .../backend/repository/StoreRepository.java | 9 ++ .../backend/repository/UserRepository.java | 10 ++ .../backend/security/SecurityConfig.java | 2 +- .../backend/service/AdoptionService.java | 10 +- .../backend/service/AnalyticsService.java | 141 ++++++++++++++++++ .../backend/service/AppointmentService.java | 37 ++++- .../backend/service/InventoryService.java | 10 +- .../service/ProductSupplierService.java | 10 +- .../backend/service/PurchaseOrderService.java | 10 +- .../petshop/backend/service/SaleService.java | 10 +- .../petshop/backend/service/StoreService.java | 37 +++++ .../petshop/backend/service/UserService.java | 10 +- 37 files changed, 650 insertions(+), 46 deletions(-) create mode 100644 src/main/java/com/petshop/backend/controller/AnalyticsController.java create mode 100644 src/main/java/com/petshop/backend/controller/StoreController.java create mode 100644 src/main/java/com/petshop/backend/dto/auth/RegisterRequest.java create mode 100644 src/main/java/com/petshop/backend/dto/store/StoreResponse.java create mode 100644 src/main/java/com/petshop/backend/service/AnalyticsService.java create mode 100644 src/main/java/com/petshop/backend/service/StoreService.java diff --git a/src/main/java/com/petshop/backend/controller/AdoptionController.java b/src/main/java/com/petshop/backend/controller/AdoptionController.java index 8fbc4db3..30da7d5e 100644 --- a/src/main/java/com/petshop/backend/controller/AdoptionController.java +++ b/src/main/java/com/petshop/backend/controller/AdoptionController.java @@ -22,8 +22,10 @@ public class AdoptionController { } @GetMapping - public ResponseEntity> getAllAdoptions(Pageable pageable) { - return ResponseEntity.ok(adoptionService.getAllAdoptions(pageable)); + public ResponseEntity> getAllAdoptions( + @RequestParam(required = false) String q, + Pageable pageable) { + return ResponseEntity.ok(adoptionService.getAllAdoptions(q, pageable)); } @GetMapping("/{id}") @@ -49,7 +51,7 @@ public class AdoptionController { return ResponseEntity.noContent().build(); } - @PostMapping("/bulk-delete") + @DeleteMapping public ResponseEntity bulkDeleteAdoptions(@Valid @RequestBody BulkDeleteRequest request) { adoptionService.bulkDeleteAdoptions(request); return ResponseEntity.noContent().build(); diff --git a/src/main/java/com/petshop/backend/controller/AnalyticsController.java b/src/main/java/com/petshop/backend/controller/AnalyticsController.java new file mode 100644 index 00000000..6c7b9b9e --- /dev/null +++ b/src/main/java/com/petshop/backend/controller/AnalyticsController.java @@ -0,0 +1,26 @@ +package com.petshop.backend.controller; + +import com.petshop.backend.dto.analytics.DashboardResponse; +import com.petshop.backend.service.AnalyticsService; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/v1/analytics") +@PreAuthorize("hasRole('ADMIN')") +public class AnalyticsController { + + private final AnalyticsService analyticsService; + + public AnalyticsController(AnalyticsService analyticsService) { + this.analyticsService = analyticsService; + } + + @GetMapping("/dashboard") + public ResponseEntity getDashboard( + @RequestParam(defaultValue = "30") int days, + @RequestParam(defaultValue = "10") int top) { + return ResponseEntity.ok(analyticsService.getDashboardData(days, top)); + } +} diff --git a/src/main/java/com/petshop/backend/controller/AppointmentController.java b/src/main/java/com/petshop/backend/controller/AppointmentController.java index a08416b5..6c9f8fa4 100644 --- a/src/main/java/com/petshop/backend/controller/AppointmentController.java +++ b/src/main/java/com/petshop/backend/controller/AppointmentController.java @@ -11,6 +11,9 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import java.time.LocalDate; +import java.util.List; + @RestController @RequestMapping("/api/v1/appointments") public class AppointmentController { @@ -22,8 +25,10 @@ public class AppointmentController { } @GetMapping - public ResponseEntity> getAllAppointments(Pageable pageable) { - return ResponseEntity.ok(appointmentService.getAllAppointments(pageable)); + public ResponseEntity> getAllAppointments( + @RequestParam(required = false) String q, + Pageable pageable) { + return ResponseEntity.ok(appointmentService.getAllAppointments(q, pageable)); } @GetMapping("/{id}") @@ -49,9 +54,18 @@ public class AppointmentController { return ResponseEntity.noContent().build(); } - @PostMapping("/bulk-delete") + @DeleteMapping public ResponseEntity bulkDeleteAppointments(@Valid @RequestBody BulkDeleteRequest request) { appointmentService.bulkDeleteAppointments(request); return ResponseEntity.noContent().build(); } + + @GetMapping("/availability") + public ResponseEntity> checkAvailability( + @RequestParam Long storeId, + @RequestParam Long serviceId, + @RequestParam String date) { + LocalDate appointmentDate = LocalDate.parse(date); + return ResponseEntity.ok(appointmentService.checkAvailability(storeId, serviceId, appointmentDate)); + } } diff --git a/src/main/java/com/petshop/backend/controller/AuthController.java b/src/main/java/com/petshop/backend/controller/AuthController.java index 970d45c9..491c4833 100644 --- a/src/main/java/com/petshop/backend/controller/AuthController.java +++ b/src/main/java/com/petshop/backend/controller/AuthController.java @@ -2,6 +2,7 @@ package com.petshop.backend.controller; import com.petshop.backend.dto.auth.LoginRequest; import com.petshop.backend.dto.auth.LoginResponse; +import com.petshop.backend.dto.auth.RegisterRequest; import com.petshop.backend.dto.auth.UserInfoResponse; import com.petshop.backend.entity.User; import com.petshop.backend.repository.UserRepository; @@ -16,6 +17,7 @@ import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.web.bind.annotation.*; import java.util.HashMap; @@ -28,11 +30,47 @@ public class AuthController { private final AuthenticationManager authenticationManager; private final UserRepository userRepository; private final JwtUtil jwtUtil; + private final PasswordEncoder passwordEncoder; - public AuthController(AuthenticationManager authenticationManager, UserRepository userRepository, JwtUtil jwtUtil) { + public AuthController(AuthenticationManager authenticationManager, UserRepository userRepository, JwtUtil jwtUtil, PasswordEncoder passwordEncoder) { this.authenticationManager = authenticationManager; this.userRepository = userRepository; this.jwtUtil = jwtUtil; + this.passwordEncoder = passwordEncoder; + } + + @PostMapping("/register") + public ResponseEntity register(@Valid @RequestBody RegisterRequest request) { + if (userRepository.findByUsername(request.getEmail()).isPresent()) { + Map error = new HashMap<>(); + error.put("message", "Email already registered"); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error); + } + + User user = new User(); + user.setUsername(request.getEmail()); + user.setEmail(request.getEmail()); + user.setPassword(passwordEncoder.encode(request.getPassword())); + user.setFullName(request.getFirstName() + " " + request.getLastName()); + user.setRole(User.Role.CUSTOMER); + user.setActive(true); + + user = userRepository.save(user); + + UserDetails userDetails = new org.springframework.security.core.userdetails.User( + user.getUsername(), + user.getPassword(), + java.util.Collections.emptyList() + ); + + String token = jwtUtil.generateToken(userDetails); + + return ResponseEntity.status(HttpStatus.CREATED).body(new LoginResponse( + token, + user.getUsername(), + user.getFullName(), + user.getRole().name() + )); } @PostMapping("/login") diff --git a/src/main/java/com/petshop/backend/controller/CategoryController.java b/src/main/java/com/petshop/backend/controller/CategoryController.java index f03055b4..ddd7b934 100644 --- a/src/main/java/com/petshop/backend/controller/CategoryController.java +++ b/src/main/java/com/petshop/backend/controller/CategoryController.java @@ -51,7 +51,7 @@ public class CategoryController { return ResponseEntity.noContent().build(); } - @PostMapping("/bulk-delete") + @DeleteMapping public ResponseEntity bulkDeleteCategories(@Valid @RequestBody BulkDeleteRequest request) { categoryService.bulkDeleteCategories(request); return ResponseEntity.noContent().build(); diff --git a/src/main/java/com/petshop/backend/controller/InventoryController.java b/src/main/java/com/petshop/backend/controller/InventoryController.java index 44db2471..35ccbff9 100644 --- a/src/main/java/com/petshop/backend/controller/InventoryController.java +++ b/src/main/java/com/petshop/backend/controller/InventoryController.java @@ -24,8 +24,10 @@ public class InventoryController { } @GetMapping - public ResponseEntity> getAllInventory(Pageable pageable) { - return ResponseEntity.ok(inventoryService.getAllInventory(pageable)); + public ResponseEntity> getAllInventory( + @RequestParam(required = false) String q, + Pageable pageable) { + return ResponseEntity.ok(inventoryService.getAllInventory(q, pageable)); } @GetMapping("/{id}") @@ -51,7 +53,7 @@ public class InventoryController { return ResponseEntity.noContent().build(); } - @PostMapping("/bulk-delete") + @DeleteMapping public ResponseEntity bulkDeleteInventory(@Valid @RequestBody BulkDeleteRequest request) { inventoryService.bulkDeleteInventory(request); return ResponseEntity.noContent().build(); diff --git a/src/main/java/com/petshop/backend/controller/PetController.java b/src/main/java/com/petshop/backend/controller/PetController.java index 70c68e8f..7ae7ca64 100644 --- a/src/main/java/com/petshop/backend/controller/PetController.java +++ b/src/main/java/com/petshop/backend/controller/PetController.java @@ -51,7 +51,7 @@ public class PetController { return ResponseEntity.noContent().build(); } - @PostMapping("/bulk-delete") + @DeleteMapping public ResponseEntity bulkDeletePets(@Valid @RequestBody BulkDeleteRequest request) { petService.bulkDeletePets(request); return ResponseEntity.noContent().build(); diff --git a/src/main/java/com/petshop/backend/controller/ProductController.java b/src/main/java/com/petshop/backend/controller/ProductController.java index 12999b70..ada0e4dc 100644 --- a/src/main/java/com/petshop/backend/controller/ProductController.java +++ b/src/main/java/com/petshop/backend/controller/ProductController.java @@ -51,7 +51,7 @@ public class ProductController { return ResponseEntity.noContent().build(); } - @PostMapping("/bulk-delete") + @DeleteMapping public ResponseEntity bulkDeleteProducts(@Valid @RequestBody BulkDeleteRequest request) { productService.bulkDeleteProducts(request); return ResponseEntity.noContent().build(); diff --git a/src/main/java/com/petshop/backend/controller/ProductSupplierController.java b/src/main/java/com/petshop/backend/controller/ProductSupplierController.java index ea56c4ad..e6d78e28 100644 --- a/src/main/java/com/petshop/backend/controller/ProductSupplierController.java +++ b/src/main/java/com/petshop/backend/controller/ProductSupplierController.java @@ -24,8 +24,10 @@ public class ProductSupplierController { } @GetMapping - public ResponseEntity> getAllProductSuppliers(Pageable pageable) { - return ResponseEntity.ok(productSupplierService.getAllProductSuppliers(pageable)); + public ResponseEntity> getAllProductSuppliers( + @RequestParam(required = false) String q, + Pageable pageable) { + return ResponseEntity.ok(productSupplierService.getAllProductSuppliers(q, pageable)); } @GetMapping("/{productId}/{supplierId}") @@ -56,7 +58,7 @@ public class ProductSupplierController { return ResponseEntity.noContent().build(); } - @PostMapping("/bulk-delete") + @DeleteMapping public ResponseEntity bulkDeleteProductSuppliers(@Valid @RequestBody BulkDeleteProductSupplierRequest request) { productSupplierService.bulkDeleteProductSuppliers(request); return ResponseEntity.noContent().build(); diff --git a/src/main/java/com/petshop/backend/controller/PurchaseOrderController.java b/src/main/java/com/petshop/backend/controller/PurchaseOrderController.java index 5a3c214c..369f6995 100644 --- a/src/main/java/com/petshop/backend/controller/PurchaseOrderController.java +++ b/src/main/java/com/petshop/backend/controller/PurchaseOrderController.java @@ -20,8 +20,10 @@ public class PurchaseOrderController { } @GetMapping - public ResponseEntity> getAllPurchaseOrders(Pageable pageable) { - return ResponseEntity.ok(purchaseOrderService.getAllPurchaseOrders(pageable)); + public ResponseEntity> getAllPurchaseOrders( + @RequestParam(required = false) String q, + Pageable pageable) { + return ResponseEntity.ok(purchaseOrderService.getAllPurchaseOrders(q, pageable)); } @GetMapping("/{id}") diff --git a/src/main/java/com/petshop/backend/controller/SaleController.java b/src/main/java/com/petshop/backend/controller/SaleController.java index 0618c317..aae791fe 100644 --- a/src/main/java/com/petshop/backend/controller/SaleController.java +++ b/src/main/java/com/petshop/backend/controller/SaleController.java @@ -21,8 +21,10 @@ public class SaleController { } @GetMapping - public ResponseEntity> getAllSales(Pageable pageable) { - return ResponseEntity.ok(saleService.getAllSales(pageable)); + public ResponseEntity> getAllSales( + @RequestParam(required = false) String q, + Pageable pageable) { + return ResponseEntity.ok(saleService.getAllSales(q, pageable)); } @GetMapping("/{id}") diff --git a/src/main/java/com/petshop/backend/controller/ServiceController.java b/src/main/java/com/petshop/backend/controller/ServiceController.java index f9167a0b..53bb5343 100644 --- a/src/main/java/com/petshop/backend/controller/ServiceController.java +++ b/src/main/java/com/petshop/backend/controller/ServiceController.java @@ -51,7 +51,7 @@ public class ServiceController { return ResponseEntity.noContent().build(); } - @PostMapping("/bulk-delete") + @DeleteMapping public ResponseEntity bulkDeleteServices(@Valid @RequestBody BulkDeleteRequest request) { serviceService.bulkDeleteServices(request); return ResponseEntity.noContent().build(); diff --git a/src/main/java/com/petshop/backend/controller/StoreController.java b/src/main/java/com/petshop/backend/controller/StoreController.java new file mode 100644 index 00000000..4fa716be --- /dev/null +++ b/src/main/java/com/petshop/backend/controller/StoreController.java @@ -0,0 +1,26 @@ +package com.petshop.backend.controller; + +import com.petshop.backend.dto.store.StoreResponse; +import com.petshop.backend.service.StoreService; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/v1/stores") +public class StoreController { + + private final StoreService storeService; + + public StoreController(StoreService storeService) { + this.storeService = storeService; + } + + @GetMapping + public ResponseEntity> getAllStores( + @RequestParam(required = false) String q, + Pageable pageable) { + return ResponseEntity.ok(storeService.getAllStores(q, pageable)); + } +} diff --git a/src/main/java/com/petshop/backend/controller/SupplierController.java b/src/main/java/com/petshop/backend/controller/SupplierController.java index 255f01dc..eb5c065d 100644 --- a/src/main/java/com/petshop/backend/controller/SupplierController.java +++ b/src/main/java/com/petshop/backend/controller/SupplierController.java @@ -53,7 +53,7 @@ public class SupplierController { return ResponseEntity.noContent().build(); } - @PostMapping("/bulk-delete") + @DeleteMapping public ResponseEntity bulkDeleteSuppliers(@Valid @RequestBody BulkDeleteRequest request) { supplierService.bulkDeleteSuppliers(request); return ResponseEntity.noContent().build(); diff --git a/src/main/java/com/petshop/backend/controller/UserController.java b/src/main/java/com/petshop/backend/controller/UserController.java index a43f01f8..b1ece730 100644 --- a/src/main/java/com/petshop/backend/controller/UserController.java +++ b/src/main/java/com/petshop/backend/controller/UserController.java @@ -24,8 +24,10 @@ public class UserController { } @GetMapping - public ResponseEntity> getAllUsers(Pageable pageable) { - return ResponseEntity.ok(userService.getAllUsers(pageable)); + public ResponseEntity> getAllUsers( + @RequestParam(required = false) String q, + Pageable pageable) { + return ResponseEntity.ok(userService.getAllUsers(q, pageable)); } @GetMapping("/{id}") @@ -51,7 +53,7 @@ public class UserController { return ResponseEntity.noContent().build(); } - @PostMapping("/bulk-delete") + @DeleteMapping public ResponseEntity bulkDeleteUsers(@Valid @RequestBody BulkDeleteRequest request) { userService.bulkDeleteUsers(request); return ResponseEntity.noContent().build(); diff --git a/src/main/java/com/petshop/backend/dto/analytics/DashboardResponse.java b/src/main/java/com/petshop/backend/dto/analytics/DashboardResponse.java index 263d3fae..c884d24c 100644 --- a/src/main/java/com/petshop/backend/dto/analytics/DashboardResponse.java +++ b/src/main/java/com/petshop/backend/dto/analytics/DashboardResponse.java @@ -74,9 +74,8 @@ public class DashboardResponse { ", dailySales=" + dailySales + '}'; } -} -class SalesSummary { + public static class SalesSummary { private BigDecimal totalRevenue; private Long totalSales; private BigDecimal totalRefunds; @@ -148,7 +147,7 @@ class SalesSummary { } } -class InventorySummary { + public static class InventorySummary { private Long totalProducts; private Long lowStockProducts; private Long outOfStockProducts; @@ -209,7 +208,7 @@ class InventorySummary { } } -class TopProduct { + public static class TopProduct { private Long productId; private String productName; private Long quantitySold; @@ -281,7 +280,7 @@ class TopProduct { } } -class DailySales { + public static class DailySales { private String date; private BigDecimal revenue; private Long salesCount; @@ -341,3 +340,4 @@ class DailySales { '}'; } } +} diff --git a/src/main/java/com/petshop/backend/dto/auth/RegisterRequest.java b/src/main/java/com/petshop/backend/dto/auth/RegisterRequest.java new file mode 100644 index 00000000..ae3f5a56 --- /dev/null +++ b/src/main/java/com/petshop/backend/dto/auth/RegisterRequest.java @@ -0,0 +1,91 @@ +package com.petshop.backend.dto.auth; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import java.util.Objects; + +public class RegisterRequest { + @NotBlank(message = "First name is required") + private String firstName; + + @NotBlank(message = "Last name is required") + private String lastName; + + @NotBlank(message = "Email is required") + @Email(message = "Email must be valid") + private String email; + + @NotBlank(message = "Phone is required") + private String phone; + + @NotBlank(message = "Password is required") + private String password; + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getPhone() { + return phone; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + RegisterRequest that = (RegisterRequest) o; + return Objects.equals(firstName, that.firstName) && + Objects.equals(lastName, that.lastName) && + Objects.equals(email, that.email) && + Objects.equals(phone, that.phone) && + Objects.equals(password, that.password); + } + + @Override + public int hashCode() { + return Objects.hash(firstName, lastName, email, phone, password); + } + + @Override + public String toString() { + return "RegisterRequest{" + + "firstName='" + firstName + '\'' + + ", lastName='" + lastName + '\'' + + ", email='" + email + '\'' + + ", phone='" + phone + '\'' + + ", password='[PROTECTED]'" + + '}'; + } +} diff --git a/src/main/java/com/petshop/backend/dto/store/StoreResponse.java b/src/main/java/com/petshop/backend/dto/store/StoreResponse.java new file mode 100644 index 00000000..6af1e702 --- /dev/null +++ b/src/main/java/com/petshop/backend/dto/store/StoreResponse.java @@ -0,0 +1,76 @@ +package com.petshop.backend.dto.store; + +import java.time.LocalDateTime; +import java.util.Objects; + +public class StoreResponse { + private Long id; + private String storeName; + private String storeLocation; + private LocalDateTime createdAt; + + public StoreResponse() { + } + + public StoreResponse(Long id, String storeName, String storeLocation, LocalDateTime createdAt) { + this.id = id; + this.storeName = storeName; + this.storeLocation = storeLocation; + this.createdAt = createdAt; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getStoreName() { + return storeName; + } + + public void setStoreName(String storeName) { + this.storeName = storeName; + } + + public String getStoreLocation() { + return storeLocation; + } + + public void setStoreLocation(String storeLocation) { + this.storeLocation = storeLocation; + } + + 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; + StoreResponse that = (StoreResponse) o; + return Objects.equals(id, that.id) && Objects.equals(storeName, that.storeName) && Objects.equals(storeLocation, that.storeLocation) && Objects.equals(createdAt, that.createdAt); + } + + @Override + public int hashCode() { + return Objects.hash(id, storeName, storeLocation, createdAt); + } + + @Override + public String toString() { + return "StoreResponse{" + + "id=" + id + + ", storeName='" + storeName + '\'' + + ", storeLocation='" + storeLocation + '\'' + + ", createdAt=" + createdAt + + '}'; + } +} diff --git a/src/main/java/com/petshop/backend/entity/User.java b/src/main/java/com/petshop/backend/entity/User.java index 7d6901a8..729e782a 100644 --- a/src/main/java/com/petshop/backend/entity/User.java +++ b/src/main/java/com/petshop/backend/entity/User.java @@ -43,7 +43,7 @@ public class User { private LocalDateTime updatedAt; public enum Role { - STAFF, ADMIN + STAFF, ADMIN, CUSTOMER } public User() { diff --git a/src/main/java/com/petshop/backend/repository/AdoptionRepository.java b/src/main/java/com/petshop/backend/repository/AdoptionRepository.java index 8bf64f74..92bf2cb2 100644 --- a/src/main/java/com/petshop/backend/repository/AdoptionRepository.java +++ b/src/main/java/com/petshop/backend/repository/AdoptionRepository.java @@ -1,9 +1,18 @@ package com.petshop.backend.repository; import com.petshop.backend.entity.Adoption; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; @Repository public interface AdoptionRepository extends JpaRepository { + + @Query("SELECT a FROM Adoption a WHERE " + + "LOWER(a.customer.customerName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + + "LOWER(a.pet.petName) LIKE LOWER(CONCAT('%', :q, '%'))") + Page searchAdoptions(@Param("q") String query, Pageable pageable); } diff --git a/src/main/java/com/petshop/backend/repository/AppointmentRepository.java b/src/main/java/com/petshop/backend/repository/AppointmentRepository.java index 5d773e8c..4040d08c 100644 --- a/src/main/java/com/petshop/backend/repository/AppointmentRepository.java +++ b/src/main/java/com/petshop/backend/repository/AppointmentRepository.java @@ -1,6 +1,8 @@ package com.petshop.backend.repository; import com.petshop.backend.entity.Appointment; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; @@ -15,4 +17,13 @@ public interface AppointmentRepository extends JpaRepository @Query("SELECT a FROM Appointment a WHERE a.appointmentDate = :date AND a.appointmentTime = :time") List findByDateAndTime(@Param("date") LocalDate date, @Param("time") LocalTime time); + + @Query("SELECT a FROM Appointment a WHERE a.service.id = :serviceId AND a.appointmentDate = :date AND a.status != 'Cancelled'") + List findByServiceAndDate(@Param("serviceId") Long serviceId, @Param("date") LocalDate date); + + @Query("SELECT DISTINCT a FROM Appointment a LEFT JOIN a.pets p WHERE " + + "LOWER(a.customer.customerName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + + "LOWER(a.service.serviceName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + + "LOWER(p.petName) LIKE LOWER(CONCAT('%', :q, '%'))") + Page searchAppointments(@Param("q") String query, Pageable pageable); } diff --git a/src/main/java/com/petshop/backend/repository/InventoryRepository.java b/src/main/java/com/petshop/backend/repository/InventoryRepository.java index 2d0e0f9c..e7e4d673 100644 --- a/src/main/java/com/petshop/backend/repository/InventoryRepository.java +++ b/src/main/java/com/petshop/backend/repository/InventoryRepository.java @@ -1,6 +1,8 @@ package com.petshop.backend.repository; import com.petshop.backend.entity.Inventory; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; @@ -13,4 +15,9 @@ public interface InventoryRepository extends JpaRepository { @Query("SELECT i FROM Inventory i WHERE i.product.id = :productId AND i.store.id = :storeId") Optional findByProductIdAndStoreId(@Param("productId") Long productId, @Param("storeId") Long storeId); + + @Query("SELECT i FROM Inventory i WHERE " + + "LOWER(i.product.productName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + + "LOWER(i.store.storeName) LIKE LOWER(CONCAT('%', :q, '%'))") + Page searchInventory(@Param("q") String query, Pageable pageable); } diff --git a/src/main/java/com/petshop/backend/repository/ProductSupplierRepository.java b/src/main/java/com/petshop/backend/repository/ProductSupplierRepository.java index f8e96185..8b5e8720 100644 --- a/src/main/java/com/petshop/backend/repository/ProductSupplierRepository.java +++ b/src/main/java/com/petshop/backend/repository/ProductSupplierRepository.java @@ -1,9 +1,18 @@ package com.petshop.backend.repository; import com.petshop.backend.entity.ProductSupplier; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; @Repository public interface ProductSupplierRepository extends JpaRepository { + + @Query("SELECT ps FROM ProductSupplier ps WHERE " + + "LOWER(ps.product.productName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + + "LOWER(ps.supplier.supplierName) LIKE LOWER(CONCAT('%', :q, '%'))") + Page searchProductSuppliers(@Param("q") String query, Pageable pageable); } diff --git a/src/main/java/com/petshop/backend/repository/PurchaseOrderRepository.java b/src/main/java/com/petshop/backend/repository/PurchaseOrderRepository.java index ed3251ac..e6087bef 100644 --- a/src/main/java/com/petshop/backend/repository/PurchaseOrderRepository.java +++ b/src/main/java/com/petshop/backend/repository/PurchaseOrderRepository.java @@ -1,9 +1,18 @@ package com.petshop.backend.repository; import com.petshop.backend.entity.PurchaseOrder; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; @Repository public interface PurchaseOrderRepository extends JpaRepository { + + @Query("SELECT po FROM PurchaseOrder po WHERE " + + "LOWER(po.supplier.supplierName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + + "LOWER(po.notes) LIKE LOWER(CONCAT('%', :q, '%'))") + Page searchPurchaseOrders(@Param("q") String query, Pageable pageable); } diff --git a/src/main/java/com/petshop/backend/repository/SaleRepository.java b/src/main/java/com/petshop/backend/repository/SaleRepository.java index f58f0660..9d5392a5 100644 --- a/src/main/java/com/petshop/backend/repository/SaleRepository.java +++ b/src/main/java/com/petshop/backend/repository/SaleRepository.java @@ -1,9 +1,19 @@ package com.petshop.backend.repository; import com.petshop.backend.entity.Sale; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; @Repository public interface SaleRepository extends JpaRepository { + + @Query("SELECT s FROM Sale s WHERE " + + "LOWER(s.customer.customerName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + + "LOWER(s.employee.fullName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + + "LOWER(s.store.storeName) LIKE LOWER(CONCAT('%', :q, '%'))") + Page searchSales(@Param("q") String query, Pageable pageable); } diff --git a/src/main/java/com/petshop/backend/repository/StoreRepository.java b/src/main/java/com/petshop/backend/repository/StoreRepository.java index 4f067633..0c855389 100644 --- a/src/main/java/com/petshop/backend/repository/StoreRepository.java +++ b/src/main/java/com/petshop/backend/repository/StoreRepository.java @@ -1,9 +1,18 @@ package com.petshop.backend.repository; import com.petshop.backend.entity.Store; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; @Repository public interface StoreRepository extends JpaRepository { + + @Query("SELECT s FROM Store s WHERE " + + "LOWER(s.storeName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + + "LOWER(s.storeLocation) LIKE LOWER(CONCAT('%', :q, '%'))") + Page searchStores(@Param("q") String query, Pageable pageable); } diff --git a/src/main/java/com/petshop/backend/repository/UserRepository.java b/src/main/java/com/petshop/backend/repository/UserRepository.java index 95b79227..486d8f1a 100644 --- a/src/main/java/com/petshop/backend/repository/UserRepository.java +++ b/src/main/java/com/petshop/backend/repository/UserRepository.java @@ -1,7 +1,11 @@ package com.petshop.backend.repository; import com.petshop.backend.entity.User; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import java.util.Optional; @@ -10,4 +14,10 @@ import java.util.Optional; public interface UserRepository extends JpaRepository { Optional findByUsername(String username); boolean existsByUsername(String username); + + @Query("SELECT u FROM User u WHERE " + + "LOWER(u.username) LIKE LOWER(CONCAT('%', :q, '%')) OR " + + "LOWER(u.fullName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + + "LOWER(u.email) LIKE LOWER(CONCAT('%', :q, '%'))") + Page searchUsers(@Param("q") String query, Pageable pageable); } diff --git a/src/main/java/com/petshop/backend/security/SecurityConfig.java b/src/main/java/com/petshop/backend/security/SecurityConfig.java index dadf9d93..e1ce42c4 100644 --- a/src/main/java/com/petshop/backend/security/SecurityConfig.java +++ b/src/main/java/com/petshop/backend/security/SecurityConfig.java @@ -36,7 +36,7 @@ public class SecurityConfig { http .csrf(AbstractHttpConfigurer::disable) .authorizeHttpRequests(auth -> auth - .requestMatchers("/api/v1/auth/login").permitAll() + .requestMatchers("/api/v1/auth/login", "/api/v1/auth/register").permitAll() .requestMatchers("/swagger-ui/**", "/v3/api-docs/**", "/swagger-ui.html").permitAll() .requestMatchers(HttpMethod.GET, "/api/v1/dropdowns/suppliers").hasRole("ADMIN") .requestMatchers("/api/v1/inventory/**").hasRole("ADMIN") diff --git a/src/main/java/com/petshop/backend/service/AdoptionService.java b/src/main/java/com/petshop/backend/service/AdoptionService.java index 021e058a..d5ecc890 100644 --- a/src/main/java/com/petshop/backend/service/AdoptionService.java +++ b/src/main/java/com/petshop/backend/service/AdoptionService.java @@ -28,8 +28,14 @@ public class AdoptionService { this.customerRepository = customerRepository; } - public Page getAllAdoptions(Pageable pageable) { - return adoptionRepository.findAll(pageable).map(this::mapToResponse); + public Page getAllAdoptions(String query, Pageable pageable) { + Page adoptions; + if (query != null && !query.trim().isEmpty()) { + adoptions = adoptionRepository.searchAdoptions(query, pageable); + } else { + adoptions = adoptionRepository.findAll(pageable); + } + return adoptions.map(this::mapToResponse); } public AdoptionResponse getAdoptionById(Long id) { diff --git a/src/main/java/com/petshop/backend/service/AnalyticsService.java b/src/main/java/com/petshop/backend/service/AnalyticsService.java new file mode 100644 index 00000000..47e27d17 --- /dev/null +++ b/src/main/java/com/petshop/backend/service/AnalyticsService.java @@ -0,0 +1,141 @@ +package com.petshop.backend.service; + +import com.petshop.backend.dto.analytics.DashboardResponse; +import com.petshop.backend.entity.Inventory; +import com.petshop.backend.entity.Product; +import com.petshop.backend.entity.Refund; +import com.petshop.backend.entity.Sale; +import com.petshop.backend.repository.InventoryRepository; +import com.petshop.backend.repository.ProductRepository; +import com.petshop.backend.repository.RefundRepository; +import com.petshop.backend.repository.SaleRepository; +import org.springframework.stereotype.Service; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.*; +import java.util.stream.Collectors; + +@Service +public class AnalyticsService { + + private final SaleRepository saleRepository; + private final RefundRepository refundRepository; + private final InventoryRepository inventoryRepository; + private final ProductRepository productRepository; + + public AnalyticsService(SaleRepository saleRepository, RefundRepository refundRepository, + InventoryRepository inventoryRepository, ProductRepository productRepository) { + this.saleRepository = saleRepository; + this.refundRepository = refundRepository; + this.inventoryRepository = inventoryRepository; + this.productRepository = productRepository; + } + + public DashboardResponse getDashboardData(int days, int top) { + LocalDateTime startDate = LocalDateTime.now().minusDays(days); + + List sales = saleRepository.findAll().stream() + .filter(sale -> sale.getSaleDate().isAfter(startDate)) + .collect(Collectors.toList()); + + List refunds = refundRepository.findAll().stream() + .filter(refund -> refund.getRefundDate().isAfter(startDate)) + .collect(Collectors.toList()); + + DashboardResponse.SalesSummary salesSummary = calculateSalesSummary(sales, refunds); + DashboardResponse.InventorySummary inventorySummary = calculateInventorySummary(); + List topProducts = calculateTopProducts(sales, top); + List dailySales = calculateDailySales(sales, days); + + return new DashboardResponse(salesSummary, inventorySummary, topProducts, dailySales); + } + + private DashboardResponse.SalesSummary calculateSalesSummary(List sales, List refunds) { + BigDecimal totalRevenue = sales.stream() + .map(Sale::getTotal) + .reduce(BigDecimal.ZERO, BigDecimal::add); + + Long totalSales = (long) sales.size(); + + BigDecimal totalRefunds = refunds.stream() + .map(Refund::getRefundAmount) + .reduce(BigDecimal.ZERO, BigDecimal::add); + + Long totalRefundCount = (long) refunds.size(); + + return new DashboardResponse.SalesSummary(totalRevenue, totalSales, totalRefunds, totalRefundCount); + } + + private DashboardResponse.InventorySummary calculateInventorySummary() { + List allInventory = inventoryRepository.findAll(); + + Long totalProducts = productRepository.count(); + + Long lowStockProducts = allInventory.stream() + .filter(inv -> inv.getQuantity() > 0 && inv.getQuantity() <= inv.getReorderLevel()) + .map(inv -> inv.getProduct().getId()) + .distinct() + .count(); + + Long outOfStockProducts = allInventory.stream() + .filter(inv -> inv.getQuantity() == 0) + .map(inv -> inv.getProduct().getId()) + .distinct() + .count(); + + return new DashboardResponse.InventorySummary(totalProducts, lowStockProducts, outOfStockProducts); + } + + private List calculateTopProducts(List sales, int top) { + Map productSalesMap = new HashMap<>(); + + for (Sale sale : sales) { + for (var item : sale.getItems()) { + Long productId = item.getProduct().getId(); + String productName = item.getProduct().getProductName(); + Long quantitySold = Long.valueOf(item.getQuantity()); + BigDecimal revenue = item.getSubtotal(); + + productSalesMap.compute(productId, (key, existing) -> { + if (existing == null) { + return new DashboardResponse.TopProduct(productId, productName, quantitySold, revenue); + } else { + existing.setQuantitySold(existing.getQuantitySold() + quantitySold); + existing.setRevenue(existing.getRevenue().add(revenue)); + return existing; + } + }); + } + } + + return productSalesMap.values().stream() + .sorted((p1, p2) -> p2.getRevenue().compareTo(p1.getRevenue())) + .limit(top) + .collect(Collectors.toList()); + } + + private List calculateDailySales(List sales, int days) { + Map dailySalesMap = new LinkedHashMap<>(); + + LocalDate startDate = LocalDate.now().minusDays(days - 1); + for (int i = 0; i < days; i++) { + LocalDate date = startDate.plusDays(i); + String dateStr = date.format(DateTimeFormatter.ISO_LOCAL_DATE); + dailySalesMap.put(date, new DashboardResponse.DailySales(dateStr, BigDecimal.ZERO, 0L)); + } + + for (Sale sale : sales) { + LocalDate saleDate = sale.getSaleDate().toLocalDate(); + if (dailySalesMap.containsKey(saleDate)) { + DashboardResponse.DailySales dailySale = dailySalesMap.get(saleDate); + dailySale.setRevenue(dailySale.getRevenue().add(sale.getTotal())); + dailySale.setSalesCount(dailySale.getSalesCount() + 1); + } + } + + return new ArrayList<>(dailySalesMap.values()); + } +} diff --git a/src/main/java/com/petshop/backend/service/AppointmentService.java b/src/main/java/com/petshop/backend/service/AppointmentService.java index 5244c5f1..8cc52b2c 100644 --- a/src/main/java/com/petshop/backend/service/AppointmentService.java +++ b/src/main/java/com/petshop/backend/service/AppointmentService.java @@ -16,6 +16,9 @@ import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -36,8 +39,14 @@ public class AppointmentService { this.petRepository = petRepository; } - public Page getAllAppointments(Pageable pageable) { - return appointmentRepository.findAll(pageable).map(this::mapToResponse); + public Page getAllAppointments(String query, Pageable pageable) { + Page appointments; + if (query != null && !query.trim().isEmpty()) { + appointments = appointmentRepository.searchAppointments(query, pageable); + } else { + appointments = appointmentRepository.findAll(pageable); + } + return appointments.map(this::mapToResponse); } public AppointmentResponse getAppointmentById(Long id) { @@ -107,6 +116,30 @@ public class AppointmentService { appointmentRepository.deleteAllById(request.getIds()); } + public List checkAvailability(Long storeId, Long serviceId, LocalDate date) { + com.petshop.backend.entity.Service service = serviceRepository.findById(serviceId) + .orElseThrow(() -> new ResourceNotFoundException("Service not found with id: " + serviceId)); + + List existingAppointments = appointmentRepository.findByServiceAndDate(serviceId, date); + Set bookedTimes = existingAppointments.stream() + .map(Appointment::getAppointmentTime) + .collect(Collectors.toSet()); + + List availableSlots = new ArrayList<>(); + LocalTime startTime = LocalTime.of(9, 0); + LocalTime endTime = LocalTime.of(17, 0); + + LocalTime currentTime = startTime; + while (currentTime.isBefore(endTime)) { + if (!bookedTimes.contains(currentTime)) { + availableSlots.add(currentTime.toString()); + } + currentTime = currentTime.plusMinutes(30); + } + + return availableSlots; + } + private Set fetchPets(List petIds) { Set pets = new HashSet<>(); for (Long petId : petIds) { diff --git a/src/main/java/com/petshop/backend/service/InventoryService.java b/src/main/java/com/petshop/backend/service/InventoryService.java index 4aa368a2..78509765 100644 --- a/src/main/java/com/petshop/backend/service/InventoryService.java +++ b/src/main/java/com/petshop/backend/service/InventoryService.java @@ -30,8 +30,14 @@ public class InventoryService { this.storeRepository = storeRepository; } - public Page getAllInventory(Pageable pageable) { - return inventoryRepository.findAll(pageable).map(this::mapToResponse); + public Page getAllInventory(String query, Pageable pageable) { + Page inventory; + if (query != null && !query.trim().isEmpty()) { + inventory = inventoryRepository.searchInventory(query, pageable); + } else { + inventory = inventoryRepository.findAll(pageable); + } + return inventory.map(this::mapToResponse); } public InventoryResponse getInventoryById(Long id) { diff --git a/src/main/java/com/petshop/backend/service/ProductSupplierService.java b/src/main/java/com/petshop/backend/service/ProductSupplierService.java index 3b87ef61..2e60b3e4 100644 --- a/src/main/java/com/petshop/backend/service/ProductSupplierService.java +++ b/src/main/java/com/petshop/backend/service/ProductSupplierService.java @@ -28,8 +28,14 @@ public class ProductSupplierService { this.supplierRepository = supplierRepository; } - public Page getAllProductSuppliers(Pageable pageable) { - return productSupplierRepository.findAll(pageable).map(this::mapToResponse); + public Page getAllProductSuppliers(String query, Pageable pageable) { + Page productSuppliers; + if (query != null && !query.trim().isEmpty()) { + productSuppliers = productSupplierRepository.searchProductSuppliers(query, pageable); + } else { + productSuppliers = productSupplierRepository.findAll(pageable); + } + return productSuppliers.map(this::mapToResponse); } public ProductSupplierResponse getProductSupplierById(Long productId, Long supplierId) { diff --git a/src/main/java/com/petshop/backend/service/PurchaseOrderService.java b/src/main/java/com/petshop/backend/service/PurchaseOrderService.java index d87b2f6f..efda04d6 100644 --- a/src/main/java/com/petshop/backend/service/PurchaseOrderService.java +++ b/src/main/java/com/petshop/backend/service/PurchaseOrderService.java @@ -22,8 +22,14 @@ public class PurchaseOrderService { this.purchaseOrderRepository = purchaseOrderRepository; } - public Page getAllPurchaseOrders(Pageable pageable) { - return purchaseOrderRepository.findAll(pageable).map(this::mapToResponse); + public Page getAllPurchaseOrders(String query, Pageable pageable) { + Page purchaseOrders; + if (query != null && !query.trim().isEmpty()) { + purchaseOrders = purchaseOrderRepository.searchPurchaseOrders(query, pageable); + } else { + purchaseOrders = purchaseOrderRepository.findAll(pageable); + } + return purchaseOrders.map(this::mapToResponse); } public PurchaseOrderResponse getPurchaseOrderById(Long id) { diff --git a/src/main/java/com/petshop/backend/service/SaleService.java b/src/main/java/com/petshop/backend/service/SaleService.java index b5db6959..698a6c66 100644 --- a/src/main/java/com/petshop/backend/service/SaleService.java +++ b/src/main/java/com/petshop/backend/service/SaleService.java @@ -36,8 +36,14 @@ public class SaleService { this.userRepository = userRepository; } - public Page getAllSales(Pageable pageable) { - return saleRepository.findAll(pageable).map(this::mapToResponse); + public Page getAllSales(String query, Pageable pageable) { + Page sales; + if (query != null && !query.trim().isEmpty()) { + sales = saleRepository.searchSales(query, pageable); + } else { + sales = saleRepository.findAll(pageable); + } + return sales.map(this::mapToResponse); } public SaleResponse getSaleById(Long id) { diff --git a/src/main/java/com/petshop/backend/service/StoreService.java b/src/main/java/com/petshop/backend/service/StoreService.java new file mode 100644 index 00000000..79ab3c47 --- /dev/null +++ b/src/main/java/com/petshop/backend/service/StoreService.java @@ -0,0 +1,37 @@ +package com.petshop.backend.service; + +import com.petshop.backend.dto.store.StoreResponse; +import com.petshop.backend.entity.Store; +import com.petshop.backend.repository.StoreRepository; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; + +@Service +public class StoreService { + + private final StoreRepository storeRepository; + + public StoreService(StoreRepository storeRepository) { + this.storeRepository = storeRepository; + } + + public Page getAllStores(String query, Pageable pageable) { + Page stores; + if (query != null && !query.trim().isEmpty()) { + stores = storeRepository.searchStores(query, pageable); + } else { + stores = storeRepository.findAll(pageable); + } + return stores.map(this::mapToResponse); + } + + private StoreResponse mapToResponse(Store store) { + return new StoreResponse( + store.getId(), + store.getStoreName(), + store.getStoreLocation(), + store.getCreatedAt() + ); + } +} diff --git a/src/main/java/com/petshop/backend/service/UserService.java b/src/main/java/com/petshop/backend/service/UserService.java index 1183ea2f..e1317aa7 100644 --- a/src/main/java/com/petshop/backend/service/UserService.java +++ b/src/main/java/com/petshop/backend/service/UserService.java @@ -23,8 +23,14 @@ public class UserService { this.passwordEncoder = passwordEncoder; } - public Page getAllUsers(Pageable pageable) { - return userRepository.findAll(pageable).map(this::mapToResponse); + public Page getAllUsers(String query, Pageable pageable) { + Page users; + if (query != null && !query.trim().isEmpty()) { + users = userRepository.searchUsers(query, pageable); + } else { + users = userRepository.findAll(pageable); + } + return users.map(this::mapToResponse); } public UserResponse getUserById(Long id) { From caec657d5b87597b3d5690b945c1dcf97814f6b1 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Thu, 5 Mar 2026 10:53:49 -0700 Subject: [PATCH 17/84] Fix product API field names to match Postman --- .../backend/dto/product/ProductRequest.java | 44 +++++++++---------- .../backend/service/ProductService.java | 12 ++--- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/src/main/java/com/petshop/backend/dto/product/ProductRequest.java b/src/main/java/com/petshop/backend/dto/product/ProductRequest.java index e4f68fd7..3e44a803 100644 --- a/src/main/java/com/petshop/backend/dto/product/ProductRequest.java +++ b/src/main/java/com/petshop/backend/dto/product/ProductRequest.java @@ -8,25 +8,25 @@ import java.util.Objects; public class ProductRequest { @NotBlank(message = "Product name is required") - private String productName; + private String prodName; @NotNull(message = "Category ID is required") private Long categoryId; - private String productDescription; + private String prodDesc; @NotNull(message = "Product price is required") @Positive(message = "Price must be positive") - private BigDecimal productPrice; + private BigDecimal prodPrice; private Boolean active = true; - public String getProductName() { - return productName; + public String getProdName() { + return prodName; } - public void setProductName(String productName) { - this.productName = productName; + public void setProdName(String prodName) { + this.prodName = prodName; } public Long getCategoryId() { @@ -37,20 +37,20 @@ public class ProductRequest { this.categoryId = categoryId; } - public String getProductDescription() { - return productDescription; + public String getProdDesc() { + return prodDesc; } - public void setProductDescription(String productDescription) { - this.productDescription = productDescription; + public void setProdDesc(String prodDesc) { + this.prodDesc = prodDesc; } - public BigDecimal getProductPrice() { - return productPrice; + public BigDecimal getProdPrice() { + return prodPrice; } - public void setProductPrice(BigDecimal productPrice) { - this.productPrice = productPrice; + public void setProdPrice(BigDecimal prodPrice) { + this.prodPrice = prodPrice; } public Boolean getActive() { @@ -66,25 +66,25 @@ public class ProductRequest { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ProductRequest that = (ProductRequest) o; - return Objects.equals(productName, that.productName) && + return Objects.equals(prodName, that.prodName) && Objects.equals(categoryId, that.categoryId) && - Objects.equals(productDescription, that.productDescription) && - Objects.equals(productPrice, that.productPrice) && + Objects.equals(prodDesc, that.prodDesc) && + Objects.equals(prodPrice, that.prodPrice) && Objects.equals(active, that.active); } @Override public int hashCode() { - return Objects.hash(productName, categoryId, productDescription, productPrice, active); + return Objects.hash(prodName, categoryId, prodDesc, prodPrice, active); } @Override public String toString() { return "ProductRequest{" + - "productName='" + productName + '\'' + + "prodName='" + prodName + '\'' + ", categoryId=" + categoryId + - ", productDescription='" + productDescription + '\'' + - ", productPrice=" + productPrice + + ", prodDesc='" + prodDesc + '\'' + + ", prodPrice=" + prodPrice + ", active=" + active + '}'; } diff --git a/src/main/java/com/petshop/backend/service/ProductService.java b/src/main/java/com/petshop/backend/service/ProductService.java index 423249a5..26f13320 100644 --- a/src/main/java/com/petshop/backend/service/ProductService.java +++ b/src/main/java/com/petshop/backend/service/ProductService.java @@ -46,10 +46,10 @@ public class ProductService { .orElseThrow(() -> new ResourceNotFoundException("Category not found with id: " + request.getCategoryId())); Product product = new Product(); - product.setProductName(request.getProductName()); + product.setProductName(request.getProdName()); product.setCategory(category); - product.setProductDescription(request.getProductDescription()); - product.setProductPrice(request.getProductPrice()); + product.setProductDescription(request.getProdDesc()); + product.setProductPrice(request.getProdPrice()); product.setActive(request.getActive() != null ? request.getActive() : true); product = productRepository.save(product); @@ -64,10 +64,10 @@ public class ProductService { Category category = categoryRepository.findById(request.getCategoryId()) .orElseThrow(() -> new ResourceNotFoundException("Category not found with id: " + request.getCategoryId())); - product.setProductName(request.getProductName()); + product.setProductName(request.getProdName()); product.setCategory(category); - product.setProductDescription(request.getProductDescription()); - product.setProductPrice(request.getProductPrice()); + product.setProductDescription(request.getProdDesc()); + product.setProductPrice(request.getProdPrice()); product.setActive(request.getActive() != null ? request.getActive() : true); product = productRepository.save(product); From 9ae6ddc48eb372719a9d2e976ff40a9bf7906104 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Thu, 5 Mar 2026 11:00:04 -0700 Subject: [PATCH 18/84] Fix inventory and supplier API field names --- .../dto/inventory/InventoryRequest.java | 17 +++-- .../backend/dto/supplier/SupplierRequest.java | 72 +++++++++---------- .../backend/service/InventoryService.java | 18 ++--- .../backend/service/SupplierService.java | 20 +++--- test_endpoints.sh | 58 +++++++++++++++ 5 files changed, 122 insertions(+), 63 deletions(-) create mode 100755 test_endpoints.sh diff --git a/src/main/java/com/petshop/backend/dto/inventory/InventoryRequest.java b/src/main/java/com/petshop/backend/dto/inventory/InventoryRequest.java index 947b7517..5ecf211a 100644 --- a/src/main/java/com/petshop/backend/dto/inventory/InventoryRequest.java +++ b/src/main/java/com/petshop/backend/dto/inventory/InventoryRequest.java @@ -6,9 +6,8 @@ import java.util.Objects; public class InventoryRequest { @NotNull(message = "Product ID is required") - private Long productId; + private Long prodId; - @NotNull(message = "Store ID is required") private Long storeId; @NotNull(message = "Quantity is required") @@ -18,12 +17,12 @@ public class InventoryRequest { @PositiveOrZero(message = "Reorder level must be zero or positive") private Integer reorderLevel = 10; - public Long getProductId() { - return productId; + public Long getProdId() { + return prodId; } - public void setProductId(Long productId) { - this.productId = productId; + public void setProdId(Long prodId) { + this.prodId = prodId; } public Long getStoreId() { @@ -55,7 +54,7 @@ public class InventoryRequest { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; InventoryRequest that = (InventoryRequest) o; - return Objects.equals(productId, that.productId) && + return Objects.equals(prodId, that.prodId) && Objects.equals(storeId, that.storeId) && Objects.equals(quantity, that.quantity) && Objects.equals(reorderLevel, that.reorderLevel); @@ -63,13 +62,13 @@ public class InventoryRequest { @Override public int hashCode() { - return Objects.hash(productId, storeId, quantity, reorderLevel); + return Objects.hash(prodId, storeId, quantity, reorderLevel); } @Override public String toString() { return "InventoryRequest{" + - "productId=" + productId + + "prodId=" + prodId + ", storeId=" + storeId + ", quantity=" + quantity + ", reorderLevel=" + reorderLevel + diff --git a/src/main/java/com/petshop/backend/dto/supplier/SupplierRequest.java b/src/main/java/com/petshop/backend/dto/supplier/SupplierRequest.java index 99fbe478..f7881ffa 100644 --- a/src/main/java/com/petshop/backend/dto/supplier/SupplierRequest.java +++ b/src/main/java/com/petshop/backend/dto/supplier/SupplierRequest.java @@ -6,55 +6,55 @@ import java.util.Objects; public class SupplierRequest { @NotBlank(message = "Supplier name is required") - private String supplierName; + private String supName; - private String supplierContact; + private String supContact; @Email(message = "Invalid email format") - private String supplierEmail; + private String supEmail; - private String supplierPhone; - private String supplierAddress; + private String supPhone; + private String supAddress; private Boolean active = true; - public String getSupplierName() { - return supplierName; + public String getSupName() { + return supName; } - public void setSupplierName(String supplierName) { - this.supplierName = supplierName; + public void setSupName(String supName) { + this.supName = supName; } - public String getSupplierContact() { - return supplierContact; + public String getSupContact() { + return supContact; } - public void setSupplierContact(String supplierContact) { - this.supplierContact = supplierContact; + public void setSupContact(String supContact) { + this.supContact = supContact; } - public String getSupplierEmail() { - return supplierEmail; + public String getSupEmail() { + return supEmail; } - public void setSupplierEmail(String supplierEmail) { - this.supplierEmail = supplierEmail; + public void setSupEmail(String supEmail) { + this.supEmail = supEmail; } - public String getSupplierPhone() { - return supplierPhone; + public String getSupPhone() { + return supPhone; } - public void setSupplierPhone(String supplierPhone) { - this.supplierPhone = supplierPhone; + public void setSupPhone(String supPhone) { + this.supPhone = supPhone; } - public String getSupplierAddress() { - return supplierAddress; + public String getSupAddress() { + return supAddress; } - public void setSupplierAddress(String supplierAddress) { - this.supplierAddress = supplierAddress; + public void setSupAddress(String supAddress) { + this.supAddress = supAddress; } public Boolean getActive() { @@ -70,27 +70,27 @@ public class SupplierRequest { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; SupplierRequest that = (SupplierRequest) o; - return Objects.equals(supplierName, that.supplierName) && - Objects.equals(supplierContact, that.supplierContact) && - Objects.equals(supplierEmail, that.supplierEmail) && - Objects.equals(supplierPhone, that.supplierPhone) && - Objects.equals(supplierAddress, that.supplierAddress) && + return Objects.equals(supName, that.supName) && + Objects.equals(supContact, that.supContact) && + Objects.equals(supEmail, that.supEmail) && + Objects.equals(supPhone, that.supPhone) && + Objects.equals(supAddress, that.supAddress) && Objects.equals(active, that.active); } @Override public int hashCode() { - return Objects.hash(supplierName, supplierContact, supplierEmail, supplierPhone, supplierAddress, active); + return Objects.hash(supName, supContact, supEmail, supPhone, supAddress, active); } @Override public String toString() { return "SupplierRequest{" + - "supplierName='" + supplierName + '\'' + - ", supplierContact='" + supplierContact + '\'' + - ", supplierEmail='" + supplierEmail + '\'' + - ", supplierPhone='" + supplierPhone + '\'' + - ", supplierAddress='" + supplierAddress + '\'' + + "supName='" + supName + '\'' + + ", supContact='" + supContact + '\'' + + ", supEmail='" + supEmail + '\'' + + ", supPhone='" + supPhone + '\'' + + ", supAddress='" + supAddress + '\'' + ", active=" + active + '}'; } diff --git a/src/main/java/com/petshop/backend/service/InventoryService.java b/src/main/java/com/petshop/backend/service/InventoryService.java index 78509765..9b2b0fe6 100644 --- a/src/main/java/com/petshop/backend/service/InventoryService.java +++ b/src/main/java/com/petshop/backend/service/InventoryService.java @@ -48,11 +48,12 @@ public class InventoryService { @Transactional public InventoryResponse createInventory(InventoryRequest request) { - Product product = productRepository.findById(request.getProductId()) - .orElseThrow(() -> new ResourceNotFoundException("Product not found with id: " + request.getProductId())); + Product product = productRepository.findById(request.getProdId()) + .orElseThrow(() -> new ResourceNotFoundException("Product not found with id: " + request.getProdId())); - Store store = storeRepository.findById(request.getStoreId()) - .orElseThrow(() -> new ResourceNotFoundException("Store not found with id: " + request.getStoreId())); + Long storeId = request.getStoreId() != null ? request.getStoreId() : 1L; + Store store = storeRepository.findById(storeId) + .orElseThrow(() -> new ResourceNotFoundException("Store not found with id: " + storeId)); Inventory inventory = new Inventory(); inventory.setProduct(product); @@ -70,11 +71,12 @@ public class InventoryService { Inventory inventory = inventoryRepository.findById(id) .orElseThrow(() -> new ResourceNotFoundException("Inventory not found with id: " + id)); - Product product = productRepository.findById(request.getProductId()) - .orElseThrow(() -> new ResourceNotFoundException("Product not found with id: " + request.getProductId())); + Product product = productRepository.findById(request.getProdId()) + .orElseThrow(() -> new ResourceNotFoundException("Product not found with id: " + request.getProdId())); - Store store = storeRepository.findById(request.getStoreId()) - .orElseThrow(() -> new ResourceNotFoundException("Store not found with id: " + request.getStoreId())); + Long storeId = request.getStoreId() != null ? request.getStoreId() : 1L; + Store store = storeRepository.findById(storeId) + .orElseThrow(() -> new ResourceNotFoundException("Store not found with id: " + storeId)); inventory.setProduct(product); inventory.setStore(store); diff --git a/src/main/java/com/petshop/backend/service/SupplierService.java b/src/main/java/com/petshop/backend/service/SupplierService.java index c7cdefaa..b234b759 100644 --- a/src/main/java/com/petshop/backend/service/SupplierService.java +++ b/src/main/java/com/petshop/backend/service/SupplierService.java @@ -39,11 +39,11 @@ public class SupplierService { @Transactional public SupplierResponse createSupplier(SupplierRequest request) { Supplier supplier = new Supplier(); - supplier.setSupplierName(request.getSupplierName()); - supplier.setSupplierContact(request.getSupplierContact()); - supplier.setSupplierEmail(request.getSupplierEmail()); - supplier.setSupplierPhone(request.getSupplierPhone()); - supplier.setSupplierAddress(request.getSupplierAddress()); + supplier.setSupplierName(request.getSupName()); + supplier.setSupplierContact(request.getSupContact()); + supplier.setSupplierEmail(request.getSupEmail()); + supplier.setSupplierPhone(request.getSupPhone()); + supplier.setSupplierAddress(request.getSupAddress()); supplier.setActive(request.getActive()); supplier = supplierRepository.save(supplier); @@ -55,11 +55,11 @@ public class SupplierService { Supplier supplier = supplierRepository.findById(id) .orElseThrow(() -> new ResourceNotFoundException("Supplier not found with id: " + id)); - supplier.setSupplierName(request.getSupplierName()); - supplier.setSupplierContact(request.getSupplierContact()); - supplier.setSupplierEmail(request.getSupplierEmail()); - supplier.setSupplierPhone(request.getSupplierPhone()); - supplier.setSupplierAddress(request.getSupplierAddress()); + supplier.setSupplierName(request.getSupName()); + supplier.setSupplierContact(request.getSupContact()); + supplier.setSupplierEmail(request.getSupEmail()); + supplier.setSupplierPhone(request.getSupPhone()); + supplier.setSupplierAddress(request.getSupAddress()); supplier.setActive(request.getActive()); supplier = supplierRepository.save(supplier); diff --git a/test_endpoints.sh b/test_endpoints.sh new file mode 100755 index 00000000..d310bbe6 --- /dev/null +++ b/test_endpoints.sh @@ -0,0 +1,58 @@ +#!/bin/bash + +ADMIN_TOKEN="eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJhZG1pbiIsImlhdCI6MTc3MjczMzAxOSwiZXhwIjoxNzcyODE5NDE5fQ.__RqJbY2_HMjMlF6MoU8LagTu8pxjmizYYg4BQ0ahxRn9PV5iSQO3WRnCnujyE04AOY5yjTDEakOZOTEpiDFSw" +STAFF_TOKEN="eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJzdGFmZiIsImlhdCI6MTc3MjczMzAyMCwiZXhwIjoxNzcyODE5NDIwfQ.m7jC_QMWmJsj-kc4Qb-9cQwUEnJEAYJ7mbpKJOMISSup1rONwloN3Heio6Iw5ysIkjNt6uZbwIX2SZygbxQSVg" +BASE_URL="http://localhost:8080/api/v1" + +PASS=0 +FAIL=0 +TOTAL=0 + +test_endpoint() { + local method=$1 + local path=$2 + local token=$3 + local expected_status=$4 + local data=$5 + local desc=$6 + + TOTAL=$((TOTAL + 1)) + + if [ -z "$data" ]; then + response=$(curl -s -w "\n%{http_code}" -X $method "$BASE_URL$path" -H "Authorization: Bearer $token" -H "Content-Type: application/json") + else + response=$(curl -s -w "\n%{http_code}" -X $method "$BASE_URL$path" -H "Authorization: Bearer $token" -H "Content-Type: application/json" -d "$data") + fi + + status=$(echo "$response" | tail -n1) + body=$(echo "$response" | head -n-1) + + if [ "$status" = "$expected_status" ]; then + echo "✓ PASS: $desc ($method $path) - $status" + PASS=$((PASS + 1)) + echo "$body" | jq '.' 2>/dev/null || echo "$body" + else + echo "✗ FAIL: $desc ($method $path) - Expected $expected_status, got $status" + FAIL=$((FAIL + 1)) + echo "$body" + fi + echo "---" +} + +echo "=========================================" +echo "PHASE 1: DROPDOWN ENDPOINTS (7 endpoints)" +echo "=========================================" + +test_endpoint "GET" "/dropdowns/pets" "$STAFF_TOKEN" "200" "" "Get pets dropdown" +test_endpoint "GET" "/dropdowns/customers" "$STAFF_TOKEN" "200" "" "Get customers dropdown" +test_endpoint "GET" "/dropdowns/services" "$STAFF_TOKEN" "200" "" "Get services dropdown" +test_endpoint "GET" "/dropdowns/products" "$STAFF_TOKEN" "200" "" "Get products dropdown" +test_endpoint "GET" "/dropdowns/categories" "$STAFF_TOKEN" "200" "" "Get categories dropdown" +test_endpoint "GET" "/dropdowns/stores" "$STAFF_TOKEN" "200" "" "Get stores dropdown" +test_endpoint "GET" "/dropdowns/suppliers" "$ADMIN_TOKEN" "200" "" "Get suppliers dropdown (admin)" + +echo "" +echo "=========================================" +echo "SUMMARY: Phase 1" +echo "=========================================" +echo "Total: $TOTAL | Pass: $PASS | Fail: $FAIL" From e57e4e11d81803479b828237e24aa2d99a25f12e Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Thu, 5 Mar 2026 11:02:04 -0700 Subject: [PATCH 19/84] Make inventory storeId optional --- .../backend/service/InventoryService.java | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/petshop/backend/service/InventoryService.java b/src/main/java/com/petshop/backend/service/InventoryService.java index 9b2b0fe6..e144e67b 100644 --- a/src/main/java/com/petshop/backend/service/InventoryService.java +++ b/src/main/java/com/petshop/backend/service/InventoryService.java @@ -51,9 +51,11 @@ public class InventoryService { Product product = productRepository.findById(request.getProdId()) .orElseThrow(() -> new ResourceNotFoundException("Product not found with id: " + request.getProdId())); - Long storeId = request.getStoreId() != null ? request.getStoreId() : 1L; - Store store = storeRepository.findById(storeId) - .orElseThrow(() -> new ResourceNotFoundException("Store not found with id: " + storeId)); + Store store = null; + if (request.getStoreId() != null) { + store = storeRepository.findById(request.getStoreId()) + .orElseThrow(() -> new ResourceNotFoundException("Store not found with id: " + request.getStoreId())); + } Inventory inventory = new Inventory(); inventory.setProduct(product); @@ -74,9 +76,11 @@ public class InventoryService { Product product = productRepository.findById(request.getProdId()) .orElseThrow(() -> new ResourceNotFoundException("Product not found with id: " + request.getProdId())); - Long storeId = request.getStoreId() != null ? request.getStoreId() : 1L; - Store store = storeRepository.findById(storeId) - .orElseThrow(() -> new ResourceNotFoundException("Store not found with id: " + storeId)); + Store store = null; + if (request.getStoreId() != null) { + store = storeRepository.findById(request.getStoreId()) + .orElseThrow(() -> new ResourceNotFoundException("Store not found with id: " + request.getStoreId())); + } inventory.setProduct(product); inventory.setStore(store); From d9689612f85fa308fc6e18fdabb8da0202fdbaa6 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Thu, 5 Mar 2026 11:03:47 -0700 Subject: [PATCH 20/84] Allow null store in inventory --- src/main/java/com/petshop/backend/entity/Inventory.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/petshop/backend/entity/Inventory.java b/src/main/java/com/petshop/backend/entity/Inventory.java index 4e67b106..4b167c91 100644 --- a/src/main/java/com/petshop/backend/entity/Inventory.java +++ b/src/main/java/com/petshop/backend/entity/Inventory.java @@ -22,7 +22,7 @@ public class Inventory { private Product product; @ManyToOne - @JoinColumn(name = "store_id", nullable = false) + @JoinColumn(name = "store_id", nullable = true) private Store store; @Column(nullable = false) From 4c082abc18587c8ad0c7358b893321ed5d98a240 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Thu, 5 Mar 2026 11:06:04 -0700 Subject: [PATCH 21/84] Fix pet status enum to uppercase --- src/main/java/com/petshop/backend/entity/Pet.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/petshop/backend/entity/Pet.java b/src/main/java/com/petshop/backend/entity/Pet.java index 0cd1c707..61abe2c0 100644 --- a/src/main/java/com/petshop/backend/entity/Pet.java +++ b/src/main/java/com/petshop/backend/entity/Pet.java @@ -30,7 +30,7 @@ public class Pet { @Enumerated(EnumType.STRING) @Column(name = "pet_status", nullable = false) - private PetStatus petStatus = PetStatus.Available; + private PetStatus petStatus = PetStatus.AVAILABLE; @Column(name = "pet_price", precision = 10, scale = 2) private BigDecimal petPrice; @@ -44,7 +44,7 @@ public class Pet { private LocalDateTime updatedAt; public enum PetStatus { - Available, Adopted, Under_Care + AVAILABLE, ADOPTED, UNDER_CARE } public Pet() { From 1755e2257f852c82e8857842d8ba583af7d399e1 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Thu, 5 Mar 2026 11:08:02 -0700 Subject: [PATCH 22/84] Handle null store in inventory response mapping --- .../java/com/petshop/backend/service/InventoryService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/petshop/backend/service/InventoryService.java b/src/main/java/com/petshop/backend/service/InventoryService.java index e144e67b..c601cf1a 100644 --- a/src/main/java/com/petshop/backend/service/InventoryService.java +++ b/src/main/java/com/petshop/backend/service/InventoryService.java @@ -111,8 +111,8 @@ public class InventoryService { inventory.getProduct().getId(), inventory.getProduct().getProductName(), inventory.getProduct().getCategory().getCategoryName(), - inventory.getStore().getId(), - inventory.getStore().getStoreName(), + inventory.getStore() != null ? inventory.getStore().getId() : null, + inventory.getStore() != null ? inventory.getStore().getStoreName() : null, inventory.getQuantity(), inventory.getReorderLevel(), inventory.getLastRestocked(), From 33c85ae49545c172b8924a319ee72bb21c905dda Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Thu, 5 Mar 2026 12:24:55 -0700 Subject: [PATCH 23/84] Add desktop schema SQL with auth table --- Petstoredata.sql | 394 +++++++++++++++++++++++++++++++++++++ sql/01_petstore_init.sql | 410 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 804 insertions(+) create mode 100644 Petstoredata.sql create mode 100644 sql/01_petstore_init.sql diff --git a/Petstoredata.sql b/Petstoredata.sql new file mode 100644 index 00000000..8c7c1576 --- /dev/null +++ b/Petstoredata.sql @@ -0,0 +1,394 @@ +DROP DATABASE IF EXISTS Petstoredb; +CREATE DATABASE Petstoredb; +USE Petstoredb; + +-- Create Tables + +CREATE TABLE storeLocation ( + storeId INT AUTO_INCREMENT PRIMARY KEY, + storeName VARCHAR(100) NOT NULL, + address VARCHAR(255) NOT NULL, + phone VARCHAR(20) NOT NULL, + email VARCHAR(100) NOT NULL +); + +CREATE TABLE employee ( + employeeId INT AUTO_INCREMENT PRIMARY KEY, + firstName VARCHAR(50) NOT NULL, + lastName VARCHAR(50) NOT NULL, + email VARCHAR(100) NOT NULL, + phone VARCHAR(20) NOT NULL, + role VARCHAR(50) NOT NULL, + isActive BOOLEAN DEFAULT TRUE NOT NULL +); + +CREATE TABLE employeeStore ( + employeeId INT NOT NULL, + storeId INT NOT NULL, + PRIMARY KEY (employeeId, storeId), + FOREIGN KEY (employeeId) REFERENCES employee(employeeId), + FOREIGN KEY (storeId) REFERENCES storeLocation(storeId) +); + +CREATE TABLE customer ( + customerId INT AUTO_INCREMENT PRIMARY KEY, + firstName VARCHAR(50) NOT NULL, + lastName VARCHAR(50) NOT NULL, + email VARCHAR(100) NOT NULL, + phone VARCHAR(20) NOT NULL +); + +CREATE TABLE pet ( + petId INT AUTO_INCREMENT PRIMARY KEY, + petName VARCHAR(50) NOT NULL, + petSpecies VARCHAR(50) NOT NULL, + petBreed VARCHAR(50) NOT NULL, + petAge INT NOT NULL, + petStatus VARCHAR(20) NOT NULL, + petPrice DECIMAL(10, 2) NOT NULL +); + +CREATE TABLE adoption ( + adoptionId INT AUTO_INCREMENT PRIMARY KEY, + petId INT NOT NULL, + customerId INT NOT NULL, + adoptionDate DATE NOT NULL, + adoptionStatus VARCHAR(20) NOT NULL, + FOREIGN KEY (petId) REFERENCES pet(petId), + FOREIGN KEY (customerId) REFERENCES customer(customerId) +); + +CREATE TABLE supplier ( + supId INT AUTO_INCREMENT PRIMARY KEY, + supCompany VARCHAR(100) NOT NULL, + supContactFirstName VARCHAR(50) NOT NULL, + supContactLastName VARCHAR(50) NOT NULL, + supEmail VARCHAR(100) NOT NULL, + supPhone VARCHAR(20) NOT NULL +); + +CREATE TABLE category ( + categoryId INT AUTO_INCREMENT PRIMARY KEY, + categoryName VARCHAR(100) NOT NULL, + categoryType VARCHAR(50) NOT NULL +); + +CREATE TABLE product ( + prodId INT AUTO_INCREMENT PRIMARY KEY, + prodName VARCHAR(100) NOT NULL, + prodPrice DECIMAL(10, 2) NOT NULL, + categoryId INT NOT NULL, + prodDesc TEXT, + FOREIGN KEY (categoryId) REFERENCES category(categoryId) +); + +CREATE TABLE productSupplier ( + supId INT NOT NULL, + prodId INT NOT NULL, + cost DECIMAL(10, 2) NOT NULL, + PRIMARY KEY (supId, prodId), + FOREIGN KEY (supId) REFERENCES supplier(supId), + FOREIGN KEY (prodId) REFERENCES product(prodId) +); + +CREATE TABLE inventory ( + inventoryId INT AUTO_INCREMENT PRIMARY KEY, + prodId INT NOT NULL, + quantity INT DEFAULT 0 NOT NULL, + FOREIGN KEY (prodId) REFERENCES product(prodId) +); + +CREATE TABLE service ( + serviceId INT AUTO_INCREMENT PRIMARY KEY, + serviceName VARCHAR(100) NOT NULL, + serviceDesc TEXT, + serviceDuration INT NOT NULL, + servicePrice DECIMAL(10, 2) NOT NULL +); + +CREATE TABLE appointment ( + appointmentId INT AUTO_INCREMENT PRIMARY KEY, + serviceId INT NOT NULL, + customerId INT NOT NULL, + appointmentDate DATE NOT NULL, + appointmentTime TIME NOT NULL, + appointmentStatus VARCHAR(20) NOT NULL, + FOREIGN KEY (serviceId) REFERENCES service(serviceId), + FOREIGN KEY (customerId) REFERENCES customer(customerId) +); + +CREATE TABLE appointmentPet ( + appointmentId INT NOT NULL, + petId INT NOT NULL, + PRIMARY KEY (appointmentId, petId), + FOREIGN KEY (appointmentId) REFERENCES appointment(appointmentId), + FOREIGN KEY (petId) REFERENCES pet(petId) +); + +CREATE TABLE sale ( + saleId INT AUTO_INCREMENT PRIMARY KEY, + saleDate DATETIME NOT NULL, + totalAmount DECIMAL(10, 2) NOT NULL, + paymentMethod VARCHAR(50) NOT NULL, + employeeId INT NOT NULL, + storeId INT NOT NULL, + isRefund BOOLEAN DEFAULT FALSE NOT NULL, + originalSaleId INT NULL, + FOREIGN KEY (employeeId) REFERENCES employee(employeeId), + FOREIGN KEY (storeId) REFERENCES storeLocation(storeId), + FOREIGN KEY (originalSaleId) REFERENCES sale(saleId) +); + +CREATE TABLE saleItem ( + saleItemId INT AUTO_INCREMENT PRIMARY KEY, + saleId INT NOT NULL, + prodId INT NOT NULL, + quantity INT NOT NULL, + unitPrice DECIMAL(10, 2) NOT NULL, + FOREIGN KEY (saleId) REFERENCES sale(saleId), + FOREIGN KEY (prodId) REFERENCES product(prodId) +); + +CREATE TABLE purchaseOrder ( + purchaseOrderId INT AUTO_INCREMENT PRIMARY KEY, + supId INT NOT NULL, + orderDate DATE NOT NULL, + status VARCHAR(50) NOT NULL, + FOREIGN KEY (supId) REFERENCES supplier(supId) +); + +CREATE TABLE activityLog ( + logId INT AUTO_INCREMENT PRIMARY KEY, + employeeId INT NOT NULL, + activity TEXT NOT NULL, + logTimestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, + FOREIGN KEY (employeeId) REFERENCES employee(employeeId) +); + +-- Insert Sample Data + +INSERT INTO storeLocation (storeName, address, phone, email) +VALUES +('Downtown Branch', '123 Main St', '123-456-7890', 'downtown@petshop.com'), +('North Branch', '456 North Ave', '987-654-3210', 'north@petshop.com'), +('West Side Store', '789 West Blvd', '555-123-4567', 'westside@petshop.com'), +('East End Shop', '321 East Road', '555-987-6543', 'eastend@petshop.com'), +('South Mall Location', '654 South Plaza', '555-246-8135', 'southmall@petshop.com'); + +INSERT INTO employee (firstName, lastName, email, phone, role, isActive) +VALUES +('John', 'Doe', 'john@petshop.com', '111-222-3333', 'Manager', TRUE), +('Sara', 'Smith', 'sara@petshop.com', '444-555-6666', 'Staff', TRUE), +('Michael', 'Johnson', 'michael@petshop.com', '222-333-4444', 'Groomer', TRUE), +('Lisa', 'Williams', 'lisa@petshop.com', '333-444-5555', 'Staff', TRUE), +('David', 'Brown', 'david@petshop.com', '555-666-7777', 'Veterinarian', TRUE), +('Emma', 'Davis', 'emma@petshop.com', '666-777-8888', 'Manager', FALSE); + +INSERT INTO employeeStore (employeeId, storeId) +VALUES +(1, 1), +(2, 1), +(2, 2), +(3, 2), +(4, 3), +(5, 1), +(5, 4), +(6, 5); + +INSERT INTO customer (firstName, lastName, email, phone) +VALUES +('Alex', 'Brown', 'alex@gmail.com', '777-888-9999'), +('Emily', 'Clark', 'emily@gmail.com', '666-555-4444'), +('James', 'Wilson', 'james@gmail.com', '888-999-0000'), +('Olivia', 'Martinez', 'olivia@gmail.com', '999-000-1111'), +('William', 'Anderson', 'william@gmail.com', '000-111-2222'), +('Sophia', 'Taylor', 'sophia@gmail.com', '111-222-3333'); + +INSERT INTO pet (petName, petSpecies, petBreed, petAge, petStatus, petPrice) +VALUES +('Buddy', 'Dog', 'Labrador', 2, 'Available', 500.00), +('Milo', 'Cat', 'Persian', 1, 'Available', 300.00), +('Charlie', 'Dog', 'Golden Retriever', 3, 'Available', 550.00), +('Luna', 'Cat', 'Siamese', 2, 'Adopted', 350.00), +('Max', 'Dog', 'Beagle', 1, 'Available', 450.00), +('Bella', 'Cat', 'Maine Coon', 4, 'Available', 400.00); + +INSERT INTO adoption (petId, customerId, adoptionDate, adoptionStatus) +VALUES +(1, 1, '2026-01-15', 'Completed'), +(4, 3, '2026-01-20', 'Completed'), +(2, 2, '2026-01-25', 'Pending'), +(5, 4, '2026-02-01', 'Completed'), +(6, 5, '2026-02-02', 'Pending'); + +INSERT INTO supplier (supCompany, supContactFirstName, supContactLastName, supEmail, supPhone) +VALUES +('PetFood Inc', 'Robert', 'King', 'contact@petfood.com', '888-111-2222'), +('Toy World', 'Jennifer', 'Lee', 'sales@toyworld.com', '888-222-3333'), +('Pet Supplies Co', 'Kevin', 'White', 'info@petsupplies.com', '888-333-4444'), +('Animal Care Products', 'Nancy', 'Green', 'orders@animalcare.com', '888-444-5555'), +('Premium Pet Goods', 'Tom', 'Black', 'support@premiumpet.com', '888-555-6666'); + +INSERT INTO category (categoryName, categoryType) +VALUES +('Dog Food', 'Product'), +('Cat Toys', 'Product'), +('Bird Supplies', 'Product'), +('Aquarium', 'Product'), +('Small Animals', 'Product'); + +INSERT INTO product (prodName, prodPrice, categoryId, prodDesc) +VALUES +('Premium Dog Food', 50.00, 1, 'High quality dog food'), +('Cat Toy Ball', 10.00, 2, 'Colorful toy for cats'), +('Bird Cage Large', 120.00, 3, 'Spacious bird cage'), +('Fish Tank 20 Gallon', 80.00, 4, 'Complete aquarium kit'), +('Hamster Wheel', 15.00, 5, 'Exercise wheel for small pets'), +('Organic Dog Treats', 25.00, 1, 'Natural dog treats'); + +INSERT INTO productSupplier (supId, prodId, cost) +VALUES +(1, 1, 35.00), +(1, 2, 6.50), +(2, 2, 7.00), +(3, 3, 90.00), +(3, 4, 60.00), +(4, 5, 10.00), +(5, 6, 18.00), +(1, 6, 17.50); + +INSERT INTO inventory (prodId, quantity) +VALUES +(1, 100), +(2, 200), +(3, 50), +(4, 30), +(5, 150), +(6, 75); + +INSERT INTO service (serviceName, serviceDesc, serviceDuration, servicePrice) +VALUES +('Pet Grooming', 'Full grooming service', 60, 40.00), +('Nail Trimming', 'Quick nail trim', 15, 10.00), +('Bath and Brush', 'Bathing and brushing service', 45, 30.00), +('Veterinary Checkup', 'Complete health examination', 30, 75.00), +('Teeth Cleaning', 'Professional dental cleaning', 90, 100.00); + +INSERT INTO appointment (serviceId, customerId, appointmentDate, appointmentTime, appointmentStatus) +VALUES +(1, 2, '2026-02-01', '10:30:00', 'Booked'), +(2, 1, '2026-02-03', '14:00:00', 'Booked'), +(3, 3, '2026-02-05', '09:00:00', 'Completed'), +(4, 4, '2026-02-07', '11:30:00', 'Booked'), +(5, 5, '2026-02-10', '15:00:00', 'Cancelled'); + +INSERT INTO appointmentPet (appointmentId, petId) +VALUES +(1, 2), +(2, 1), +(3, 3), +(4, 5), +(5, 6); + +INSERT INTO sale (saleDate, totalAmount, paymentMethod, employeeId, storeId) +VALUES +-- January Sales +('2026-01-05 09:15:00', 125.00, 'Card', 1, 1), -- Sale 1: Dog food + treats +('2026-01-08 11:30:00', 200.00, 'Card', 2, 1), -- Sale 2: Bird cage + fish tank +('2026-01-12 14:20:00', 60.00, 'Cash', 3, 2), -- Sale 3: Cat toys + hamster wheel +('2026-01-15 10:45:00', 150.00, 'Debit', 1, 1), -- Sale 4: Dog food (bulk purchase) +('2026-01-18 16:30:00', 80.00, 'Card', 4, 3), -- Sale 5: Fish tank +('2026-01-22 13:15:00', 95.00, 'Cash', 2, 2), -- Sale 6: Mixed items +('2026-01-25 15:40:00', 240.00, 'Card', 5, 4), -- Sale 7: Two bird cages +('2026-01-28 10:30:00', 80.00, 'Cash', 1, 1), -- Sale 8: Dog food + cat toys +-- February Sales +('2026-02-01 09:00:00', 175.00, 'Card', 3, 3), -- Sale 9: Dog food + treats (bulk) +('2026-02-03 11:20:00', 120.00, 'Card', 2, 1), -- Sale 10: Bird cage +('2026-02-05 14:50:00', 45.00, 'Cash', 4, 2), -- Sale 11: Hamster wheel + cat toys +('2026-02-08 16:15:00', 160.00, 'Debit', 1, 1), -- Sale 12: Fish tank + accessories +('2026-02-10 10:25:00', 100.00, 'Card', 5, 4), -- Sale 13: Dog treats (bulk) +('2026-02-12 13:45:00', 50.00, 'Cash', 2, 2), -- Sale 14: Dog food +('2026-02-15 15:30:00', 85.00, 'Card', 3, 3), -- Sale 15: Mixed pet supplies +('2026-02-18 11:10:00', 200.00, 'Card', 1, 1), -- Sale 16: Bird cage + hamster wheel +('2026-02-20 14:35:00', 155.00, 'Debit', 4, 3), -- Sale 17: Fish tank + cat toys +('2026-02-22 16:50:00', 75.00, 'Cash', 2, 1), -- Sale 18: Dog treats + toys +('2026-02-24 10:15:00', 140.00, 'Card', 5, 4), -- Sale 19: Dog food + treats +(NOW(), 95.00, 'Card', 1, 1); -- Sale 20: Recent sale (current timestamp) + +INSERT INTO saleItem (saleId, prodId, quantity, unitPrice) +VALUES +-- Sale 1 items (Dog food + treats) +(1, 1, 2, 50.00), -- 2x Premium Dog Food +(1, 6, 1, 25.00), -- 1x Organic Dog Treats +-- Sale 2 items (Bird cage + fish tank) +(2, 3, 1, 120.00), -- 1x Bird Cage Large +(2, 4, 1, 80.00), -- 1x Fish Tank 20 Gallon +-- Sale 3 items (Cat toys + hamster wheel) +(3, 2, 3, 10.00), -- 3x Cat Toy Ball +(3, 5, 2, 15.00), -- 2x Hamster Wheel +-- Sale 4 items (Dog food bulk) +(4, 1, 3, 50.00), -- 3x Premium Dog Food +-- Sale 5 items (Fish tank) +(5, 4, 1, 80.00), -- 1x Fish Tank 20 Gallon +-- Sale 6 items (Mixed) +(6, 2, 4, 10.00), -- 4x Cat Toy Ball +(6, 5, 1, 15.00), -- 1x Hamster Wheel +(6, 6, 1, 25.00), -- 1x Organic Dog Treats +(6, 1, 1, 50.00), -- 1x Premium Dog Food (partial - discount applied) +-- Sale 7 items (Two bird cages) +(7, 3, 2, 120.00), -- 2x Bird Cage Large +-- Sale 8 items (Dog food + cat toys) +(8, 1, 1, 50.00), -- 1x Premium Dog Food +(8, 2, 3, 10.00), -- 3x Cat Toy Ball +-- Sale 9 items (Dog food + treats bulk) +(9, 1, 3, 50.00), -- 3x Premium Dog Food +(9, 6, 1, 25.00), -- 1x Organic Dog Treats +-- Sale 10 items (Bird cage) +(10, 3, 1, 120.00), -- 1x Bird Cage Large +-- Sale 11 items (Hamster wheel + cat toys) +(11, 5, 1, 15.00), -- 1x Hamster Wheel +(11, 2, 3, 10.00), -- 3x Cat Toy Ball +-- Sale 12 items (Fish tank + accessories) +(12, 4, 2, 80.00), -- 2x Fish Tank 20 Gallon +-- Sale 13 items (Dog treats bulk) +(13, 6, 4, 25.00), -- 4x Organic Dog Treats +-- Sale 14 items (Dog food) +(14, 1, 1, 50.00), -- 1x Premium Dog Food +-- Sale 15 items (Mixed supplies) +(15, 2, 2, 10.00), -- 2x Cat Toy Ball +(15, 5, 1, 15.00), -- 1x Hamster Wheel +(15, 6, 2, 25.00), -- 2x Organic Dog Treats +-- Sale 16 items (Bird cage + hamster wheel) +(16, 3, 1, 120.00), -- 1x Bird Cage Large +(16, 4, 1, 80.00), -- 1x Fish Tank 20 Gallon +-- Sale 17 items (Fish tank + cat toys) +(17, 4, 1, 80.00), -- 1x Fish Tank 20 Gallon +(17, 1, 1, 50.00), -- 1x Premium Dog Food +(17, 6, 1, 25.00), -- 1x Organic Dog Treats +-- Sale 18 items (Dog treats + toys) +(18, 6, 2, 25.00), -- 2x Organic Dog Treats +(18, 2, 2, 10.00), -- 2x Cat Toy Ball +(18, 5, 1, 15.00), -- 1x Hamster Wheel +-- Sale 19 items (Dog food + treats) +(19, 1, 2, 50.00), -- 2x Premium Dog Food +(19, 6, 2, 25.00), -- 2x Organic Dog Treats (discount applied) +-- Sale 20 items (Recent sale) +(20, 2, 5, 10.00), -- 5x Cat Toy Ball +(20, 5, 3, 15.00); -- 3x Hamster Wheel + +INSERT INTO purchaseOrder (supId, orderDate, status) +VALUES +(1, '2025-01-15', 'Delivered'), +(2, '2025-01-20', 'Pending'), +(3, '2025-02-01', 'Delivered'), +(4, '2025-02-10', 'In Transit'), +(1, '2025-02-15', 'Pending'); + +INSERT INTO activityLog (employeeId, activity) +VALUES +(1, 'Created new sale'), +(2, 'Booked appointment'), +(3, 'Completed grooming service'), +(4, 'Processed inventory order'), +(5, 'Conducted health checkup'), +(1, 'Updated customer information'); \ No newline at end of file diff --git a/sql/01_petstore_init.sql b/sql/01_petstore_init.sql new file mode 100644 index 00000000..65d708c4 --- /dev/null +++ b/sql/01_petstore_init.sql @@ -0,0 +1,410 @@ +DROP DATABASE IF EXISTS Petstoredb; +CREATE DATABASE Petstoredb; +USE Petstoredb; + +-- Create Tables + +CREATE TABLE storeLocation ( + storeId INT AUTO_INCREMENT PRIMARY KEY, + storeName VARCHAR(100) NOT NULL, + address VARCHAR(255) NOT NULL, + phone VARCHAR(20) NOT NULL, + email VARCHAR(100) NOT NULL +); + +CREATE TABLE employee ( + employeeId INT AUTO_INCREMENT PRIMARY KEY, + firstName VARCHAR(50) NOT NULL, + lastName VARCHAR(50) NOT NULL, + email VARCHAR(100) NOT NULL, + phone VARCHAR(20) NOT NULL, + role VARCHAR(50) NOT NULL, + isActive BOOLEAN DEFAULT TRUE NOT NULL +); + +CREATE TABLE employeeStore ( + employeeId INT NOT NULL, + storeId INT NOT NULL, + PRIMARY KEY (employeeId, storeId), + FOREIGN KEY (employeeId) REFERENCES employee(employeeId), + FOREIGN KEY (storeId) REFERENCES storeLocation(storeId) +); + +CREATE TABLE customer ( + customerId INT AUTO_INCREMENT PRIMARY KEY, + firstName VARCHAR(50) NOT NULL, + lastName VARCHAR(50) NOT NULL, + email VARCHAR(100) NOT NULL, + phone VARCHAR(20) NOT NULL +); + +CREATE TABLE pet ( + petId INT AUTO_INCREMENT PRIMARY KEY, + petName VARCHAR(50) NOT NULL, + petSpecies VARCHAR(50) NOT NULL, + petBreed VARCHAR(50) NOT NULL, + petAge INT NOT NULL, + petStatus VARCHAR(20) NOT NULL, + petPrice DECIMAL(10, 2) NOT NULL +); + +CREATE TABLE adoption ( + adoptionId INT AUTO_INCREMENT PRIMARY KEY, + petId INT NOT NULL, + customerId INT NOT NULL, + adoptionDate DATE NOT NULL, + adoptionStatus VARCHAR(20) NOT NULL, + FOREIGN KEY (petId) REFERENCES pet(petId), + FOREIGN KEY (customerId) REFERENCES customer(customerId) +); + +CREATE TABLE supplier ( + supId INT AUTO_INCREMENT PRIMARY KEY, + supCompany VARCHAR(100) NOT NULL, + supContactFirstName VARCHAR(50) NOT NULL, + supContactLastName VARCHAR(50) NOT NULL, + supEmail VARCHAR(100) NOT NULL, + supPhone VARCHAR(20) NOT NULL +); + +CREATE TABLE category ( + categoryId INT AUTO_INCREMENT PRIMARY KEY, + categoryName VARCHAR(100) NOT NULL, + categoryType VARCHAR(50) NOT NULL +); + +CREATE TABLE product ( + prodId INT AUTO_INCREMENT PRIMARY KEY, + prodName VARCHAR(100) NOT NULL, + prodPrice DECIMAL(10, 2) NOT NULL, + categoryId INT NOT NULL, + prodDesc TEXT, + FOREIGN KEY (categoryId) REFERENCES category(categoryId) +); + +CREATE TABLE productSupplier ( + supId INT NOT NULL, + prodId INT NOT NULL, + cost DECIMAL(10, 2) NOT NULL, + PRIMARY KEY (supId, prodId), + FOREIGN KEY (supId) REFERENCES supplier(supId), + FOREIGN KEY (prodId) REFERENCES product(prodId) +); + +CREATE TABLE inventory ( + inventoryId INT AUTO_INCREMENT PRIMARY KEY, + prodId INT NOT NULL, + quantity INT DEFAULT 0 NOT NULL, + FOREIGN KEY (prodId) REFERENCES product(prodId) +); + +CREATE TABLE service ( + serviceId INT AUTO_INCREMENT PRIMARY KEY, + serviceName VARCHAR(100) NOT NULL, + serviceDesc TEXT, + serviceDuration INT NOT NULL, + servicePrice DECIMAL(10, 2) NOT NULL +); + +CREATE TABLE appointment ( + appointmentId INT AUTO_INCREMENT PRIMARY KEY, + serviceId INT NOT NULL, + customerId INT NOT NULL, + appointmentDate DATE NOT NULL, + appointmentTime TIME NOT NULL, + appointmentStatus VARCHAR(20) NOT NULL, + FOREIGN KEY (serviceId) REFERENCES service(serviceId), + FOREIGN KEY (customerId) REFERENCES customer(customerId) +); + +CREATE TABLE appointmentPet ( + appointmentId INT NOT NULL, + petId INT NOT NULL, + PRIMARY KEY (appointmentId, petId), + FOREIGN KEY (appointmentId) REFERENCES appointment(appointmentId), + FOREIGN KEY (petId) REFERENCES pet(petId) +); + +CREATE TABLE sale ( + saleId INT AUTO_INCREMENT PRIMARY KEY, + saleDate DATETIME NOT NULL, + totalAmount DECIMAL(10, 2) NOT NULL, + paymentMethod VARCHAR(50) NOT NULL, + employeeId INT NOT NULL, + storeId INT NOT NULL, + isRefund BOOLEAN DEFAULT FALSE NOT NULL, + originalSaleId INT NULL, + FOREIGN KEY (employeeId) REFERENCES employee(employeeId), + FOREIGN KEY (storeId) REFERENCES storeLocation(storeId), + FOREIGN KEY (originalSaleId) REFERENCES sale(saleId) +); + +CREATE TABLE saleItem ( + saleItemId INT AUTO_INCREMENT PRIMARY KEY, + saleId INT NOT NULL, + prodId INT NOT NULL, + quantity INT NOT NULL, + unitPrice DECIMAL(10, 2) NOT NULL, + FOREIGN KEY (saleId) REFERENCES sale(saleId), + FOREIGN KEY (prodId) REFERENCES product(prodId) +); + +CREATE TABLE purchaseOrder ( + purchaseOrderId INT AUTO_INCREMENT PRIMARY KEY, + supId INT NOT NULL, + orderDate DATE NOT NULL, + status VARCHAR(50) NOT NULL, + FOREIGN KEY (supId) REFERENCES supplier(supId) +); + +CREATE TABLE activityLog ( + logId INT AUTO_INCREMENT PRIMARY KEY, + employeeId INT NOT NULL, + activity TEXT NOT NULL, + logTimestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, + FOREIGN KEY (employeeId) REFERENCES employee(employeeId) +); + +-- Authentication table (not in desktop schema) +CREATE TABLE users ( + userId INT AUTO_INCREMENT PRIMARY KEY, + username VARCHAR(50) NOT NULL UNIQUE, + password VARCHAR(255) NOT NULL, + role VARCHAR(20) NOT NULL, + createdAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +); + +-- Insert Sample Data + +INSERT INTO storeLocation (storeName, address, phone, email) +VALUES +('Downtown Branch', '123 Main St', '123-456-7890', 'downtown@petshop.com'), +('North Branch', '456 North Ave', '987-654-3210', 'north@petshop.com'), +('West Side Store', '789 West Blvd', '555-123-4567', 'westside@petshop.com'), +('East End Shop', '321 East Road', '555-987-6543', 'eastend@petshop.com'), +('South Mall Location', '654 South Plaza', '555-246-8135', 'southmall@petshop.com'); + +INSERT INTO employee (firstName, lastName, email, phone, role, isActive) +VALUES +('John', 'Doe', 'john@petshop.com', '111-222-3333', 'Manager', TRUE), +('Sara', 'Smith', 'sara@petshop.com', '444-555-6666', 'Staff', TRUE), +('Michael', 'Johnson', 'michael@petshop.com', '222-333-4444', 'Groomer', TRUE), +('Lisa', 'Williams', 'lisa@petshop.com', '333-444-5555', 'Staff', TRUE), +('David', 'Brown', 'david@petshop.com', '555-666-7777', 'Veterinarian', TRUE), +('Emma', 'Davis', 'emma@petshop.com', '666-777-8888', 'Manager', FALSE); + +INSERT INTO employeeStore (employeeId, storeId) +VALUES +(1, 1), +(2, 1), +(2, 2), +(3, 2), +(4, 3), +(5, 1), +(5, 4), +(6, 5); + +INSERT INTO customer (firstName, lastName, email, phone) +VALUES +('Alex', 'Brown', 'alex@gmail.com', '777-888-9999'), +('Emily', 'Clark', 'emily@gmail.com', '666-555-4444'), +('James', 'Wilson', 'james@gmail.com', '888-999-0000'), +('Olivia', 'Martinez', 'olivia@gmail.com', '999-000-1111'), +('William', 'Anderson', 'william@gmail.com', '000-111-2222'), +('Sophia', 'Taylor', 'sophia@gmail.com', '111-222-3333'); + +INSERT INTO pet (petName, petSpecies, petBreed, petAge, petStatus, petPrice) +VALUES +('Buddy', 'Dog', 'Labrador', 2, 'Available', 500.00), +('Milo', 'Cat', 'Persian', 1, 'Available', 300.00), +('Charlie', 'Dog', 'Golden Retriever', 3, 'Available', 550.00), +('Luna', 'Cat', 'Siamese', 2, 'Adopted', 350.00), +('Max', 'Dog', 'Beagle', 1, 'Available', 450.00), +('Bella', 'Cat', 'Maine Coon', 4, 'Available', 400.00); + +INSERT INTO adoption (petId, customerId, adoptionDate, adoptionStatus) +VALUES +(1, 1, '2026-01-15', 'Completed'), +(4, 3, '2026-01-20', 'Completed'), +(2, 2, '2026-01-25', 'Pending'), +(5, 4, '2026-02-01', 'Completed'), +(6, 5, '2026-02-02', 'Pending'); + +INSERT INTO supplier (supCompany, supContactFirstName, supContactLastName, supEmail, supPhone) +VALUES +('PetFood Inc', 'Robert', 'King', 'contact@petfood.com', '888-111-2222'), +('Toy World', 'Jennifer', 'Lee', 'sales@toyworld.com', '888-222-3333'), +('Pet Supplies Co', 'Kevin', 'White', 'info@petsupplies.com', '888-333-4444'), +('Animal Care Products', 'Nancy', 'Green', 'orders@animalcare.com', '888-444-5555'), +('Premium Pet Goods', 'Tom', 'Black', 'support@premiumpet.com', '888-555-6666'); + +INSERT INTO category (categoryName, categoryType) +VALUES +('Dog Food', 'Product'), +('Cat Toys', 'Product'), +('Bird Supplies', 'Product'), +('Aquarium', 'Product'), +('Small Animals', 'Product'); + +INSERT INTO product (prodName, prodPrice, categoryId, prodDesc) +VALUES +('Premium Dog Food', 50.00, 1, 'High quality dog food'), +('Cat Toy Ball', 10.00, 2, 'Colorful toy for cats'), +('Bird Cage Large', 120.00, 3, 'Spacious bird cage'), +('Fish Tank 20 Gallon', 80.00, 4, 'Complete aquarium kit'), +('Hamster Wheel', 15.00, 5, 'Exercise wheel for small pets'), +('Organic Dog Treats', 25.00, 1, 'Natural dog treats'); + +INSERT INTO productSupplier (supId, prodId, cost) +VALUES +(1, 1, 35.00), +(1, 2, 6.50), +(2, 2, 7.00), +(3, 3, 90.00), +(3, 4, 60.00), +(4, 5, 10.00), +(5, 6, 18.00), +(1, 6, 17.50); + +INSERT INTO inventory (prodId, quantity) +VALUES +(1, 100), +(2, 200), +(3, 50), +(4, 30), +(5, 150), +(6, 75); + +INSERT INTO service (serviceName, serviceDesc, serviceDuration, servicePrice) +VALUES +('Pet Grooming', 'Full grooming service', 60, 40.00), +('Nail Trimming', 'Quick nail trim', 15, 10.00), +('Bath and Brush', 'Bathing and brushing service', 45, 30.00), +('Veterinary Checkup', 'Complete health examination', 30, 75.00), +('Teeth Cleaning', 'Professional dental cleaning', 90, 100.00); + +INSERT INTO appointment (serviceId, customerId, appointmentDate, appointmentTime, appointmentStatus) +VALUES +(1, 2, '2026-02-01', '10:30:00', 'Booked'), +(2, 1, '2026-02-03', '14:00:00', 'Booked'), +(3, 3, '2026-02-05', '09:00:00', 'Completed'), +(4, 4, '2026-02-07', '11:30:00', 'Booked'), +(5, 5, '2026-02-10', '15:00:00', 'Cancelled'); + +INSERT INTO appointmentPet (appointmentId, petId) +VALUES +(1, 2), +(2, 1), +(3, 3), +(4, 5), +(5, 6); + +INSERT INTO sale (saleDate, totalAmount, paymentMethod, employeeId, storeId) +VALUES +-- January Sales +('2026-01-05 09:15:00', 125.00, 'Card', 1, 1), -- Sale 1: Dog food + treats +('2026-01-08 11:30:00', 200.00, 'Card', 2, 1), -- Sale 2: Bird cage + fish tank +('2026-01-12 14:20:00', 60.00, 'Cash', 3, 2), -- Sale 3: Cat toys + hamster wheel +('2026-01-15 10:45:00', 150.00, 'Debit', 1, 1), -- Sale 4: Dog food (bulk purchase) +('2026-01-18 16:30:00', 80.00, 'Card', 4, 3), -- Sale 5: Fish tank +('2026-01-22 13:15:00', 95.00, 'Cash', 2, 2), -- Sale 6: Mixed items +('2026-01-25 15:40:00', 240.00, 'Card', 5, 4), -- Sale 7: Two bird cages +('2026-01-28 10:30:00', 80.00, 'Cash', 1, 1), -- Sale 8: Dog food + cat toys +-- February Sales +('2026-02-01 09:00:00', 175.00, 'Card', 3, 3), -- Sale 9: Dog food + treats (bulk) +('2026-02-03 11:20:00', 120.00, 'Card', 2, 1), -- Sale 10: Bird cage +('2026-02-05 14:50:00', 45.00, 'Cash', 4, 2), -- Sale 11: Hamster wheel + cat toys +('2026-02-08 16:15:00', 160.00, 'Debit', 1, 1), -- Sale 12: Fish tank + accessories +('2026-02-10 10:25:00', 100.00, 'Card', 5, 4), -- Sale 13: Dog treats (bulk) +('2026-02-12 13:45:00', 50.00, 'Cash', 2, 2), -- Sale 14: Dog food +('2026-02-15 15:30:00', 85.00, 'Card', 3, 3), -- Sale 15: Mixed pet supplies +('2026-02-18 11:10:00', 200.00, 'Card', 1, 1), -- Sale 16: Bird cage + hamster wheel +('2026-02-20 14:35:00', 155.00, 'Debit', 4, 3), -- Sale 17: Fish tank + cat toys +('2026-02-22 16:50:00', 75.00, 'Cash', 2, 1), -- Sale 18: Dog treats + toys +('2026-02-24 10:15:00', 140.00, 'Card', 5, 4), -- Sale 19: Dog food + treats +(NOW(), 95.00, 'Card', 1, 1); -- Sale 20: Recent sale (current timestamp) + +INSERT INTO saleItem (saleId, prodId, quantity, unitPrice) +VALUES +-- Sale 1 items (Dog food + treats) +(1, 1, 2, 50.00), -- 2x Premium Dog Food +(1, 6, 1, 25.00), -- 1x Organic Dog Treats +-- Sale 2 items (Bird cage + fish tank) +(2, 3, 1, 120.00), -- 1x Bird Cage Large +(2, 4, 1, 80.00), -- 1x Fish Tank 20 Gallon +-- Sale 3 items (Cat toys + hamster wheel) +(3, 2, 3, 10.00), -- 3x Cat Toy Ball +(3, 5, 2, 15.00), -- 2x Hamster Wheel +-- Sale 4 items (Dog food bulk) +(4, 1, 3, 50.00), -- 3x Premium Dog Food +-- Sale 5 items (Fish tank) +(5, 4, 1, 80.00), -- 1x Fish Tank 20 Gallon +-- Sale 6 items (Mixed) +(6, 2, 4, 10.00), -- 4x Cat Toy Ball +(6, 5, 1, 15.00), -- 1x Hamster Wheel +(6, 6, 1, 25.00), -- 1x Organic Dog Treats +(6, 1, 1, 50.00), -- 1x Premium Dog Food (partial - discount applied) +-- Sale 7 items (Two bird cages) +(7, 3, 2, 120.00), -- 2x Bird Cage Large +-- Sale 8 items (Dog food + cat toys) +(8, 1, 1, 50.00), -- 1x Premium Dog Food +(8, 2, 3, 10.00), -- 3x Cat Toy Ball +-- Sale 9 items (Dog food + treats bulk) +(9, 1, 3, 50.00), -- 3x Premium Dog Food +(9, 6, 1, 25.00), -- 1x Organic Dog Treats +-- Sale 10 items (Bird cage) +(10, 3, 1, 120.00), -- 1x Bird Cage Large +-- Sale 11 items (Hamster wheel + cat toys) +(11, 5, 1, 15.00), -- 1x Hamster Wheel +(11, 2, 3, 10.00), -- 3x Cat Toy Ball +-- Sale 12 items (Fish tank + accessories) +(12, 4, 2, 80.00), -- 2x Fish Tank 20 Gallon +-- Sale 13 items (Dog treats bulk) +(13, 6, 4, 25.00), -- 4x Organic Dog Treats +-- Sale 14 items (Dog food) +(14, 1, 1, 50.00), -- 1x Premium Dog Food +-- Sale 15 items (Mixed supplies) +(15, 2, 2, 10.00), -- 2x Cat Toy Ball +(15, 5, 1, 15.00), -- 1x Hamster Wheel +(15, 6, 2, 25.00), -- 2x Organic Dog Treats +-- Sale 16 items (Bird cage + hamster wheel) +(16, 3, 1, 120.00), -- 1x Bird Cage Large +(16, 4, 1, 80.00), -- 1x Fish Tank 20 Gallon +-- Sale 17 items (Fish tank + cat toys) +(17, 4, 1, 80.00), -- 1x Fish Tank 20 Gallon +(17, 1, 1, 50.00), -- 1x Premium Dog Food +(17, 6, 1, 25.00), -- 1x Organic Dog Treats +-- Sale 18 items (Dog treats + toys) +(18, 6, 2, 25.00), -- 2x Organic Dog Treats +(18, 2, 2, 10.00), -- 2x Cat Toy Ball +(18, 5, 1, 15.00), -- 1x Hamster Wheel +-- Sale 19 items (Dog food + treats) +(19, 1, 2, 50.00), -- 2x Premium Dog Food +(19, 6, 2, 25.00), -- 2x Organic Dog Treats (discount applied) +-- Sale 20 items (Recent sale) +(20, 2, 5, 10.00), -- 5x Cat Toy Ball +(20, 5, 3, 15.00); -- 3x Hamster Wheel + +INSERT INTO purchaseOrder (supId, orderDate, status) +VALUES +(1, '2025-01-15', 'Delivered'), +(2, '2025-01-20', 'Pending'), +(3, '2025-02-01', 'Delivered'), +(4, '2025-02-10', 'In Transit'), +(1, '2025-02-15', 'Pending'); + +INSERT INTO activityLog (employeeId, activity) +VALUES +(1, 'Created new sale'), +(2, 'Booked appointment'), +(3, 'Completed grooming service'), +(4, 'Processed inventory order'), +(5, 'Conducted health checkup'), +(1, 'Updated customer information'); + +-- Sample users for authentication (passwords are bcrypt hashes of "password123") +INSERT INTO users (username, password, role) +VALUES +('admin', '$2a$10$8.UnVuG9HHgffUDAlk8qfOuVGkqRzgVymGe07xd00DMxs.AQubh4a', 'ADMIN'), +('staff', '$2a$10$8.UnVuG9HHgffUDAlk8qfOuVGkqRzgVymGe07xd00DMxs.AQubh4a', 'STAFF'); From 90197ededd4658aee336702f2bc4308d9a106876 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Thu, 5 Mar 2026 16:39:57 -0700 Subject: [PATCH 24/84] Align backend schema with desktop application --- docker-compose.yml | 1 + .../backend/controller/AuthController.java | 35 --- .../backend/controller/RefundController.java | 27 --- .../backend/dto/auth/RegisterRequest.java | 91 ------- .../backend/dto/refund/RefundItemRequest.java | 52 ---- .../backend/dto/refund/RefundRequest.java | 52 ---- .../backend/dto/refund/RefundResponse.java | 227 ------------------ .../petshop/backend/entity/ActivityLog.java | 90 +++++++ .../com/petshop/backend/entity/Adoption.java | 57 ++--- .../petshop/backend/entity/Appointment.java | 70 ++---- .../com/petshop/backend/entity/Category.java | 40 +-- .../com/petshop/backend/entity/Customer.java | 86 +++---- .../com/petshop/backend/entity/Employee.java | 158 ++++++++++++ .../petshop/backend/entity/EmployeeStore.java | 148 ++++++++++++ .../com/petshop/backend/entity/Inventory.java | 66 +---- .../java/com/petshop/backend/entity/Pet.java | 47 ++-- .../com/petshop/backend/entity/Product.java | 85 +++---- .../backend/entity/ProductSupplier.java | 50 +--- .../petshop/backend/entity/PurchaseOrder.java | 93 ++----- .../com/petshop/backend/entity/Refund.java | 148 ------------ .../petshop/backend/entity/RefundItem.java | 104 -------- .../java/com/petshop/backend/entity/Sale.java | 126 ++++------ .../com/petshop/backend/entity/SaleItem.java | 41 ++-- .../com/petshop/backend/entity/Service.java | 69 +++--- .../petshop/backend/entity/StoreLocation.java | 132 ++++++++++ .../com/petshop/backend/entity/Supplier.java | 115 ++++----- .../java/com/petshop/backend/entity/User.java | 2 +- .../backend/repository/RefundRepository.java | 9 - .../backend/security/SecurityConfig.java | 2 +- .../backend/service/RefundService.java | 121 ---------- src/main/resources/application.yml | 2 +- 31 files changed, 885 insertions(+), 1461 deletions(-) delete mode 100644 src/main/java/com/petshop/backend/controller/RefundController.java delete mode 100644 src/main/java/com/petshop/backend/dto/auth/RegisterRequest.java delete mode 100644 src/main/java/com/petshop/backend/dto/refund/RefundItemRequest.java delete mode 100644 src/main/java/com/petshop/backend/dto/refund/RefundRequest.java delete mode 100644 src/main/java/com/petshop/backend/dto/refund/RefundResponse.java create mode 100644 src/main/java/com/petshop/backend/entity/ActivityLog.java create mode 100644 src/main/java/com/petshop/backend/entity/Employee.java create mode 100644 src/main/java/com/petshop/backend/entity/EmployeeStore.java delete mode 100644 src/main/java/com/petshop/backend/entity/Refund.java delete mode 100644 src/main/java/com/petshop/backend/entity/RefundItem.java create mode 100644 src/main/java/com/petshop/backend/entity/StoreLocation.java delete mode 100644 src/main/java/com/petshop/backend/repository/RefundRepository.java delete mode 100644 src/main/java/com/petshop/backend/service/RefundService.java diff --git a/docker-compose.yml b/docker-compose.yml index f82b2d8e..6eb0cf4e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,6 +11,7 @@ services: - "3306:3306" volumes: - db_data:/var/lib/mysql + - ./sql:/docker-entrypoint-initdb.d healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-uroot", "-proot"] interval: 5s diff --git a/src/main/java/com/petshop/backend/controller/AuthController.java b/src/main/java/com/petshop/backend/controller/AuthController.java index 491c4833..2b1faa98 100644 --- a/src/main/java/com/petshop/backend/controller/AuthController.java +++ b/src/main/java/com/petshop/backend/controller/AuthController.java @@ -2,7 +2,6 @@ package com.petshop.backend.controller; import com.petshop.backend.dto.auth.LoginRequest; import com.petshop.backend.dto.auth.LoginResponse; -import com.petshop.backend.dto.auth.RegisterRequest; import com.petshop.backend.dto.auth.UserInfoResponse; import com.petshop.backend.entity.User; import com.petshop.backend.repository.UserRepository; @@ -39,40 +38,6 @@ public class AuthController { this.passwordEncoder = passwordEncoder; } - @PostMapping("/register") - public ResponseEntity register(@Valid @RequestBody RegisterRequest request) { - if (userRepository.findByUsername(request.getEmail()).isPresent()) { - Map error = new HashMap<>(); - error.put("message", "Email already registered"); - return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error); - } - - User user = new User(); - user.setUsername(request.getEmail()); - user.setEmail(request.getEmail()); - user.setPassword(passwordEncoder.encode(request.getPassword())); - user.setFullName(request.getFirstName() + " " + request.getLastName()); - user.setRole(User.Role.CUSTOMER); - user.setActive(true); - - user = userRepository.save(user); - - UserDetails userDetails = new org.springframework.security.core.userdetails.User( - user.getUsername(), - user.getPassword(), - java.util.Collections.emptyList() - ); - - String token = jwtUtil.generateToken(userDetails); - - return ResponseEntity.status(HttpStatus.CREATED).body(new LoginResponse( - token, - user.getUsername(), - user.getFullName(), - user.getRole().name() - )); - } - @PostMapping("/login") public ResponseEntity login(@Valid @RequestBody LoginRequest request) { try { diff --git a/src/main/java/com/petshop/backend/controller/RefundController.java b/src/main/java/com/petshop/backend/controller/RefundController.java deleted file mode 100644 index 1086b9af..00000000 --- a/src/main/java/com/petshop/backend/controller/RefundController.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.petshop.backend.controller; - -import com.petshop.backend.dto.refund.RefundRequest; -import com.petshop.backend.dto.refund.RefundResponse; -import com.petshop.backend.service.RefundService; -import jakarta.validation.Valid; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -@RestController -@RequestMapping("/api/v1/sales") -public class RefundController { - - private final RefundService refundService; - - public RefundController(RefundService refundService) { - this.refundService = refundService; - } - - @PostMapping("/{saleId}/refunds") - public ResponseEntity createRefund( - @PathVariable Long saleId, - @Valid @RequestBody RefundRequest request) { - return ResponseEntity.status(HttpStatus.CREATED).body(refundService.createRefund(saleId, request)); - } -} diff --git a/src/main/java/com/petshop/backend/dto/auth/RegisterRequest.java b/src/main/java/com/petshop/backend/dto/auth/RegisterRequest.java deleted file mode 100644 index ae3f5a56..00000000 --- a/src/main/java/com/petshop/backend/dto/auth/RegisterRequest.java +++ /dev/null @@ -1,91 +0,0 @@ -package com.petshop.backend.dto.auth; - -import jakarta.validation.constraints.Email; -import jakarta.validation.constraints.NotBlank; -import java.util.Objects; - -public class RegisterRequest { - @NotBlank(message = "First name is required") - private String firstName; - - @NotBlank(message = "Last name is required") - private String lastName; - - @NotBlank(message = "Email is required") - @Email(message = "Email must be valid") - private String email; - - @NotBlank(message = "Phone is required") - private String phone; - - @NotBlank(message = "Password is required") - private String password; - - public String getFirstName() { - return firstName; - } - - public void setFirstName(String firstName) { - this.firstName = firstName; - } - - public String getLastName() { - return lastName; - } - - public void setLastName(String lastName) { - this.lastName = lastName; - } - - public String getEmail() { - return email; - } - - public void setEmail(String email) { - this.email = email; - } - - public String getPhone() { - return phone; - } - - public void setPhone(String phone) { - this.phone = phone; - } - - public String getPassword() { - return password; - } - - public void setPassword(String password) { - this.password = password; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - RegisterRequest that = (RegisterRequest) o; - return Objects.equals(firstName, that.firstName) && - Objects.equals(lastName, that.lastName) && - Objects.equals(email, that.email) && - Objects.equals(phone, that.phone) && - Objects.equals(password, that.password); - } - - @Override - public int hashCode() { - return Objects.hash(firstName, lastName, email, phone, password); - } - - @Override - public String toString() { - return "RegisterRequest{" + - "firstName='" + firstName + '\'' + - ", lastName='" + lastName + '\'' + - ", email='" + email + '\'' + - ", phone='" + phone + '\'' + - ", password='[PROTECTED]'" + - '}'; - } -} diff --git a/src/main/java/com/petshop/backend/dto/refund/RefundItemRequest.java b/src/main/java/com/petshop/backend/dto/refund/RefundItemRequest.java deleted file mode 100644 index 5c28c3f3..00000000 --- a/src/main/java/com/petshop/backend/dto/refund/RefundItemRequest.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.petshop.backend.dto.refund; - -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Positive; -import java.util.Objects; - -public class RefundItemRequest { - @NotNull(message = "Sale item ID is required") - private Long saleItemId; - - @NotNull(message = "Quantity is required") - @Positive(message = "Quantity must be positive") - private Integer quantity; - - public Long getSaleItemId() { - return saleItemId; - } - - public void setSaleItemId(Long saleItemId) { - this.saleItemId = saleItemId; - } - - public Integer getQuantity() { - return quantity; - } - - public void setQuantity(Integer quantity) { - this.quantity = quantity; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - RefundItemRequest that = (RefundItemRequest) o; - return Objects.equals(saleItemId, that.saleItemId) && - Objects.equals(quantity, that.quantity); - } - - @Override - public int hashCode() { - return Objects.hash(saleItemId, quantity); - } - - @Override - public String toString() { - return "RefundItemRequest{" + - "saleItemId=" + saleItemId + - ", quantity=" + quantity + - '}'; - } -} diff --git a/src/main/java/com/petshop/backend/dto/refund/RefundRequest.java b/src/main/java/com/petshop/backend/dto/refund/RefundRequest.java deleted file mode 100644 index 154e838f..00000000 --- a/src/main/java/com/petshop/backend/dto/refund/RefundRequest.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.petshop.backend.dto.refund; - -import jakarta.validation.Valid; -import jakarta.validation.constraints.NotEmpty; -import java.util.List; -import java.util.Objects; - -public class RefundRequest { - @NotEmpty(message = "At least one item is required") - @Valid - private List items; - - private String refundReason; - - public List getItems() { - return items; - } - - public void setItems(List items) { - this.items = items; - } - - public String getRefundReason() { - return refundReason; - } - - public void setRefundReason(String refundReason) { - this.refundReason = refundReason; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - RefundRequest that = (RefundRequest) o; - return Objects.equals(items, that.items) && - Objects.equals(refundReason, that.refundReason); - } - - @Override - public int hashCode() { - return Objects.hash(items, refundReason); - } - - @Override - public String toString() { - return "RefundRequest{" + - "items=" + items + - ", refundReason='" + refundReason + '\'' + - '}'; - } -} diff --git a/src/main/java/com/petshop/backend/dto/refund/RefundResponse.java b/src/main/java/com/petshop/backend/dto/refund/RefundResponse.java deleted file mode 100644 index a502fb25..00000000 --- a/src/main/java/com/petshop/backend/dto/refund/RefundResponse.java +++ /dev/null @@ -1,227 +0,0 @@ -package com.petshop.backend.dto.refund; - -import java.math.BigDecimal; -import java.time.LocalDateTime; -import java.util.List; -import java.util.Objects; - -public class RefundResponse { - private Long id; - private Long saleId; - private LocalDateTime refundDate; - private BigDecimal refundAmount; - private String refundReason; - private Long processedBy; - private String processedByName; - private List items; - private LocalDateTime createdAt; - - public RefundResponse() { - } - - public RefundResponse(Long id, Long saleId, LocalDateTime refundDate, BigDecimal refundAmount, String refundReason, Long processedBy, String processedByName, List items, LocalDateTime createdAt) { - this.id = id; - this.saleId = saleId; - this.refundDate = refundDate; - this.refundAmount = refundAmount; - this.refundReason = refundReason; - this.processedBy = processedBy; - this.processedByName = processedByName; - this.items = items; - this.createdAt = createdAt; - } - - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public Long getSaleId() { - return saleId; - } - - public void setSaleId(Long saleId) { - this.saleId = saleId; - } - - public LocalDateTime getRefundDate() { - return refundDate; - } - - public void setRefundDate(LocalDateTime refundDate) { - this.refundDate = refundDate; - } - - public BigDecimal getRefundAmount() { - return refundAmount; - } - - public void setRefundAmount(BigDecimal refundAmount) { - this.refundAmount = refundAmount; - } - - public String getRefundReason() { - return refundReason; - } - - public void setRefundReason(String refundReason) { - this.refundReason = refundReason; - } - - public Long getProcessedBy() { - return processedBy; - } - - public void setProcessedBy(Long processedBy) { - this.processedBy = processedBy; - } - - public String getProcessedByName() { - return processedByName; - } - - public void setProcessedByName(String processedByName) { - this.processedByName = processedByName; - } - - public List getItems() { - return items; - } - - public void setItems(List items) { - this.items = items; - } - - 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; - RefundResponse that = (RefundResponse) o; - return Objects.equals(id, that.id) && Objects.equals(saleId, that.saleId) && Objects.equals(refundDate, that.refundDate) && Objects.equals(refundAmount, that.refundAmount) && Objects.equals(refundReason, that.refundReason) && Objects.equals(processedBy, that.processedBy) && Objects.equals(processedByName, that.processedByName) && Objects.equals(items, that.items) && Objects.equals(createdAt, that.createdAt); - } - - @Override - public int hashCode() { - return Objects.hash(id, saleId, refundDate, refundAmount, refundReason, processedBy, processedByName, items, createdAt); - } - - @Override - public String toString() { - return "RefundResponse{" + - "id=" + id + - ", saleId=" + saleId + - ", refundDate=" + refundDate + - ", refundAmount=" + refundAmount + - ", refundReason='" + refundReason + '\'' + - ", processedBy=" + processedBy + - ", processedByName='" + processedByName + '\'' + - ", items=" + items + - ", createdAt=" + createdAt + - '}'; - } - - public static class RefundItemResponse { - private Long id; - private Long saleItemId; - private Long productId; - private String productName; - private Integer quantity; - private BigDecimal refundAmount; - - public RefundItemResponse() { - } - - public RefundItemResponse(Long id, Long saleItemId, Long productId, String productName, Integer quantity, BigDecimal refundAmount) { - this.id = id; - this.saleItemId = saleItemId; - this.productId = productId; - this.productName = productName; - this.quantity = quantity; - this.refundAmount = refundAmount; - } - - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public Long getSaleItemId() { - return saleItemId; - } - - public void setSaleItemId(Long saleItemId) { - this.saleItemId = saleItemId; - } - - public Long getProductId() { - return productId; - } - - public void setProductId(Long productId) { - this.productId = productId; - } - - public String getProductName() { - return productName; - } - - public void setProductName(String productName) { - this.productName = productName; - } - - public Integer getQuantity() { - return quantity; - } - - public void setQuantity(Integer quantity) { - this.quantity = quantity; - } - - public BigDecimal getRefundAmount() { - return refundAmount; - } - - public void setRefundAmount(BigDecimal refundAmount) { - this.refundAmount = refundAmount; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - RefundItemResponse that = (RefundItemResponse) o; - return Objects.equals(id, that.id) && Objects.equals(saleItemId, that.saleItemId) && Objects.equals(productId, that.productId) && Objects.equals(productName, that.productName) && Objects.equals(quantity, that.quantity) && Objects.equals(refundAmount, that.refundAmount); - } - - @Override - public int hashCode() { - return Objects.hash(id, saleItemId, productId, productName, quantity, refundAmount); - } - - @Override - public String toString() { - return "RefundItemResponse{" + - "id=" + id + - ", saleItemId=" + saleItemId + - ", productId=" + productId + - ", productName='" + productName + '\'' + - ", quantity=" + quantity + - ", refundAmount=" + refundAmount + - '}'; - } - } -} diff --git a/src/main/java/com/petshop/backend/entity/ActivityLog.java b/src/main/java/com/petshop/backend/entity/ActivityLog.java new file mode 100644 index 00000000..211f75de --- /dev/null +++ b/src/main/java/com/petshop/backend/entity/ActivityLog.java @@ -0,0 +1,90 @@ +package com.petshop.backend.entity; + +import jakarta.persistence.*; + +import java.time.LocalDateTime; +import java.util.Objects; + +@Entity +@Table(name = "activityLog") +public class ActivityLog { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long logId; + + @ManyToOne + @JoinColumn(name = "employeeId", nullable = false) + private Employee employee; + + @Column(nullable = false, columnDefinition = "TEXT") + private String activity; + + @Column(nullable = false) + private LocalDateTime logTimestamp = LocalDateTime.now(); + + public ActivityLog() { + } + + public ActivityLog(Long logId, Employee employee, String activity, LocalDateTime logTimestamp) { + this.logId = logId; + this.employee = employee; + this.activity = activity; + this.logTimestamp = logTimestamp; + } + + public Long getLogId() { + return logId; + } + + public void setLogId(Long logId) { + this.logId = logId; + } + + public Employee getEmployee() { + return employee; + } + + public void setEmployee(Employee employee) { + this.employee = employee; + } + + public String getActivity() { + return activity; + } + + public void setActivity(String activity) { + this.activity = activity; + } + + public LocalDateTime getLogTimestamp() { + return logTimestamp; + } + + public void setLogTimestamp(LocalDateTime logTimestamp) { + this.logTimestamp = logTimestamp; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ActivityLog that = (ActivityLog) o; + return Objects.equals(logId, that.logId); + } + + @Override + public int hashCode() { + return Objects.hash(logId); + } + + @Override + public String toString() { + return "ActivityLog{" + + "logId=" + logId + + ", employee=" + employee + + ", activity='" + activity + '\'' + + ", logTimestamp=" + logTimestamp + + '}'; + } +} diff --git a/src/main/java/com/petshop/backend/entity/Adoption.java b/src/main/java/com/petshop/backend/entity/Adoption.java index da005d5f..84912ba9 100644 --- a/src/main/java/com/petshop/backend/entity/Adoption.java +++ b/src/main/java/com/petshop/backend/entity/Adoption.java @@ -10,29 +10,26 @@ import java.time.LocalDateTime; import java.util.Objects; @Entity -@Table(name = "adoptions") +@Table(name = "adoption") public class Adoption { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; + private Long adoptionId; @ManyToOne - @JoinColumn(name = "pet_id", nullable = false) + @JoinColumn(name = "petId", nullable = false) private Pet pet; @ManyToOne - @JoinColumn(name = "customer_id", nullable = false) + @JoinColumn(name = "customerId", nullable = false) private Customer customer; - @Column(name = "adoption_date", nullable = false) + @Column(nullable = false) private LocalDate adoptionDate; - @Column(name = "adoption_fee", nullable = false, precision = 10, scale = 2) - private BigDecimal adoptionFee; - - @Column(columnDefinition = "TEXT") - private String notes; + @Column(nullable = false, length = 20) + private String adoptionStatus; @CreationTimestamp @Column(name = "created_at", updatable = false) @@ -45,23 +42,22 @@ public class Adoption { public Adoption() { } - public Adoption(Long id, Pet pet, Customer customer, LocalDate adoptionDate, BigDecimal adoptionFee, String notes, LocalDateTime createdAt, LocalDateTime updatedAt) { - this.id = id; + public Adoption(Long adoptionId, Pet pet, Customer customer, LocalDate adoptionDate, String adoptionStatus, LocalDateTime createdAt, LocalDateTime updatedAt) { + this.adoptionId = adoptionId; this.pet = pet; this.customer = customer; this.adoptionDate = adoptionDate; - this.adoptionFee = adoptionFee; - this.notes = notes; + this.adoptionStatus = adoptionStatus; this.createdAt = createdAt; this.updatedAt = updatedAt; } - public Long getId() { - return id; + public Long getAdoptionId() { + return adoptionId; } - public void setId(Long id) { - this.id = id; + public void setAdoptionId(Long adoptionId) { + this.adoptionId = adoptionId; } public Pet getPet() { @@ -88,20 +84,12 @@ public class Adoption { this.adoptionDate = adoptionDate; } - public BigDecimal getAdoptionFee() { - return adoptionFee; + public String getAdoptionStatus() { + return adoptionStatus; } - public void setAdoptionFee(BigDecimal adoptionFee) { - this.adoptionFee = adoptionFee; - } - - public String getNotes() { - return notes; - } - - public void setNotes(String notes) { - this.notes = notes; + public void setAdoptionStatus(String adoptionStatus) { + this.adoptionStatus = adoptionStatus; } public LocalDateTime getCreatedAt() { @@ -125,23 +113,22 @@ public class Adoption { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Adoption adoption = (Adoption) o; - return Objects.equals(id, adoption.id); + return Objects.equals(adoptionId, adoption.adoptionId); } @Override public int hashCode() { - return Objects.hash(id); + return Objects.hash(adoptionId); } @Override public String toString() { return "Adoption{" + - "id=" + id + + "adoptionId=" + adoptionId + ", pet=" + pet + ", customer=" + customer + ", adoptionDate=" + adoptionDate + - ", adoptionFee=" + adoptionFee + - ", notes='" + notes + '\'' + + ", adoptionStatus='" + adoptionStatus + '\'' + ", createdAt=" + createdAt + ", updatedAt=" + updatedAt + '}'; diff --git a/src/main/java/com/petshop/backend/entity/Appointment.java b/src/main/java/com/petshop/backend/entity/Appointment.java index 358c137b..c31a94ff 100644 --- a/src/main/java/com/petshop/backend/entity/Appointment.java +++ b/src/main/java/com/petshop/backend/entity/Appointment.java @@ -12,39 +12,35 @@ import java.util.Objects; import java.util.Set; @Entity -@Table(name = "appointments") +@Table(name = "appointment") public class Appointment { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; + private Long appointmentId; @ManyToOne - @JoinColumn(name = "customer_id", nullable = false) + @JoinColumn(name = "customerId", nullable = false) private Customer customer; @ManyToOne - @JoinColumn(name = "service_id", nullable = false) + @JoinColumn(name = "serviceId", nullable = false) private Service service; - @Column(name = "appointment_date", nullable = false) + @Column(nullable = false) private LocalDate appointmentDate; - @Column(name = "appointment_time", nullable = false) + @Column(nullable = false) private LocalTime appointmentTime; - @Enumerated(EnumType.STRING) - @Column(nullable = false) - private AppointmentStatus status = AppointmentStatus.Scheduled; - - @Column(columnDefinition = "TEXT") - private String notes; + @Column(nullable = false, length = 20) + private String appointmentStatus; @ManyToMany @JoinTable( - name = "appointment_pets", - joinColumns = @JoinColumn(name = "appointment_id"), - inverseJoinColumns = @JoinColumn(name = "pet_id") + name = "appointmentPet", + joinColumns = @JoinColumn(name = "appointmentId"), + inverseJoinColumns = @JoinColumn(name = "petId") ) private Set pets = new HashSet<>(); @@ -56,32 +52,27 @@ public class Appointment { @Column(name = "updated_at") private LocalDateTime updatedAt; - public enum AppointmentStatus { - Scheduled, Completed, Cancelled - } - public Appointment() { } - public Appointment(Long id, Customer customer, Service service, LocalDate appointmentDate, LocalTime appointmentTime, AppointmentStatus status, String notes, Set pets, LocalDateTime createdAt, LocalDateTime updatedAt) { - this.id = id; + public Appointment(Long appointmentId, Customer customer, Service service, LocalDate appointmentDate, LocalTime appointmentTime, String appointmentStatus, Set pets, LocalDateTime createdAt, LocalDateTime updatedAt) { + this.appointmentId = appointmentId; this.customer = customer; this.service = service; this.appointmentDate = appointmentDate; this.appointmentTime = appointmentTime; - this.status = status; - this.notes = notes; + this.appointmentStatus = appointmentStatus; this.pets = pets; this.createdAt = createdAt; this.updatedAt = updatedAt; } - public Long getId() { - return id; + public Long getAppointmentId() { + return appointmentId; } - public void setId(Long id) { - this.id = id; + public void setAppointmentId(Long appointmentId) { + this.appointmentId = appointmentId; } public Customer getCustomer() { @@ -116,20 +107,12 @@ public class Appointment { this.appointmentTime = appointmentTime; } - public AppointmentStatus getStatus() { - return status; + public String getAppointmentStatus() { + return appointmentStatus; } - public void setStatus(AppointmentStatus status) { - this.status = status; - } - - public String getNotes() { - return notes; - } - - public void setNotes(String notes) { - this.notes = notes; + public void setAppointmentStatus(String appointmentStatus) { + this.appointmentStatus = appointmentStatus; } public Set getPets() { @@ -161,24 +144,23 @@ public class Appointment { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Appointment that = (Appointment) o; - return Objects.equals(id, that.id); + return Objects.equals(appointmentId, that.appointmentId); } @Override public int hashCode() { - return Objects.hash(id); + return Objects.hash(appointmentId); } @Override public String toString() { return "Appointment{" + - "id=" + id + + "appointmentId=" + appointmentId + ", customer=" + customer + ", service=" + service + ", appointmentDate=" + appointmentDate + ", appointmentTime=" + appointmentTime + - ", status=" + status + - ", notes='" + notes + '\'' + + ", appointmentStatus='" + appointmentStatus + '\'' + ", pets=" + pets + ", createdAt=" + createdAt + ", updatedAt=" + updatedAt + diff --git a/src/main/java/com/petshop/backend/entity/Category.java b/src/main/java/com/petshop/backend/entity/Category.java index 47a9963a..ee79207e 100644 --- a/src/main/java/com/petshop/backend/entity/Category.java +++ b/src/main/java/com/petshop/backend/entity/Category.java @@ -8,18 +8,18 @@ import java.time.LocalDateTime; import java.util.Objects; @Entity -@Table(name = "categories") +@Table(name = "category") public class Category { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; + private Long categoryId; - @Column(name = "category_name", nullable = false, unique = true, length = 100) + @Column(nullable = false, length = 100) private String categoryName; - @Column(name = "category_description", columnDefinition = "TEXT") - private String categoryDescription; + @Column(nullable = false, length = 50) + private String categoryType; @CreationTimestamp @Column(name = "created_at", updatable = false) @@ -32,20 +32,20 @@ public class Category { public Category() { } - public Category(Long id, String categoryName, String categoryDescription, LocalDateTime createdAt, LocalDateTime updatedAt) { - this.id = id; + public Category(Long categoryId, String categoryName, String categoryType, LocalDateTime createdAt, LocalDateTime updatedAt) { + this.categoryId = categoryId; this.categoryName = categoryName; - this.categoryDescription = categoryDescription; + this.categoryType = categoryType; this.createdAt = createdAt; this.updatedAt = updatedAt; } - public Long getId() { - return id; + public Long getCategoryId() { + return categoryId; } - public void setId(Long id) { - this.id = id; + public void setCategoryId(Long categoryId) { + this.categoryId = categoryId; } public String getCategoryName() { @@ -56,12 +56,12 @@ public class Category { this.categoryName = categoryName; } - public String getCategoryDescription() { - return categoryDescription; + public String getCategoryType() { + return categoryType; } - public void setCategoryDescription(String categoryDescription) { - this.categoryDescription = categoryDescription; + public void setCategoryType(String categoryType) { + this.categoryType = categoryType; } public LocalDateTime getCreatedAt() { @@ -85,20 +85,20 @@ public class Category { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Category category = (Category) o; - return Objects.equals(id, category.id); + return Objects.equals(categoryId, category.categoryId); } @Override public int hashCode() { - return Objects.hash(id); + return Objects.hash(categoryId); } @Override public String toString() { return "Category{" + - "id=" + id + + "categoryId=" + categoryId + ", categoryName='" + categoryName + '\'' + - ", categoryDescription='" + categoryDescription + '\'' + + ", categoryType='" + categoryType + '\'' + ", createdAt=" + createdAt + ", updatedAt=" + updatedAt + '}'; diff --git a/src/main/java/com/petshop/backend/entity/Customer.java b/src/main/java/com/petshop/backend/entity/Customer.java index b0b319a6..661f9d80 100644 --- a/src/main/java/com/petshop/backend/entity/Customer.java +++ b/src/main/java/com/petshop/backend/entity/Customer.java @@ -8,24 +8,24 @@ import java.time.LocalDateTime; import java.util.Objects; @Entity -@Table(name = "customers") +@Table(name = "customer") public class Customer { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; + private Long customerId; - @Column(name = "customer_name", nullable = false, length = 100) - private String customerName; + @Column(nullable = false, length = 50) + private String firstName; - @Column(name = "customer_email", length = 100) - private String customerEmail; + @Column(nullable = false, length = 50) + private String lastName; - @Column(name = "customer_phone", length = 20) - private String customerPhone; + @Column(nullable = false, length = 100) + private String email; - @Column(name = "customer_address", columnDefinition = "TEXT") - private String customerAddress; + @Column(nullable = false, length = 20) + private String phone; @CreationTimestamp @Column(name = "created_at", updatable = false) @@ -38,54 +38,54 @@ public class Customer { public Customer() { } - public Customer(Long id, String customerName, String customerEmail, String customerPhone, String customerAddress, LocalDateTime createdAt, LocalDateTime updatedAt) { - this.id = id; - this.customerName = customerName; - this.customerEmail = customerEmail; - this.customerPhone = customerPhone; - this.customerAddress = customerAddress; + public Customer(Long customerId, String firstName, String lastName, String email, String phone, LocalDateTime createdAt, LocalDateTime updatedAt) { + this.customerId = customerId; + this.firstName = firstName; + this.lastName = lastName; + this.email = email; + this.phone = phone; this.createdAt = createdAt; this.updatedAt = updatedAt; } - public Long getId() { - return id; + public Long getCustomerId() { + return customerId; } - public void setId(Long id) { - this.id = id; + public void setCustomerId(Long customerId) { + this.customerId = customerId; } - public String getCustomerName() { - return customerName; + public String getFirstName() { + return firstName; } - public void setCustomerName(String customerName) { - this.customerName = customerName; + public void setFirstName(String firstName) { + this.firstName = firstName; } - public String getCustomerEmail() { - return customerEmail; + public String getLastName() { + return lastName; } - public void setCustomerEmail(String customerEmail) { - this.customerEmail = customerEmail; + public void setLastName(String lastName) { + this.lastName = lastName; } - public String getCustomerPhone() { - return customerPhone; + public String getEmail() { + return email; } - public void setCustomerPhone(String customerPhone) { - this.customerPhone = customerPhone; + public void setEmail(String email) { + this.email = email; } - public String getCustomerAddress() { - return customerAddress; + public String getPhone() { + return phone; } - public void setCustomerAddress(String customerAddress) { - this.customerAddress = customerAddress; + public void setPhone(String phone) { + this.phone = phone; } public LocalDateTime getCreatedAt() { @@ -109,22 +109,22 @@ public class Customer { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Customer customer = (Customer) o; - return Objects.equals(id, customer.id); + return Objects.equals(customerId, customer.customerId); } @Override public int hashCode() { - return Objects.hash(id); + return Objects.hash(customerId); } @Override public String toString() { return "Customer{" + - "id=" + id + - ", customerName='" + customerName + '\'' + - ", customerEmail='" + customerEmail + '\'' + - ", customerPhone='" + customerPhone + '\'' + - ", customerAddress='" + customerAddress + '\'' + + "customerId=" + customerId + + ", firstName='" + firstName + '\'' + + ", lastName='" + lastName + '\'' + + ", email='" + email + '\'' + + ", phone='" + phone + '\'' + ", createdAt=" + createdAt + ", updatedAt=" + updatedAt + '}'; diff --git a/src/main/java/com/petshop/backend/entity/Employee.java b/src/main/java/com/petshop/backend/entity/Employee.java new file mode 100644 index 00000000..573a446f --- /dev/null +++ b/src/main/java/com/petshop/backend/entity/Employee.java @@ -0,0 +1,158 @@ +package com.petshop.backend.entity; + +import jakarta.persistence.*; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; + +import java.time.LocalDateTime; +import java.util.Objects; + +@Entity +@Table(name = "employee") +public class Employee { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long employeeId; + + @Column(nullable = false, length = 50) + private String firstName; + + @Column(nullable = false, length = 50) + private String lastName; + + @Column(nullable = false, length = 100) + private String email; + + @Column(nullable = false, length = 20) + private String phone; + + @Column(nullable = false, length = 50) + private String role; + + @Column(nullable = false) + private Boolean isActive = true; + + @CreationTimestamp + @Column(name = "created_at", updatable = false) + private LocalDateTime createdAt; + + @UpdateTimestamp + @Column(name = "updated_at") + private LocalDateTime updatedAt; + + public Employee() { + } + + public Employee(Long employeeId, String firstName, String lastName, String email, String phone, String role, Boolean isActive, LocalDateTime createdAt, LocalDateTime updatedAt) { + this.employeeId = employeeId; + this.firstName = firstName; + this.lastName = lastName; + this.email = email; + this.phone = phone; + this.role = role; + this.isActive = isActive; + this.createdAt = createdAt; + this.updatedAt = updatedAt; + } + + public Long getEmployeeId() { + return employeeId; + } + + public void setEmployeeId(Long employeeId) { + this.employeeId = employeeId; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getPhone() { + return phone; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public String getRole() { + return role; + } + + public void setRole(String role) { + this.role = role; + } + + public Boolean getIsActive() { + return isActive; + } + + public void setIsActive(Boolean isActive) { + this.isActive = isActive; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(LocalDateTime createdAt) { + this.createdAt = createdAt; + } + + public LocalDateTime getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(LocalDateTime updatedAt) { + this.updatedAt = updatedAt; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Employee employee = (Employee) o; + return Objects.equals(employeeId, employee.employeeId); + } + + @Override + public int hashCode() { + return Objects.hash(employeeId); + } + + @Override + public String toString() { + return "Employee{" + + "employeeId=" + employeeId + + ", firstName='" + firstName + '\'' + + ", lastName='" + lastName + '\'' + + ", email='" + email + '\'' + + ", phone='" + phone + '\'' + + ", role='" + role + '\'' + + ", isActive=" + isActive + + ", createdAt=" + createdAt + + ", updatedAt=" + updatedAt + + '}'; + } +} diff --git a/src/main/java/com/petshop/backend/entity/EmployeeStore.java b/src/main/java/com/petshop/backend/entity/EmployeeStore.java new file mode 100644 index 00000000..d6d29b98 --- /dev/null +++ b/src/main/java/com/petshop/backend/entity/EmployeeStore.java @@ -0,0 +1,148 @@ +package com.petshop.backend.entity; + +import jakarta.persistence.*; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; + +import java.io.Serializable; +import java.time.LocalDateTime; +import java.util.Objects; + +@Entity +@Table(name = "employeeStore") +@IdClass(EmployeeStore.EmployeeStoreId.class) +public class EmployeeStore { + + @Id + @ManyToOne + @JoinColumn(name = "employeeId", nullable = false) + private Employee employee; + + @Id + @ManyToOne + @JoinColumn(name = "storeId", nullable = false) + private StoreLocation store; + + @CreationTimestamp + @Column(name = "created_at", updatable = false) + private LocalDateTime createdAt; + + @UpdateTimestamp + @Column(name = "updated_at") + private LocalDateTime updatedAt; + + public EmployeeStore() { + } + + public EmployeeStore(Employee employee, StoreLocation store, LocalDateTime createdAt, LocalDateTime updatedAt) { + this.employee = employee; + this.store = store; + this.createdAt = createdAt; + this.updatedAt = updatedAt; + } + + public Employee getEmployee() { + return employee; + } + + public void setEmployee(Employee employee) { + this.employee = employee; + } + + public StoreLocation getStore() { + return store; + } + + public void setStore(StoreLocation store) { + this.store = store; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(LocalDateTime createdAt) { + this.createdAt = createdAt; + } + + public LocalDateTime getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(LocalDateTime updatedAt) { + this.updatedAt = updatedAt; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + EmployeeStore that = (EmployeeStore) o; + return Objects.equals(employee, that.employee) && Objects.equals(store, that.store); + } + + @Override + public int hashCode() { + return Objects.hash(employee, store); + } + + @Override + public String toString() { + return "EmployeeStore{" + + "employee=" + employee + + ", store=" + store + + ", createdAt=" + createdAt + + ", updatedAt=" + updatedAt + + '}'; + } + + public static class EmployeeStoreId implements Serializable { + private Long employee; + private Long store; + + public EmployeeStoreId() { + } + + public EmployeeStoreId(Long employee, Long store) { + this.employee = employee; + this.store = store; + } + + public Long getEmployee() { + return employee; + } + + public void setEmployee(Long employee) { + this.employee = employee; + } + + public Long getStore() { + return store; + } + + public void setStore(Long store) { + this.store = store; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + EmployeeStoreId that = (EmployeeStoreId) o; + return Objects.equals(employee, that.employee) && Objects.equals(store, that.store); + } + + @Override + public int hashCode() { + return Objects.hash(employee, store); + } + + @Override + public String toString() { + return "EmployeeStoreId{" + + "employee=" + employee + + ", store=" + store + + '}'; + } + } +} diff --git a/src/main/java/com/petshop/backend/entity/Inventory.java b/src/main/java/com/petshop/backend/entity/Inventory.java index 4b167c91..07b93501 100644 --- a/src/main/java/com/petshop/backend/entity/Inventory.java +++ b/src/main/java/com/petshop/backend/entity/Inventory.java @@ -8,32 +8,20 @@ import java.time.LocalDateTime; import java.util.Objects; @Entity -@Table(name = "inventory", uniqueConstraints = { - @UniqueConstraint(name = "unique_product_store", columnNames = {"product_id", "store_id"}) -}) +@Table(name = "inventory") public class Inventory { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; + private Long inventoryId; @ManyToOne - @JoinColumn(name = "product_id", nullable = false) + @JoinColumn(name = "prodId", nullable = false) private Product product; - @ManyToOne - @JoinColumn(name = "store_id", nullable = true) - private Store store; - @Column(nullable = false) private Integer quantity = 0; - @Column(name = "reorder_level") - private Integer reorderLevel = 10; - - @Column(name = "last_restocked") - private LocalDateTime lastRestocked; - @CreationTimestamp @Column(name = "created_at", updatable = false) private LocalDateTime createdAt; @@ -45,23 +33,20 @@ public class Inventory { public Inventory() { } - public Inventory(Long id, Product product, Store store, Integer quantity, Integer reorderLevel, LocalDateTime lastRestocked, LocalDateTime createdAt, LocalDateTime updatedAt) { - this.id = id; + public Inventory(Long inventoryId, Product product, Integer quantity, LocalDateTime createdAt, LocalDateTime updatedAt) { + this.inventoryId = inventoryId; this.product = product; - this.store = store; this.quantity = quantity; - this.reorderLevel = reorderLevel; - this.lastRestocked = lastRestocked; this.createdAt = createdAt; this.updatedAt = updatedAt; } - public Long getId() { - return id; + public Long getInventoryId() { + return inventoryId; } - public void setId(Long id) { - this.id = id; + public void setInventoryId(Long inventoryId) { + this.inventoryId = inventoryId; } public Product getProduct() { @@ -72,14 +57,6 @@ public class Inventory { this.product = product; } - public Store getStore() { - return store; - } - - public void setStore(Store store) { - this.store = store; - } - public Integer getQuantity() { return quantity; } @@ -88,22 +65,6 @@ public class Inventory { this.quantity = quantity; } - public Integer getReorderLevel() { - return reorderLevel; - } - - public void setReorderLevel(Integer reorderLevel) { - this.reorderLevel = reorderLevel; - } - - public LocalDateTime getLastRestocked() { - return lastRestocked; - } - - public void setLastRestocked(LocalDateTime lastRestocked) { - this.lastRestocked = lastRestocked; - } - public LocalDateTime getCreatedAt() { return createdAt; } @@ -125,23 +86,20 @@ public class Inventory { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Inventory inventory = (Inventory) o; - return Objects.equals(id, inventory.id); + return Objects.equals(inventoryId, inventory.inventoryId); } @Override public int hashCode() { - return Objects.hash(id); + return Objects.hash(inventoryId); } @Override public String toString() { return "Inventory{" + - "id=" + id + + "inventoryId=" + inventoryId + ", product=" + product + - ", store=" + store + ", quantity=" + quantity + - ", reorderLevel=" + reorderLevel + - ", lastRestocked=" + lastRestocked + ", createdAt=" + createdAt + ", updatedAt=" + updatedAt + '}'; diff --git a/src/main/java/com/petshop/backend/entity/Pet.java b/src/main/java/com/petshop/backend/entity/Pet.java index 61abe2c0..cbbb1f90 100644 --- a/src/main/java/com/petshop/backend/entity/Pet.java +++ b/src/main/java/com/petshop/backend/entity/Pet.java @@ -9,30 +9,29 @@ import java.time.LocalDateTime; import java.util.Objects; @Entity -@Table(name = "pets") +@Table(name = "pet") public class Pet { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; + private Long petId; - @Column(name = "pet_name", nullable = false, length = 100) + @Column(nullable = false, length = 50) private String petName; - @Column(name = "pet_species", nullable = false, length = 50) + @Column(nullable = false, length = 50) private String petSpecies; - @Column(name = "pet_breed", length = 50) + @Column(nullable = false, length = 50) private String petBreed; - @Column(name = "pet_age") + @Column(nullable = false) private Integer petAge; - @Enumerated(EnumType.STRING) - @Column(name = "pet_status", nullable = false) - private PetStatus petStatus = PetStatus.AVAILABLE; + @Column(nullable = false, length = 20) + private String petStatus; - @Column(name = "pet_price", precision = 10, scale = 2) + @Column(nullable = false, precision = 10, scale = 2) private BigDecimal petPrice; @CreationTimestamp @@ -43,15 +42,11 @@ public class Pet { @Column(name = "updated_at") private LocalDateTime updatedAt; - public enum PetStatus { - AVAILABLE, ADOPTED, UNDER_CARE - } - public Pet() { } - public Pet(Long id, String petName, String petSpecies, String petBreed, Integer petAge, PetStatus petStatus, BigDecimal petPrice, LocalDateTime createdAt, LocalDateTime updatedAt) { - this.id = id; + public Pet(Long petId, String petName, String petSpecies, String petBreed, Integer petAge, String petStatus, BigDecimal petPrice, LocalDateTime createdAt, LocalDateTime updatedAt) { + this.petId = petId; this.petName = petName; this.petSpecies = petSpecies; this.petBreed = petBreed; @@ -62,12 +57,12 @@ public class Pet { this.updatedAt = updatedAt; } - public Long getId() { - return id; + public Long getPetId() { + return petId; } - public void setId(Long id) { - this.id = id; + public void setPetId(Long petId) { + this.petId = petId; } public String getPetName() { @@ -102,11 +97,11 @@ public class Pet { this.petAge = petAge; } - public PetStatus getPetStatus() { + public String getPetStatus() { return petStatus; } - public void setPetStatus(PetStatus petStatus) { + public void setPetStatus(String petStatus) { this.petStatus = petStatus; } @@ -139,23 +134,23 @@ public class Pet { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Pet pet = (Pet) o; - return Objects.equals(id, pet.id); + return Objects.equals(petId, pet.petId); } @Override public int hashCode() { - return Objects.hash(id); + return Objects.hash(petId); } @Override public String toString() { return "Pet{" + - "id=" + id + + "petId=" + petId + ", petName='" + petName + '\'' + ", petSpecies='" + petSpecies + '\'' + ", petBreed='" + petBreed + '\'' + ", petAge=" + petAge + - ", petStatus=" + petStatus + + ", petStatus='" + petStatus + '\'' + ", petPrice=" + petPrice + ", createdAt=" + createdAt + ", updatedAt=" + updatedAt + diff --git a/src/main/java/com/petshop/backend/entity/Product.java b/src/main/java/com/petshop/backend/entity/Product.java index 2a3e0dd9..9eb9c2d6 100644 --- a/src/main/java/com/petshop/backend/entity/Product.java +++ b/src/main/java/com/petshop/backend/entity/Product.java @@ -9,28 +9,25 @@ import java.time.LocalDateTime; import java.util.Objects; @Entity -@Table(name = "products") +@Table(name = "product") public class Product { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; + private Long prodId; - @Column(name = "product_name", nullable = false, length = 100) - private String productName; + @Column(nullable = false, length = 100) + private String prodName; @ManyToOne - @JoinColumn(name = "category_id", nullable = false) + @JoinColumn(name = "categoryId", nullable = false) private Category category; - @Column(name = "product_description", columnDefinition = "TEXT") - private String productDescription; + @Column(columnDefinition = "TEXT") + private String prodDesc; - @Column(name = "product_price", nullable = false, precision = 10, scale = 2) - private BigDecimal productPrice; - - @Column(nullable = false) - private Boolean active = true; + @Column(nullable = false, precision = 10, scale = 2) + private BigDecimal prodPrice; @CreationTimestamp @Column(name = "created_at", updatable = false) @@ -43,31 +40,30 @@ public class Product { public Product() { } - public Product(Long id, String productName, Category category, String productDescription, BigDecimal productPrice, Boolean active, LocalDateTime createdAt, LocalDateTime updatedAt) { - this.id = id; - this.productName = productName; + public Product(Long prodId, String prodName, Category category, String prodDesc, BigDecimal prodPrice, LocalDateTime createdAt, LocalDateTime updatedAt) { + this.prodId = prodId; + this.prodName = prodName; this.category = category; - this.productDescription = productDescription; - this.productPrice = productPrice; - this.active = active; + this.prodDesc = prodDesc; + this.prodPrice = prodPrice; this.createdAt = createdAt; this.updatedAt = updatedAt; } - public Long getId() { - return id; + public Long getProdId() { + return prodId; } - public void setId(Long id) { - this.id = id; + public void setProdId(Long prodId) { + this.prodId = prodId; } - public String getProductName() { - return productName; + public String getProdName() { + return prodName; } - public void setProductName(String productName) { - this.productName = productName; + public void setProdName(String prodName) { + this.prodName = prodName; } public Category getCategory() { @@ -78,28 +74,20 @@ public class Product { this.category = category; } - public String getProductDescription() { - return productDescription; + public String getProdDesc() { + return prodDesc; } - public void setProductDescription(String productDescription) { - this.productDescription = productDescription; + public void setProdDesc(String prodDesc) { + this.prodDesc = prodDesc; } - public BigDecimal getProductPrice() { - return productPrice; + public BigDecimal getProdPrice() { + return prodPrice; } - public void setProductPrice(BigDecimal productPrice) { - this.productPrice = productPrice; - } - - public Boolean getActive() { - return active; - } - - public void setActive(Boolean active) { - this.active = active; + public void setProdPrice(BigDecimal prodPrice) { + this.prodPrice = prodPrice; } public LocalDateTime getCreatedAt() { @@ -123,23 +111,22 @@ public class Product { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Product product = (Product) o; - return Objects.equals(id, product.id); + return Objects.equals(prodId, product.prodId); } @Override public int hashCode() { - return Objects.hash(id); + return Objects.hash(prodId); } @Override public String toString() { return "Product{" + - "id=" + id + - ", productName='" + productName + '\'' + + "prodId=" + prodId + + ", prodName='" + prodName + '\'' + ", category=" + category + - ", productDescription='" + productDescription + '\'' + - ", productPrice=" + productPrice + - ", active=" + active + + ", prodDesc='" + prodDesc + '\'' + + ", prodPrice=" + prodPrice + ", createdAt=" + createdAt + ", updatedAt=" + updatedAt + '}'; diff --git a/src/main/java/com/petshop/backend/entity/ProductSupplier.java b/src/main/java/com/petshop/backend/entity/ProductSupplier.java index b9758404..95aa6017 100644 --- a/src/main/java/com/petshop/backend/entity/ProductSupplier.java +++ b/src/main/java/com/petshop/backend/entity/ProductSupplier.java @@ -10,28 +10,22 @@ import java.time.LocalDateTime; import java.util.Objects; @Entity -@Table(name = "product_suppliers") +@Table(name = "productSupplier") @IdClass(ProductSupplier.ProductSupplierId.class) public class ProductSupplier { @Id @ManyToOne - @JoinColumn(name = "product_id", nullable = false) + @JoinColumn(name = "prodId", nullable = false) private Product product; @Id @ManyToOne - @JoinColumn(name = "supplier_id", nullable = false) + @JoinColumn(name = "supId", nullable = false) private Supplier supplier; - @Column(name = "cost_price", nullable = false, precision = 10, scale = 2) - private BigDecimal costPrice; - - @Column(name = "lead_time_days") - private Integer leadTimeDays; - - @Column(name = "is_preferred") - private Boolean isPreferred = false; + @Column(nullable = false, precision = 10, scale = 2) + private BigDecimal cost; @CreationTimestamp @Column(name = "created_at", updatable = false) @@ -44,12 +38,10 @@ public class ProductSupplier { public ProductSupplier() { } - public ProductSupplier(Product product, Supplier supplier, BigDecimal costPrice, Integer leadTimeDays, Boolean isPreferred, LocalDateTime createdAt, LocalDateTime updatedAt) { + public ProductSupplier(Product product, Supplier supplier, BigDecimal cost, LocalDateTime createdAt, LocalDateTime updatedAt) { this.product = product; this.supplier = supplier; - this.costPrice = costPrice; - this.leadTimeDays = leadTimeDays; - this.isPreferred = isPreferred; + this.cost = cost; this.createdAt = createdAt; this.updatedAt = updatedAt; } @@ -70,28 +62,12 @@ public class ProductSupplier { this.supplier = supplier; } - public BigDecimal getCostPrice() { - return costPrice; + public BigDecimal getCost() { + return cost; } - public void setCostPrice(BigDecimal costPrice) { - this.costPrice = costPrice; - } - - public Integer getLeadTimeDays() { - return leadTimeDays; - } - - public void setLeadTimeDays(Integer leadTimeDays) { - this.leadTimeDays = leadTimeDays; - } - - public Boolean getIsPreferred() { - return isPreferred; - } - - public void setIsPreferred(Boolean isPreferred) { - this.isPreferred = isPreferred; + public void setCost(BigDecimal cost) { + this.cost = cost; } public LocalDateTime getCreatedAt() { @@ -128,9 +104,7 @@ public class ProductSupplier { return "ProductSupplier{" + "product=" + product + ", supplier=" + supplier + - ", costPrice=" + costPrice + - ", leadTimeDays=" + leadTimeDays + - ", isPreferred=" + isPreferred + + ", cost=" + cost + ", createdAt=" + createdAt + ", updatedAt=" + updatedAt + '}'; diff --git a/src/main/java/com/petshop/backend/entity/PurchaseOrder.java b/src/main/java/com/petshop/backend/entity/PurchaseOrder.java index 3a63683f..76fc9a9d 100644 --- a/src/main/java/com/petshop/backend/entity/PurchaseOrder.java +++ b/src/main/java/com/petshop/backend/entity/PurchaseOrder.java @@ -12,35 +12,22 @@ import java.util.List; import java.util.Objects; @Entity -@Table(name = "purchase_orders") +@Table(name = "purchaseOrder") public class PurchaseOrder { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; + private Long purchaseOrderId; @ManyToOne - @JoinColumn(name = "supplier_id", nullable = false) + @JoinColumn(name = "supId", nullable = false) private Supplier supplier; - @Column(name = "order_date", nullable = false) + @Column(nullable = false) private LocalDate orderDate; - @Column(name = "expected_delivery") - private LocalDate expectedDelivery; - - @Enumerated(EnumType.STRING) - @Column(nullable = false) - private OrderStatus status = OrderStatus.Pending; - - @Column(name = "total_amount", nullable = false, precision = 10, scale = 2) - private BigDecimal totalAmount; - - @Column(columnDefinition = "TEXT") - private String notes; - - @OneToMany(mappedBy = "purchaseOrder", cascade = CascadeType.ALL) - private List items = new ArrayList<>(); + @Column(nullable = false, length = 50) + private String status; @CreationTimestamp @Column(name = "created_at", updatable = false) @@ -50,32 +37,24 @@ public class PurchaseOrder { @Column(name = "updated_at") private LocalDateTime updatedAt; - public enum OrderStatus { - Pending, Delivered, Cancelled - } - public PurchaseOrder() { } - public PurchaseOrder(Long id, Supplier supplier, LocalDate orderDate, LocalDate expectedDelivery, OrderStatus status, BigDecimal totalAmount, String notes, List items, LocalDateTime createdAt, LocalDateTime updatedAt) { - this.id = id; + public PurchaseOrder(Long purchaseOrderId, Supplier supplier, LocalDate orderDate, String status, LocalDateTime createdAt, LocalDateTime updatedAt) { + this.purchaseOrderId = purchaseOrderId; this.supplier = supplier; this.orderDate = orderDate; - this.expectedDelivery = expectedDelivery; this.status = status; - this.totalAmount = totalAmount; - this.notes = notes; - this.items = items; this.createdAt = createdAt; this.updatedAt = updatedAt; } - public Long getId() { - return id; + public Long getPurchaseOrderId() { + return purchaseOrderId; } - public void setId(Long id) { - this.id = id; + public void setPurchaseOrderId(Long purchaseOrderId) { + this.purchaseOrderId = purchaseOrderId; } public Supplier getSupplier() { @@ -94,46 +73,14 @@ public class PurchaseOrder { this.orderDate = orderDate; } - public LocalDate getExpectedDelivery() { - return expectedDelivery; - } - - public void setExpectedDelivery(LocalDate expectedDelivery) { - this.expectedDelivery = expectedDelivery; - } - - public OrderStatus getStatus() { + public String getStatus() { return status; } - public void setStatus(OrderStatus status) { + public void setStatus(String status) { this.status = status; } - public BigDecimal getTotalAmount() { - return totalAmount; - } - - public void setTotalAmount(BigDecimal totalAmount) { - this.totalAmount = totalAmount; - } - - public String getNotes() { - return notes; - } - - public void setNotes(String notes) { - this.notes = notes; - } - - public List getItems() { - return items; - } - - public void setItems(List items) { - this.items = items; - } - public LocalDateTime getCreatedAt() { return createdAt; } @@ -155,25 +102,21 @@ public class PurchaseOrder { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; PurchaseOrder that = (PurchaseOrder) o; - return Objects.equals(id, that.id); + return Objects.equals(purchaseOrderId, that.purchaseOrderId); } @Override public int hashCode() { - return Objects.hash(id); + return Objects.hash(purchaseOrderId); } @Override public String toString() { return "PurchaseOrder{" + - "id=" + id + + "purchaseOrderId=" + purchaseOrderId + ", supplier=" + supplier + ", orderDate=" + orderDate + - ", expectedDelivery=" + expectedDelivery + - ", status=" + status + - ", totalAmount=" + totalAmount + - ", notes='" + notes + '\'' + - ", items=" + items + + ", status='" + status + '\'' + ", createdAt=" + createdAt + ", updatedAt=" + updatedAt + '}'; diff --git a/src/main/java/com/petshop/backend/entity/Refund.java b/src/main/java/com/petshop/backend/entity/Refund.java deleted file mode 100644 index 8fe89c9a..00000000 --- a/src/main/java/com/petshop/backend/entity/Refund.java +++ /dev/null @@ -1,148 +0,0 @@ -package com.petshop.backend.entity; - -import jakarta.persistence.*; -import org.hibernate.annotations.CreationTimestamp; - -import java.math.BigDecimal; -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -@Entity -@Table(name = "refunds") -public class Refund { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @ManyToOne - @JoinColumn(name = "sale_id", nullable = false) - private Sale sale; - - @Column(name = "refund_date", nullable = false) - private LocalDateTime refundDate = LocalDateTime.now(); - - @Column(name = "refund_amount", nullable = false, precision = 10, scale = 2) - private BigDecimal refundAmount; - - @Column(name = "refund_reason", columnDefinition = "TEXT") - private String refundReason; - - @ManyToOne - @JoinColumn(name = "processed_by", nullable = false) - private User processedBy; - - @OneToMany(mappedBy = "refund", cascade = CascadeType.ALL) - private List items = new ArrayList<>(); - - @CreationTimestamp - @Column(name = "created_at", updatable = false) - private LocalDateTime createdAt; - - public Refund() { - } - - public Refund(Long id, Sale sale, LocalDateTime refundDate, BigDecimal refundAmount, String refundReason, User processedBy, List items, LocalDateTime createdAt) { - this.id = id; - this.sale = sale; - this.refundDate = refundDate; - this.refundAmount = refundAmount; - this.refundReason = refundReason; - this.processedBy = processedBy; - this.items = items; - this.createdAt = createdAt; - } - - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public Sale getSale() { - return sale; - } - - public void setSale(Sale sale) { - this.sale = sale; - } - - public LocalDateTime getRefundDate() { - return refundDate; - } - - public void setRefundDate(LocalDateTime refundDate) { - this.refundDate = refundDate; - } - - public BigDecimal getRefundAmount() { - return refundAmount; - } - - public void setRefundAmount(BigDecimal refundAmount) { - this.refundAmount = refundAmount; - } - - public String getRefundReason() { - return refundReason; - } - - public void setRefundReason(String refundReason) { - this.refundReason = refundReason; - } - - public User getProcessedBy() { - return processedBy; - } - - public void setProcessedBy(User processedBy) { - this.processedBy = processedBy; - } - - public List getItems() { - return items; - } - - public void setItems(List items) { - this.items = items; - } - - 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; - Refund refund = (Refund) o; - return Objects.equals(id, refund.id); - } - - @Override - public int hashCode() { - return Objects.hash(id); - } - - @Override - public String toString() { - return "Refund{" + - "id=" + id + - ", sale=" + sale + - ", refundDate=" + refundDate + - ", refundAmount=" + refundAmount + - ", refundReason='" + refundReason + '\'' + - ", processedBy=" + processedBy + - ", items=" + items + - ", createdAt=" + createdAt + - '}'; - } -} diff --git a/src/main/java/com/petshop/backend/entity/RefundItem.java b/src/main/java/com/petshop/backend/entity/RefundItem.java deleted file mode 100644 index 1724679a..00000000 --- a/src/main/java/com/petshop/backend/entity/RefundItem.java +++ /dev/null @@ -1,104 +0,0 @@ -package com.petshop.backend.entity; - -import jakarta.persistence.*; - -import java.math.BigDecimal; -import java.util.Objects; - -@Entity -@Table(name = "refund_items") -public class RefundItem { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @ManyToOne - @JoinColumn(name = "refund_id", nullable = false) - private Refund refund; - - @ManyToOne - @JoinColumn(name = "sale_item_id", nullable = false) - private SaleItem saleItem; - - @Column(nullable = false) - private Integer quantity; - - @Column(name = "refund_amount", nullable = false, precision = 10, scale = 2) - private BigDecimal refundAmount; - - public RefundItem() { - } - - public RefundItem(Long id, Refund refund, SaleItem saleItem, Integer quantity, BigDecimal refundAmount) { - this.id = id; - this.refund = refund; - this.saleItem = saleItem; - this.quantity = quantity; - this.refundAmount = refundAmount; - } - - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public Refund getRefund() { - return refund; - } - - public void setRefund(Refund refund) { - this.refund = refund; - } - - public SaleItem getSaleItem() { - return saleItem; - } - - public void setSaleItem(SaleItem saleItem) { - this.saleItem = saleItem; - } - - public Integer getQuantity() { - return quantity; - } - - public void setQuantity(Integer quantity) { - this.quantity = quantity; - } - - public BigDecimal getRefundAmount() { - return refundAmount; - } - - public void setRefundAmount(BigDecimal refundAmount) { - this.refundAmount = refundAmount; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - RefundItem that = (RefundItem) o; - return Objects.equals(id, that.id); - } - - @Override - public int hashCode() { - return Objects.hash(id); - } - - @Override - public String toString() { - return "RefundItem{" + - "id=" + id + - ", refund=" + refund + - ", saleItem=" + saleItem + - ", quantity=" + quantity + - ", refundAmount=" + refundAmount + - '}'; - } -} diff --git a/src/main/java/com/petshop/backend/entity/Sale.java b/src/main/java/com/petshop/backend/entity/Sale.java index fc1e8765..1874d439 100644 --- a/src/main/java/com/petshop/backend/entity/Sale.java +++ b/src/main/java/com/petshop/backend/entity/Sale.java @@ -10,42 +10,36 @@ import java.util.List; import java.util.Objects; @Entity -@Table(name = "sales") +@Table(name = "sale") public class Sale { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; + private Long saleId; - @Column(name = "sale_date", nullable = false) + @Column(nullable = false) private LocalDateTime saleDate = LocalDateTime.now(); @ManyToOne - @JoinColumn(name = "employee_id", nullable = false) - private User employee; + @JoinColumn(name = "employeeId", nullable = false) + private Employee employee; @ManyToOne - @JoinColumn(name = "customer_id") - private Customer customer; - - @ManyToOne - @JoinColumn(name = "store_id") - private Store store; + @JoinColumn(name = "storeId", nullable = false) + private StoreLocation store; @Column(nullable = false, precision = 10, scale = 2) - private BigDecimal subtotal; + private BigDecimal totalAmount; - @Column(nullable = false, precision = 10, scale = 2) - private BigDecimal tax = BigDecimal.ZERO; - - @Column(nullable = false, precision = 10, scale = 2) - private BigDecimal total; - - @Column(name = "payment_method", length = 50) + @Column(nullable = false, length = 50) private String paymentMethod; - @Column(columnDefinition = "TEXT") - private String notes; + @Column(nullable = false) + private Boolean isRefund = false; + + @ManyToOne + @JoinColumn(name = "originalSaleId") + private Sale originalSale; @OneToMany(mappedBy = "sale", cascade = CascadeType.ALL) private List items = new ArrayList<>(); @@ -57,27 +51,25 @@ public class Sale { public Sale() { } - public Sale(Long id, LocalDateTime saleDate, User employee, Customer customer, Store store, BigDecimal subtotal, BigDecimal tax, BigDecimal total, String paymentMethod, String notes, List items, LocalDateTime createdAt) { - this.id = id; + public Sale(Long saleId, LocalDateTime saleDate, Employee employee, StoreLocation store, BigDecimal totalAmount, String paymentMethod, Boolean isRefund, Sale originalSale, List items, LocalDateTime createdAt) { + this.saleId = saleId; this.saleDate = saleDate; this.employee = employee; - this.customer = customer; this.store = store; - this.subtotal = subtotal; - this.tax = tax; - this.total = total; + this.totalAmount = totalAmount; this.paymentMethod = paymentMethod; - this.notes = notes; + this.isRefund = isRefund; + this.originalSale = originalSale; this.items = items; this.createdAt = createdAt; } - public Long getId() { - return id; + public Long getSaleId() { + return saleId; } - public void setId(Long id) { - this.id = id; + public void setSaleId(Long saleId) { + this.saleId = saleId; } public LocalDateTime getSaleDate() { @@ -88,52 +80,28 @@ public class Sale { this.saleDate = saleDate; } - public User getEmployee() { + public Employee getEmployee() { return employee; } - public void setEmployee(User employee) { + public void setEmployee(Employee employee) { this.employee = employee; } - public Customer getCustomer() { - return customer; - } - - public void setCustomer(Customer customer) { - this.customer = customer; - } - - public Store getStore() { + public StoreLocation getStore() { return store; } - public void setStore(Store store) { + public void setStore(StoreLocation store) { this.store = store; } - public BigDecimal getSubtotal() { - return subtotal; + public BigDecimal getTotalAmount() { + return totalAmount; } - public void setSubtotal(BigDecimal subtotal) { - this.subtotal = subtotal; - } - - public BigDecimal getTax() { - return tax; - } - - public void setTax(BigDecimal tax) { - this.tax = tax; - } - - public BigDecimal getTotal() { - return total; - } - - public void setTotal(BigDecimal total) { - this.total = total; + public void setTotalAmount(BigDecimal totalAmount) { + this.totalAmount = totalAmount; } public String getPaymentMethod() { @@ -144,12 +112,20 @@ public class Sale { this.paymentMethod = paymentMethod; } - public String getNotes() { - return notes; + public Boolean getIsRefund() { + return isRefund; } - public void setNotes(String notes) { - this.notes = notes; + public void setIsRefund(Boolean isRefund) { + this.isRefund = isRefund; + } + + public Sale getOriginalSale() { + return originalSale; + } + + public void setOriginalSale(Sale originalSale) { + this.originalSale = originalSale; } public List getItems() { @@ -173,27 +149,25 @@ public class Sale { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Sale sale = (Sale) o; - return Objects.equals(id, sale.id); + return Objects.equals(saleId, sale.saleId); } @Override public int hashCode() { - return Objects.hash(id); + return Objects.hash(saleId); } @Override public String toString() { return "Sale{" + - "id=" + id + + "saleId=" + saleId + ", saleDate=" + saleDate + ", employee=" + employee + - ", customer=" + customer + ", store=" + store + - ", subtotal=" + subtotal + - ", tax=" + tax + - ", total=" + total + + ", totalAmount=" + totalAmount + ", paymentMethod='" + paymentMethod + '\'' + - ", notes='" + notes + '\'' + + ", isRefund=" + isRefund + + ", originalSale=" + originalSale + ", items=" + items + ", createdAt=" + createdAt + '}'; diff --git a/src/main/java/com/petshop/backend/entity/SaleItem.java b/src/main/java/com/petshop/backend/entity/SaleItem.java index 5b883f4c..3738b29a 100644 --- a/src/main/java/com/petshop/backend/entity/SaleItem.java +++ b/src/main/java/com/petshop/backend/entity/SaleItem.java @@ -6,48 +6,44 @@ import java.math.BigDecimal; import java.util.Objects; @Entity -@Table(name = "sale_items") +@Table(name = "saleItem") public class SaleItem { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; + private Long saleItemId; @ManyToOne - @JoinColumn(name = "sale_id", nullable = false) + @JoinColumn(name = "saleId", nullable = false) private Sale sale; @ManyToOne - @JoinColumn(name = "product_id", nullable = false) + @JoinColumn(name = "prodId", nullable = false) private Product product; @Column(nullable = false) private Integer quantity; - @Column(name = "unit_price", nullable = false, precision = 10, scale = 2) - private BigDecimal unitPrice; - @Column(nullable = false, precision = 10, scale = 2) - private BigDecimal subtotal; + private BigDecimal unitPrice; public SaleItem() { } - public SaleItem(Long id, Sale sale, Product product, Integer quantity, BigDecimal unitPrice, BigDecimal subtotal) { - this.id = id; + public SaleItem(Long saleItemId, Sale sale, Product product, Integer quantity, BigDecimal unitPrice) { + this.saleItemId = saleItemId; this.sale = sale; this.product = product; this.quantity = quantity; this.unitPrice = unitPrice; - this.subtotal = subtotal; } - public Long getId() { - return id; + public Long getSaleItemId() { + return saleItemId; } - public void setId(Long id) { - this.id = id; + public void setSaleItemId(Long saleItemId) { + this.saleItemId = saleItemId; } public Sale getSale() { @@ -82,36 +78,27 @@ public class SaleItem { this.unitPrice = unitPrice; } - public BigDecimal getSubtotal() { - return subtotal; - } - - public void setSubtotal(BigDecimal subtotal) { - this.subtotal = subtotal; - } - @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; SaleItem saleItem = (SaleItem) o; - return Objects.equals(id, saleItem.id); + return Objects.equals(saleItemId, saleItem.saleItemId); } @Override public int hashCode() { - return Objects.hash(id); + return Objects.hash(saleItemId); } @Override public String toString() { return "SaleItem{" + - "id=" + id + + "saleItemId=" + saleItemId + ", sale=" + sale + ", product=" + product + ", quantity=" + quantity + ", unitPrice=" + unitPrice + - ", subtotal=" + subtotal + '}'; } } diff --git a/src/main/java/com/petshop/backend/entity/Service.java b/src/main/java/com/petshop/backend/entity/Service.java index e3148cd9..a73387c8 100644 --- a/src/main/java/com/petshop/backend/entity/Service.java +++ b/src/main/java/com/petshop/backend/entity/Service.java @@ -9,27 +9,24 @@ import java.time.LocalDateTime; import java.util.Objects; @Entity -@Table(name = "services") +@Table(name = "service") public class Service { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; + private Long serviceId; - @Column(name = "service_name", nullable = false, length = 100) + @Column(nullable = false, length = 100) private String serviceName; - @Column(name = "service_description", columnDefinition = "TEXT") - private String serviceDescription; + @Column(columnDefinition = "TEXT") + private String serviceDesc; - @Column(name = "service_price", nullable = false, precision = 10, scale = 2) + @Column(nullable = false, precision = 10, scale = 2) private BigDecimal servicePrice; - @Column(name = "service_duration_minutes") - private Integer serviceDurationMinutes; - @Column(nullable = false) - private Boolean active = true; + private Integer serviceDuration; @CreationTimestamp @Column(name = "created_at", updatable = false) @@ -42,23 +39,22 @@ public class Service { public Service() { } - public Service(Long id, String serviceName, String serviceDescription, BigDecimal servicePrice, Integer serviceDurationMinutes, Boolean active, LocalDateTime createdAt, LocalDateTime updatedAt) { - this.id = id; + public Service(Long serviceId, String serviceName, String serviceDesc, BigDecimal servicePrice, Integer serviceDuration, LocalDateTime createdAt, LocalDateTime updatedAt) { + this.serviceId = serviceId; this.serviceName = serviceName; - this.serviceDescription = serviceDescription; + this.serviceDesc = serviceDesc; this.servicePrice = servicePrice; - this.serviceDurationMinutes = serviceDurationMinutes; - this.active = active; + this.serviceDuration = serviceDuration; this.createdAt = createdAt; this.updatedAt = updatedAt; } - public Long getId() { - return id; + public Long getServiceId() { + return serviceId; } - public void setId(Long id) { - this.id = id; + public void setServiceId(Long serviceId) { + this.serviceId = serviceId; } public String getServiceName() { @@ -69,12 +65,12 @@ public class Service { this.serviceName = serviceName; } - public String getServiceDescription() { - return serviceDescription; + public String getServiceDesc() { + return serviceDesc; } - public void setServiceDescription(String serviceDescription) { - this.serviceDescription = serviceDescription; + public void setServiceDesc(String serviceDesc) { + this.serviceDesc = serviceDesc; } public BigDecimal getServicePrice() { @@ -85,20 +81,12 @@ public class Service { this.servicePrice = servicePrice; } - public Integer getServiceDurationMinutes() { - return serviceDurationMinutes; + public Integer getServiceDuration() { + return serviceDuration; } - public void setServiceDurationMinutes(Integer serviceDurationMinutes) { - this.serviceDurationMinutes = serviceDurationMinutes; - } - - public Boolean getActive() { - return active; - } - - public void setActive(Boolean active) { - this.active = active; + public void setServiceDuration(Integer serviceDuration) { + this.serviceDuration = serviceDuration; } public LocalDateTime getCreatedAt() { @@ -122,23 +110,22 @@ public class Service { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Service service = (Service) o; - return Objects.equals(id, service.id); + return Objects.equals(serviceId, service.serviceId); } @Override public int hashCode() { - return Objects.hash(id); + return Objects.hash(serviceId); } @Override public String toString() { return "Service{" + - "id=" + id + + "serviceId=" + serviceId + ", serviceName='" + serviceName + '\'' + - ", serviceDescription='" + serviceDescription + '\'' + + ", serviceDesc='" + serviceDesc + '\'' + ", servicePrice=" + servicePrice + - ", serviceDurationMinutes=" + serviceDurationMinutes + - ", active=" + active + + ", serviceDuration=" + serviceDuration + ", createdAt=" + createdAt + ", updatedAt=" + updatedAt + '}'; diff --git a/src/main/java/com/petshop/backend/entity/StoreLocation.java b/src/main/java/com/petshop/backend/entity/StoreLocation.java new file mode 100644 index 00000000..6b1a2ced --- /dev/null +++ b/src/main/java/com/petshop/backend/entity/StoreLocation.java @@ -0,0 +1,132 @@ +package com.petshop.backend.entity; + +import jakarta.persistence.*; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; + +import java.time.LocalDateTime; +import java.util.Objects; + +@Entity +@Table(name = "storeLocation") +public class StoreLocation { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long storeId; + + @Column(nullable = false, length = 100) + private String storeName; + + @Column(nullable = false, length = 255) + private String address; + + @Column(nullable = false, length = 20) + private String phone; + + @Column(nullable = false, length = 100) + private String email; + + @CreationTimestamp + @Column(name = "created_at", updatable = false) + private LocalDateTime createdAt; + + @UpdateTimestamp + @Column(name = "updated_at") + private LocalDateTime updatedAt; + + public StoreLocation() { + } + + public StoreLocation(Long storeId, String storeName, String address, String phone, String email, LocalDateTime createdAt, LocalDateTime updatedAt) { + this.storeId = storeId; + this.storeName = storeName; + this.address = address; + this.phone = phone; + this.email = email; + this.createdAt = createdAt; + this.updatedAt = updatedAt; + } + + public Long getStoreId() { + return storeId; + } + + public void setStoreId(Long storeId) { + this.storeId = storeId; + } + + public String getStoreName() { + return storeName; + } + + public void setStoreName(String storeName) { + this.storeName = storeName; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + public String getPhone() { + return phone; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(LocalDateTime createdAt) { + this.createdAt = createdAt; + } + + public LocalDateTime getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(LocalDateTime updatedAt) { + this.updatedAt = updatedAt; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + StoreLocation that = (StoreLocation) o; + return Objects.equals(storeId, that.storeId); + } + + @Override + public int hashCode() { + return Objects.hash(storeId); + } + + @Override + public String toString() { + return "StoreLocation{" + + "storeId=" + storeId + + ", storeName='" + storeName + '\'' + + ", address='" + address + '\'' + + ", phone='" + phone + '\'' + + ", email='" + email + '\'' + + ", createdAt=" + createdAt + + ", updatedAt=" + updatedAt + + '}'; + } +} diff --git a/src/main/java/com/petshop/backend/entity/Supplier.java b/src/main/java/com/petshop/backend/entity/Supplier.java index c0279738..5dc35f17 100644 --- a/src/main/java/com/petshop/backend/entity/Supplier.java +++ b/src/main/java/com/petshop/backend/entity/Supplier.java @@ -8,30 +8,27 @@ import java.time.LocalDateTime; import java.util.Objects; @Entity -@Table(name = "suppliers") +@Table(name = "supplier") public class Supplier { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; + private Long supId; - @Column(name = "supplier_name", nullable = false, length = 100) - private String supplierName; + @Column(nullable = false, length = 100) + private String supCompany; - @Column(name = "supplier_contact", length = 100) - private String supplierContact; + @Column(nullable = false, length = 50) + private String supContactFirstName; - @Column(name = "supplier_email", length = 100) - private String supplierEmail; + @Column(nullable = false, length = 50) + private String supContactLastName; - @Column(name = "supplier_phone", length = 20) - private String supplierPhone; + @Column(nullable = false, length = 100) + private String supEmail; - @Column(name = "supplier_address", columnDefinition = "TEXT") - private String supplierAddress; - - @Column(nullable = false) - private Boolean active = true; + @Column(nullable = false, length = 20) + private String supPhone; @CreationTimestamp @Column(name = "created_at", updatable = false) @@ -44,72 +41,63 @@ public class Supplier { public Supplier() { } - public Supplier(Long id, String supplierName, String supplierContact, String supplierEmail, String supplierPhone, String supplierAddress, Boolean active, LocalDateTime createdAt, LocalDateTime updatedAt) { - this.id = id; - this.supplierName = supplierName; - this.supplierContact = supplierContact; - this.supplierEmail = supplierEmail; - this.supplierPhone = supplierPhone; - this.supplierAddress = supplierAddress; - this.active = active; + public Supplier(Long supId, String supCompany, String supContactFirstName, String supContactLastName, String supEmail, String supPhone, LocalDateTime createdAt, LocalDateTime updatedAt) { + this.supId = supId; + this.supCompany = supCompany; + this.supContactFirstName = supContactFirstName; + this.supContactLastName = supContactLastName; + this.supEmail = supEmail; + this.supPhone = supPhone; this.createdAt = createdAt; this.updatedAt = updatedAt; } - public Long getId() { - return id; + public Long getSupId() { + return supId; } - public void setId(Long id) { - this.id = id; + public void setSupId(Long supId) { + this.supId = supId; } - public String getSupplierName() { - return supplierName; + public String getSupCompany() { + return supCompany; } - public void setSupplierName(String supplierName) { - this.supplierName = supplierName; + public void setSupCompany(String supCompany) { + this.supCompany = supCompany; } - public String getSupplierContact() { - return supplierContact; + public String getSupContactFirstName() { + return supContactFirstName; } - public void setSupplierContact(String supplierContact) { - this.supplierContact = supplierContact; + public void setSupContactFirstName(String supContactFirstName) { + this.supContactFirstName = supContactFirstName; } - public String getSupplierEmail() { - return supplierEmail; + public String getSupContactLastName() { + return supContactLastName; } - public void setSupplierEmail(String supplierEmail) { - this.supplierEmail = supplierEmail; + public void setSupContactLastName(String supContactLastName) { + this.supContactLastName = supContactLastName; } - public String getSupplierPhone() { - return supplierPhone; + public String getSupEmail() { + return supEmail; } - public void setSupplierPhone(String supplierPhone) { - this.supplierPhone = supplierPhone; + public void setSupEmail(String supEmail) { + this.supEmail = supEmail; } - public String getSupplierAddress() { - return supplierAddress; + public String getSupPhone() { + return supPhone; } - public void setSupplierAddress(String supplierAddress) { - this.supplierAddress = supplierAddress; - } - - public Boolean getActive() { - return active; - } - - public void setActive(Boolean active) { - this.active = active; + public void setSupPhone(String supPhone) { + this.supPhone = supPhone; } public LocalDateTime getCreatedAt() { @@ -133,24 +121,23 @@ public class Supplier { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Supplier supplier = (Supplier) o; - return Objects.equals(id, supplier.id); + return Objects.equals(supId, supplier.supId); } @Override public int hashCode() { - return Objects.hash(id); + return Objects.hash(supId); } @Override public String toString() { return "Supplier{" + - "id=" + id + - ", supplierName='" + supplierName + '\'' + - ", supplierContact='" + supplierContact + '\'' + - ", supplierEmail='" + supplierEmail + '\'' + - ", supplierPhone='" + supplierPhone + '\'' + - ", supplierAddress='" + supplierAddress + '\'' + - ", active=" + active + + "supId=" + supId + + ", supCompany='" + supCompany + '\'' + + ", supContactFirstName='" + supContactFirstName + '\'' + + ", supContactLastName='" + supContactLastName + '\'' + + ", supEmail='" + supEmail + '\'' + + ", supPhone='" + supPhone + '\'' + ", createdAt=" + createdAt + ", updatedAt=" + updatedAt + '}'; diff --git a/src/main/java/com/petshop/backend/entity/User.java b/src/main/java/com/petshop/backend/entity/User.java index 729e782a..7d6901a8 100644 --- a/src/main/java/com/petshop/backend/entity/User.java +++ b/src/main/java/com/petshop/backend/entity/User.java @@ -43,7 +43,7 @@ public class User { private LocalDateTime updatedAt; public enum Role { - STAFF, ADMIN, CUSTOMER + STAFF, ADMIN } public User() { diff --git a/src/main/java/com/petshop/backend/repository/RefundRepository.java b/src/main/java/com/petshop/backend/repository/RefundRepository.java deleted file mode 100644 index 8378b672..00000000 --- a/src/main/java/com/petshop/backend/repository/RefundRepository.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.petshop.backend.repository; - -import com.petshop.backend.entity.Refund; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; - -@Repository -public interface RefundRepository extends JpaRepository { -} diff --git a/src/main/java/com/petshop/backend/security/SecurityConfig.java b/src/main/java/com/petshop/backend/security/SecurityConfig.java index e1ce42c4..dadf9d93 100644 --- a/src/main/java/com/petshop/backend/security/SecurityConfig.java +++ b/src/main/java/com/petshop/backend/security/SecurityConfig.java @@ -36,7 +36,7 @@ public class SecurityConfig { http .csrf(AbstractHttpConfigurer::disable) .authorizeHttpRequests(auth -> auth - .requestMatchers("/api/v1/auth/login", "/api/v1/auth/register").permitAll() + .requestMatchers("/api/v1/auth/login").permitAll() .requestMatchers("/swagger-ui/**", "/v3/api-docs/**", "/swagger-ui.html").permitAll() .requestMatchers(HttpMethod.GET, "/api/v1/dropdowns/suppliers").hasRole("ADMIN") .requestMatchers("/api/v1/inventory/**").hasRole("ADMIN") diff --git a/src/main/java/com/petshop/backend/service/RefundService.java b/src/main/java/com/petshop/backend/service/RefundService.java deleted file mode 100644 index ae378996..00000000 --- a/src/main/java/com/petshop/backend/service/RefundService.java +++ /dev/null @@ -1,121 +0,0 @@ -package com.petshop.backend.service; - -import com.petshop.backend.dto.refund.RefundRequest; -import com.petshop.backend.dto.refund.RefundResponse; -import com.petshop.backend.entity.*; -import com.petshop.backend.exception.BusinessException; -import com.petshop.backend.exception.ResourceNotFoundException; -import com.petshop.backend.repository.*; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.math.BigDecimal; -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; - -@Service -public class RefundService { - - private final RefundRepository refundRepository; - private final SaleRepository saleRepository; - private final SaleItemRepository saleItemRepository; - private final InventoryRepository inventoryRepository; - private final UserRepository userRepository; - - public RefundService(RefundRepository refundRepository, SaleRepository saleRepository, SaleItemRepository saleItemRepository, InventoryRepository inventoryRepository, UserRepository userRepository) { - this.refundRepository = refundRepository; - this.saleRepository = saleRepository; - this.saleItemRepository = saleItemRepository; - this.inventoryRepository = inventoryRepository; - this.userRepository = userRepository; - } - - @Transactional - public RefundResponse createRefund(Long saleId, RefundRequest request) { - String username = SecurityContextHolder.getContext().getAuthentication().getName(); - User processedBy = userRepository.findByUsername(username) - .orElseThrow(() -> new ResourceNotFoundException("User not found: " + username)); - - Sale sale = saleRepository.findById(saleId) - .orElseThrow(() -> new ResourceNotFoundException("Sale not found with id: " + saleId)); - - Refund refund = new Refund(); - refund.setSale(sale); - refund.setRefundDate(LocalDateTime.now()); - refund.setRefundReason(request.getRefundReason()); - refund.setProcessedBy(processedBy); - - BigDecimal totalRefundAmount = BigDecimal.ZERO; - List refundItems = new ArrayList<>(); - - for (var itemRequest : request.getItems()) { - SaleItem saleItem = saleItemRepository.findById(itemRequest.getSaleItemId()) - .orElseThrow(() -> new ResourceNotFoundException("Sale item not found with id: " + itemRequest.getSaleItemId())); - - if (!saleItem.getSale().getId().equals(saleId)) { - throw new BusinessException("Sale item " + itemRequest.getSaleItemId() + " does not belong to sale " + saleId); - } - - if (itemRequest.getQuantity() > saleItem.getQuantity()) { - throw new BusinessException("Refund quantity (" + itemRequest.getQuantity() + - ") exceeds original sale quantity (" + saleItem.getQuantity() + ") for product: " + saleItem.getProduct().getProductName()); - } - - Inventory inventory = inventoryRepository.findByProductIdAndStoreId( - saleItem.getProduct().getId(), - sale.getStore().getId()) - .orElseThrow(() -> new ResourceNotFoundException("Inventory not found for product " + - saleItem.getProduct().getId() + " at store " + sale.getStore().getId())); - - inventory.setQuantity(inventory.getQuantity() + itemRequest.getQuantity()); - inventory.setLastRestocked(LocalDateTime.now()); - inventoryRepository.save(inventory); - - BigDecimal itemRefundAmount = saleItem.getUnitPrice().multiply(BigDecimal.valueOf(itemRequest.getQuantity())); - - RefundItem refundItem = new RefundItem(); - refundItem.setRefund(refund); - refundItem.setSaleItem(saleItem); - refundItem.setQuantity(itemRequest.getQuantity()); - refundItem.setRefundAmount(itemRefundAmount); - - refundItems.add(refundItem); - totalRefundAmount = totalRefundAmount.add(itemRefundAmount); - } - - refund.setRefundAmount(totalRefundAmount); - refund.setItems(refundItems); - - Refund savedRefund = refundRepository.save(refund); - return mapToResponse(savedRefund); - } - - private RefundResponse mapToResponse(Refund refund) { - RefundResponse response = new RefundResponse(); - response.setId(refund.getId()); - response.setSaleId(refund.getSale().getId()); - response.setRefundDate(refund.getRefundDate()); - response.setRefundAmount(refund.getRefundAmount()); - response.setRefundReason(refund.getRefundReason()); - response.setProcessedBy(refund.getProcessedBy().getId()); - response.setProcessedByName(refund.getProcessedBy().getFullName()); - response.setCreatedAt(refund.getCreatedAt()); - - List itemResponses = new ArrayList<>(); - for (RefundItem item : refund.getItems()) { - RefundResponse.RefundItemResponse itemResponse = new RefundResponse.RefundItemResponse(); - itemResponse.setId(item.getId()); - itemResponse.setSaleItemId(item.getSaleItem().getId()); - itemResponse.setProductId(item.getSaleItem().getProduct().getId()); - itemResponse.setProductName(item.getSaleItem().getProduct().getProductName()); - itemResponse.setQuantity(item.getQuantity()); - itemResponse.setRefundAmount(item.getRefundAmount()); - itemResponses.add(itemResponse); - } - response.setItems(itemResponses); - - return response; - } -} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 315667e7..6452f1a7 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -10,7 +10,7 @@ spring: jpa: hibernate: - ddl-auto: update + ddl-auto: validate show-sql: ${JPA_SHOW_SQL:false} properties: hibernate: From a018c98ce71a597bab08a41a1e0fda2ab3620993 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Thu, 5 Mar 2026 16:40:53 -0700 Subject: [PATCH 25/84] Add repositories for new entities and cleanup --- .../com/petshop/backend/entity/Store.java | 91 ------------------- .../repository/ActivityLogRepository.java | 9 ++ .../repository/EmployeeRepository.java | 9 ++ .../backend/repository/StoreRepository.java | 10 +- test_endpoints.sh | 58 ------------ 5 files changed, 23 insertions(+), 154 deletions(-) delete mode 100644 src/main/java/com/petshop/backend/entity/Store.java create mode 100644 src/main/java/com/petshop/backend/repository/ActivityLogRepository.java create mode 100644 src/main/java/com/petshop/backend/repository/EmployeeRepository.java delete mode 100755 test_endpoints.sh diff --git a/src/main/java/com/petshop/backend/entity/Store.java b/src/main/java/com/petshop/backend/entity/Store.java deleted file mode 100644 index 35861c87..00000000 --- a/src/main/java/com/petshop/backend/entity/Store.java +++ /dev/null @@ -1,91 +0,0 @@ -package com.petshop.backend.entity; - -import jakarta.persistence.*; -import org.hibernate.annotations.CreationTimestamp; - -import java.time.LocalDateTime; -import java.util.Objects; - -@Entity -@Table(name = "stores") -public class Store { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @Column(name = "store_name", nullable = false, length = 100) - private String storeName; - - @Column(name = "store_location", length = 200) - private String storeLocation; - - @CreationTimestamp - @Column(name = "created_at", updatable = false) - private LocalDateTime createdAt; - - public Store() { - } - - public Store(Long id, String storeName, String storeLocation, LocalDateTime createdAt) { - this.id = id; - this.storeName = storeName; - this.storeLocation = storeLocation; - this.createdAt = createdAt; - } - - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public String getStoreName() { - return storeName; - } - - public void setStoreName(String storeName) { - this.storeName = storeName; - } - - public String getStoreLocation() { - return storeLocation; - } - - public void setStoreLocation(String storeLocation) { - this.storeLocation = storeLocation; - } - - 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; - Store store = (Store) o; - return Objects.equals(id, store.id); - } - - @Override - public int hashCode() { - return Objects.hash(id); - } - - @Override - public String toString() { - return "Store{" + - "id=" + id + - ", storeName='" + storeName + '\'' + - ", storeLocation='" + storeLocation + '\'' + - ", createdAt=" + createdAt + - '}'; - } -} diff --git a/src/main/java/com/petshop/backend/repository/ActivityLogRepository.java b/src/main/java/com/petshop/backend/repository/ActivityLogRepository.java new file mode 100644 index 00000000..5c5db4c5 --- /dev/null +++ b/src/main/java/com/petshop/backend/repository/ActivityLogRepository.java @@ -0,0 +1,9 @@ +package com.petshop.backend.repository; + +import com.petshop.backend.entity.ActivityLog; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface ActivityLogRepository extends JpaRepository { +} diff --git a/src/main/java/com/petshop/backend/repository/EmployeeRepository.java b/src/main/java/com/petshop/backend/repository/EmployeeRepository.java new file mode 100644 index 00000000..4c5aa9d8 --- /dev/null +++ b/src/main/java/com/petshop/backend/repository/EmployeeRepository.java @@ -0,0 +1,9 @@ +package com.petshop.backend.repository; + +import com.petshop.backend.entity.Employee; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface EmployeeRepository extends JpaRepository { +} diff --git a/src/main/java/com/petshop/backend/repository/StoreRepository.java b/src/main/java/com/petshop/backend/repository/StoreRepository.java index 0c855389..5ee3758b 100644 --- a/src/main/java/com/petshop/backend/repository/StoreRepository.java +++ b/src/main/java/com/petshop/backend/repository/StoreRepository.java @@ -1,6 +1,6 @@ package com.petshop.backend.repository; -import com.petshop.backend.entity.Store; +import com.petshop.backend.entity.StoreLocation; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; @@ -9,10 +9,10 @@ import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; @Repository -public interface StoreRepository extends JpaRepository { +public interface StoreRepository extends JpaRepository { - @Query("SELECT s FROM Store s WHERE " + + @Query("SELECT s FROM StoreLocation s WHERE " + "LOWER(s.storeName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + - "LOWER(s.storeLocation) LIKE LOWER(CONCAT('%', :q, '%'))") - Page searchStores(@Param("q") String query, Pageable pageable); + "LOWER(s.address) LIKE LOWER(CONCAT('%', :q, '%'))") + Page searchStores(@Param("q") String query, Pageable pageable); } diff --git a/test_endpoints.sh b/test_endpoints.sh deleted file mode 100755 index d310bbe6..00000000 --- a/test_endpoints.sh +++ /dev/null @@ -1,58 +0,0 @@ -#!/bin/bash - -ADMIN_TOKEN="eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJhZG1pbiIsImlhdCI6MTc3MjczMzAxOSwiZXhwIjoxNzcyODE5NDE5fQ.__RqJbY2_HMjMlF6MoU8LagTu8pxjmizYYg4BQ0ahxRn9PV5iSQO3WRnCnujyE04AOY5yjTDEakOZOTEpiDFSw" -STAFF_TOKEN="eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJzdGFmZiIsImlhdCI6MTc3MjczMzAyMCwiZXhwIjoxNzcyODE5NDIwfQ.m7jC_QMWmJsj-kc4Qb-9cQwUEnJEAYJ7mbpKJOMISSup1rONwloN3Heio6Iw5ysIkjNt6uZbwIX2SZygbxQSVg" -BASE_URL="http://localhost:8080/api/v1" - -PASS=0 -FAIL=0 -TOTAL=0 - -test_endpoint() { - local method=$1 - local path=$2 - local token=$3 - local expected_status=$4 - local data=$5 - local desc=$6 - - TOTAL=$((TOTAL + 1)) - - if [ -z "$data" ]; then - response=$(curl -s -w "\n%{http_code}" -X $method "$BASE_URL$path" -H "Authorization: Bearer $token" -H "Content-Type: application/json") - else - response=$(curl -s -w "\n%{http_code}" -X $method "$BASE_URL$path" -H "Authorization: Bearer $token" -H "Content-Type: application/json" -d "$data") - fi - - status=$(echo "$response" | tail -n1) - body=$(echo "$response" | head -n-1) - - if [ "$status" = "$expected_status" ]; then - echo "✓ PASS: $desc ($method $path) - $status" - PASS=$((PASS + 1)) - echo "$body" | jq '.' 2>/dev/null || echo "$body" - else - echo "✗ FAIL: $desc ($method $path) - Expected $expected_status, got $status" - FAIL=$((FAIL + 1)) - echo "$body" - fi - echo "---" -} - -echo "=========================================" -echo "PHASE 1: DROPDOWN ENDPOINTS (7 endpoints)" -echo "=========================================" - -test_endpoint "GET" "/dropdowns/pets" "$STAFF_TOKEN" "200" "" "Get pets dropdown" -test_endpoint "GET" "/dropdowns/customers" "$STAFF_TOKEN" "200" "" "Get customers dropdown" -test_endpoint "GET" "/dropdowns/services" "$STAFF_TOKEN" "200" "" "Get services dropdown" -test_endpoint "GET" "/dropdowns/products" "$STAFF_TOKEN" "200" "" "Get products dropdown" -test_endpoint "GET" "/dropdowns/categories" "$STAFF_TOKEN" "200" "" "Get categories dropdown" -test_endpoint "GET" "/dropdowns/stores" "$STAFF_TOKEN" "200" "" "Get stores dropdown" -test_endpoint "GET" "/dropdowns/suppliers" "$ADMIN_TOKEN" "200" "" "Get suppliers dropdown (admin)" - -echo "" -echo "=========================================" -echo "SUMMARY: Phase 1" -echo "=========================================" -echo "Total: $TOTAL | Pass: $PASS | Fail: $FAIL" From 8f6644f11fd9b57a3d862c70cfe6f0519091cd26 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Thu, 5 Mar 2026 16:53:35 -0700 Subject: [PATCH 26/84] Add comprehensive Phase 7 checklist --- PHASE_7_CHECKLIST.md | 331 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 331 insertions(+) create mode 100644 PHASE_7_CHECKLIST.md diff --git a/PHASE_7_CHECKLIST.md b/PHASE_7_CHECKLIST.md new file mode 100644 index 00000000..304ad5b5 --- /dev/null +++ b/PHASE_7_CHECKLIST.md @@ -0,0 +1,331 @@ +# Phase 7: Service & DTO Update Checklist + +## Overview +All entity schemas have been updated. This checklist covers required changes to compile the application. + +--- + +## 1. Store → StoreLocation Updates + +### Repositories +- [x] `StoreRepository.java` - Updated to use StoreLocation + +### Services +- [ ] `StoreService.java` + - [ ] Change all `Store` → `StoreLocation` + - [ ] Update field references: `store.getStoreLocation()` → `store.getAddress()` + - [ ] Update DTOs to match StoreLocation fields + +- [ ] `SaleService.java` + - [ ] Change `Store store` → `StoreLocation store` + - [ ] Update `.getStore()` → returns StoreLocation + - [ ] Update DTO mappings + +- [ ] `InventoryService.java` + - [ ] Remove all Store/storeId references (inventory is now global) + - [ ] Update queries to remove store filtering + +- [ ] `AnalyticsService.java` + - [ ] Update Store references to StoreLocation + +### DTOs +- [ ] `dto/store/StoreRequest.java` → rename/update to StoreLocationRequest + - [ ] Update fields: add `address`, `phone`, `email` + - [ ] Remove `storeLocation` field + +- [ ] `dto/store/StoreResponse.java` → rename/update to StoreLocationResponse + - [ ] Change all field names to match StoreLocation entity + +- [ ] `dto/sale/SaleResponse.java` + - [ ] Update Store references to StoreLocation + +- [ ] `dto/inventory/InventoryResponse.java` + - [ ] Remove `storeId` and `storeName` fields + +### Controllers +- [ ] `StoreController.java` + - [ ] Update all Store references to StoreLocation + +--- + +## 2. User → Employee Updates (Sales) + +### Services +- [ ] `SaleService.java` + - [ ] Change `User employee` → `Employee employee` + - [ ] Update repository/entity references + - [ ] Map employee fields correctly (employeeId, firstName, lastName) + +### DTOs +- [ ] `dto/sale/SaleRequest.java` + - [ ] Change `userId` → `employeeId` + +- [ ] `dto/sale/SaleResponse.java` + - [ ] Add employee details (firstName, lastName, not username/fullName) + +--- + +## 3. ID Field Name Changes + +### All Services - Update getter/setter calls: + +- [ ] `PetService.java` + - [ ] `.getId()` → `.getPetId()` + - [ ] `.setId()` → `.setPetId()` + +- [ ] `CustomerService.java` + - [ ] `.getId()` → `.getCustomerId()` + - [ ] `.setId()` → `.setCustomerId()` + +- [ ] `ProductService.java` + - [ ] `.getId()` → `.getProdId()` + - [ ] `.setId()` → `.setProdId()` + +- [ ] `SupplierService.java` + - [ ] `.getId()` → `.getSupId()` + - [ ] `.setId()` → `.setSupId()` + +- [ ] `CategoryService.java` + - [ ] `.getId()` → `.getCategoryId()` + - [ ] `.setId()` → `.setCategoryId()` + +- [ ] `ServiceService.java` (service for Service entity) + - [ ] `.getId()` → `.getServiceId()` + - [ ] `.setId()` → `.setServiceId()` + +- [ ] `AdoptionService.java` + - [ ] `.getId()` → `.getAdoptionId()` + - [ ] `.setId()` → `.setAdoptionId()` + +- [ ] `AppointmentService.java` + - [ ] `.getId()` → `.getAppointmentId()` + - [ ] `.setId()` → `.setAppointmentId()` + +- [ ] `SaleService.java` + - [ ] `.getId()` → `.getSaleId()` + - [ ] `.setId()` → `.setSaleId()` + +- [ ] `SaleItemRepository.java` / `SaleService.java` + - [ ] `.getId()` → `.getSaleItemId()` + +- [ ] `InventoryService.java` + - [ ] `.getId()` → `.getInventoryId()` + +- [ ] `PurchaseOrderService.java` + - [ ] `.getId()` → `.getPurchaseOrderId()` + - [ ] `.setId()` → `.setPurchaseOrderId()` + +### All DTOs - Update field names: +- [ ] All Response DTOs: change `id` → specific ID field (`petId`, `customerId`, etc.) +- [ ] All Request DTOs: update ID references accordingly + +--- + +## 4. Enum → String Conversions + +### Services + +- [ ] `PetService.java` + - [ ] Remove `Pet.PetStatus` enum references + - [ ] Use `String` for status + - [ ] Update validation to check string values ("Available", "Adopted", etc.) + +- [ ] `AppointmentService.java` + - [ ] Remove `Appointment.AppointmentStatus` enum + - [ ] Use `String` for appointmentStatus + - [ ] Update validation to check string values ("Booked", "Completed", "Cancelled") + +- [ ] `PurchaseOrderService.java` + - [ ] Remove `PurchaseOrder.OrderStatus` enum + - [ ] Use `String` for status + - [ ] Update validation to check string values ("Pending", "Delivered", etc.) + +### DTOs + +- [ ] `dto/pet/PetRequest.java` & `PetResponse.java` + - [ ] Change `PetStatus status` → `String petStatus` + +- [ ] `dto/appointment/AppointmentRequest.java` & `AppointmentResponse.java` + - [ ] Change `AppointmentStatus status` → `String appointmentStatus` + +- [ ] `dto/purchaseorder/PurchaseOrderRequest.java` & `PurchaseOrderResponse.java` + - [ ] Change `OrderStatus status` → `String status` + +--- + +## 5. Removed Fields - Delete References + +### CustomerService.java & DTOs +- [ ] Remove `customerAddress` field +- [ ] Update `Customer` entity mappings + +### SupplierService.java & DTOs +- [ ] Remove `active` field +- [ ] Remove `supplierAddress` field +- [ ] Split contact: `supplierContact` → `supContactFirstName` + `supContactLastName` + +### ServiceService.java & DTOs +- [ ] Remove `active` field + +### ProductService.java & DTOs +- [ ] Remove `active` field + +### SaleService.java & DTOs +- [ ] Remove `customer` field (sales no longer track customer) +- [ ] Remove `subtotal` field +- [ ] Remove `tax` field +- [ ] Remove `notes` field +- [ ] Change `total` → `totalAmount` + +### SaleItemService/DTOs +- [ ] Remove `subtotal` field from SaleItem + +### InventoryService.java & DTOs +- [ ] Remove `reorderLevel` field +- [ ] Remove `lastRestocked` field + +### PurchaseOrderService.java & DTOs +- [ ] Remove `expectedDelivery` field +- [ ] Remove `totalAmount` field +- [ ] Remove `notes` field +- [ ] Remove `items` list (PurchaseOrderItem not in desktop schema) + +### AdoptionService.java & DTOs +- [ ] Remove `adoptionFee` field +- [ ] Remove `notes` field +- [ ] Add `adoptionStatus` field + +--- + +## 6. Renamed Fields (snake_case → camelCase) + +### Customer +- [ ] `customer_name` → split to `firstName` + `lastName` +- [ ] `customer_email` → `email` +- [ ] `customer_phone` → `phone` + +### Pet +- [ ] `pet_name` → `petName` +- [ ] `pet_species` → `petSpecies` +- [ ] `pet_breed` → `petBreed` +- [ ] `pet_age` → `petAge` +- [ ] `pet_status` → `petStatus` +- [ ] `pet_price` → `petPrice` + +### Product +- [ ] `product_name` → `prodName` +- [ ] `product_description` → `prodDesc` +- [ ] `product_price` → `prodPrice` +- [ ] `category_id` → `categoryId` (FK) + +### Supplier +- [ ] `supplier_name` → `supCompany` +- [ ] `supplier_contact` → `supContactFirstName` + `supContactLastName` +- [ ] `supplier_email` → `supEmail` +- [ ] `supplier_phone` → `supPhone` + +### Category +- [ ] `category_name` → `categoryName` +- [ ] `category_description` → `categoryType` + +### Service +- [ ] `service_name` → `serviceName` +- [ ] `service_description` → `serviceDesc` +- [ ] `service_price` → `servicePrice` +- [ ] `service_duration_minutes` → `serviceDuration` + +### Sale +- [ ] `sale_date` → `saleDate` +- [ ] `employee_id` → `employeeId` (FK) +- [ ] `store_id` → `storeId` (FK) +- [ ] `total` → `totalAmount` +- [ ] `payment_method` → `paymentMethod` +- [ ] Add `isRefund` field +- [ ] Add `originalSaleId` field (FK to sale) + +### SaleItem +- [ ] `sale_id` → `saleId` (FK) +- [ ] `product_id` → `prodId` (FK) +- [ ] `unit_price` → `unitPrice` + +### Appointment +- [ ] `appointment_date` → `appointmentDate` +- [ ] `appointment_time` → `appointmentTime` +- [ ] `status` → `appointmentStatus` +- [ ] Join table: `appointment_pets` → `appointmentPet` + +### Adoption +- [ ] `pet_id` → `petId` (FK) +- [ ] `customer_id` → `customerId` (FK) +- [ ] `adoption_date` → `adoptionDate` +- [ ] Add `adoptionStatus` + +### Inventory +- [ ] `product_id` → `prodId` (FK) + +### ProductSupplier +- [ ] `product_id` → `prodId` (FK) +- [ ] `supplier_id` → `supId` (FK) +- [ ] `cost_price` → `cost` + +### PurchaseOrder +- [ ] `supplier_id` → `supId` (FK) +- [ ] `order_date` → `orderDate` + +--- + +## 7. New Entities - Add Services/Controllers (Optional) + +These entities exist but may not need full CRUD yet: + +- [ ] `EmployeeService.java` - basic CRUD for employees +- [ ] `EmployeeController.java` - REST endpoints for employees +- [ ] `ActivityLogService.java` - logging service +- [ ] Employee/ActivityLog DTOs + +--- + +## 8. DropdownController Updates + +- [ ] `DropdownController.java` + - [ ] Update queries for renamed columns + - [ ] Update Store → StoreLocation references + - [ ] Fix any broken field references + +--- + +## 9. Validation & Error Messages + +After making all changes above: + +- [ ] Search codebase for any remaining references to removed fields +- [ ] Update validation error messages to use new field names +- [ ] Update API documentation (Swagger annotations) + +--- + +## Quick Search Commands + +Find remaining issues: +```bash +# Find Store references (should be StoreLocation) +grep -r "import.*\.Store;" src/main/java --include="*.java" + +# Find .getId() calls (should be specific IDs) +grep -r "\.getId()" src/main/java/com/petshop/backend/service + +# Find enum references +grep -r "PetStatus\|AppointmentStatus\|OrderStatus" src/main/java --include="*.java" + +# Find removed fields +grep -r "customerAddress\|subtotal\|active" src/main/java --include="*.java" +``` + +--- + +## Testing After Changes + +1. Verify compilation: `mvn clean compile` +2. Run tests: `mvn test` +3. Start services: `docker-compose up -d` +4. Test endpoints with updated field names From 9313ca74f48a23064549e9ea70ce5bc15b4ccd37 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Thu, 5 Mar 2026 16:58:37 -0700 Subject: [PATCH 27/84] Update Store and Pet services and DTOs --- .../petshop/backend/dto/pet/PetRequest.java | 7 +-- .../petshop/backend/dto/pet/PetResponse.java | 20 +++---- .../backend/dto/store/StoreResponse.java | 56 +++++++++++++------ .../petshop/backend/service/PetService.java | 4 +- .../petshop/backend/service/StoreService.java | 12 ++-- 5 files changed, 61 insertions(+), 38 deletions(-) diff --git a/src/main/java/com/petshop/backend/dto/pet/PetRequest.java b/src/main/java/com/petshop/backend/dto/pet/PetRequest.java index aeb0f0db..db3f71c9 100644 --- a/src/main/java/com/petshop/backend/dto/pet/PetRequest.java +++ b/src/main/java/com/petshop/backend/dto/pet/PetRequest.java @@ -1,6 +1,5 @@ package com.petshop.backend.dto.pet; -import com.petshop.backend.entity.Pet; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Positive; @@ -20,7 +19,7 @@ public class PetRequest { private Integer petAge; @NotNull(message = "Status is required") - private Pet.PetStatus petStatus; + private String petStatus; private BigDecimal petPrice; @@ -56,11 +55,11 @@ public class PetRequest { this.petAge = petAge; } - public Pet.PetStatus getPetStatus() { + public String getPetStatus() { return petStatus; } - public void setPetStatus(Pet.PetStatus petStatus) { + public void setPetStatus(String petStatus) { this.petStatus = petStatus; } diff --git a/src/main/java/com/petshop/backend/dto/pet/PetResponse.java b/src/main/java/com/petshop/backend/dto/pet/PetResponse.java index 18e31381..f691361a 100644 --- a/src/main/java/com/petshop/backend/dto/pet/PetResponse.java +++ b/src/main/java/com/petshop/backend/dto/pet/PetResponse.java @@ -5,7 +5,7 @@ import java.time.LocalDateTime; import java.util.Objects; public class PetResponse { - private Long id; + private Long petId; private String petName; private String petSpecies; private String petBreed; @@ -18,8 +18,8 @@ public class PetResponse { public PetResponse() { } - public PetResponse(Long id, String petName, String petSpecies, String petBreed, Integer petAge, String petStatus, BigDecimal petPrice, LocalDateTime createdAt, LocalDateTime updatedAt) { - this.id = id; + public PetResponse(Long petId, String petName, String petSpecies, String petBreed, Integer petAge, String petStatus, BigDecimal petPrice, LocalDateTime createdAt, LocalDateTime updatedAt) { + this.petId = petId; this.petName = petName; this.petSpecies = petSpecies; this.petBreed = petBreed; @@ -30,12 +30,12 @@ public class PetResponse { this.updatedAt = updatedAt; } - public Long getId() { - return id; + public Long getPetId() { + return petId; } - public void setId(Long id) { - this.id = id; + public void setPetId(Long petId) { + this.petId = petId; } public String getPetName() { @@ -107,18 +107,18 @@ public class PetResponse { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; PetResponse that = (PetResponse) o; - return Objects.equals(id, that.id) && Objects.equals(petName, that.petName) && Objects.equals(petSpecies, that.petSpecies) && Objects.equals(petBreed, that.petBreed) && Objects.equals(petAge, that.petAge) && Objects.equals(petStatus, that.petStatus) && Objects.equals(petPrice, that.petPrice) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt); + return Objects.equals(petId, that.petId) && Objects.equals(petName, that.petName) && Objects.equals(petSpecies, that.petSpecies) && Objects.equals(petBreed, that.petBreed) && Objects.equals(petAge, that.petAge) && Objects.equals(petStatus, that.petStatus) && Objects.equals(petPrice, that.petPrice) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt); } @Override public int hashCode() { - return Objects.hash(id, petName, petSpecies, petBreed, petAge, petStatus, petPrice, createdAt, updatedAt); + return Objects.hash(petId, petName, petSpecies, petBreed, petAge, petStatus, petPrice, createdAt, updatedAt); } @Override public String toString() { return "PetResponse{" + - "id=" + id + + "petId=" + petId + ", petName='" + petName + '\'' + ", petSpecies='" + petSpecies + '\'' + ", petBreed='" + petBreed + '\'' + diff --git a/src/main/java/com/petshop/backend/dto/store/StoreResponse.java b/src/main/java/com/petshop/backend/dto/store/StoreResponse.java index 6af1e702..e6fd563e 100644 --- a/src/main/java/com/petshop/backend/dto/store/StoreResponse.java +++ b/src/main/java/com/petshop/backend/dto/store/StoreResponse.java @@ -4,27 +4,31 @@ import java.time.LocalDateTime; import java.util.Objects; public class StoreResponse { - private Long id; + private Long storeId; private String storeName; - private String storeLocation; + private String address; + private String phone; + private String email; private LocalDateTime createdAt; public StoreResponse() { } - public StoreResponse(Long id, String storeName, String storeLocation, LocalDateTime createdAt) { - this.id = id; + public StoreResponse(Long storeId, String storeName, String address, String phone, String email, LocalDateTime createdAt) { + this.storeId = storeId; this.storeName = storeName; - this.storeLocation = storeLocation; + this.address = address; + this.phone = phone; + this.email = email; this.createdAt = createdAt; } - public Long getId() { - return id; + public Long getStoreId() { + return storeId; } - public void setId(Long id) { - this.id = id; + public void setStoreId(Long storeId) { + this.storeId = storeId; } public String getStoreName() { @@ -35,12 +39,28 @@ public class StoreResponse { this.storeName = storeName; } - public String getStoreLocation() { - return storeLocation; + public String getAddress() { + return address; } - public void setStoreLocation(String storeLocation) { - this.storeLocation = storeLocation; + public void setAddress(String address) { + this.address = address; + } + + public String getPhone() { + return phone; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; } public LocalDateTime getCreatedAt() { @@ -56,20 +76,22 @@ public class StoreResponse { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; StoreResponse that = (StoreResponse) o; - return Objects.equals(id, that.id) && Objects.equals(storeName, that.storeName) && Objects.equals(storeLocation, that.storeLocation) && Objects.equals(createdAt, that.createdAt); + return Objects.equals(storeId, that.storeId); } @Override public int hashCode() { - return Objects.hash(id, storeName, storeLocation, createdAt); + return Objects.hash(storeId); } @Override public String toString() { return "StoreResponse{" + - "id=" + id + + "storeId=" + storeId + ", storeName='" + storeName + '\'' + - ", storeLocation='" + storeLocation + '\'' + + ", address='" + address + '\'' + + ", phone='" + phone + '\'' + + ", email='" + email + '\'' + ", createdAt=" + createdAt + '}'; } diff --git a/src/main/java/com/petshop/backend/service/PetService.java b/src/main/java/com/petshop/backend/service/PetService.java index 3c40a161..b59d589b 100644 --- a/src/main/java/com/petshop/backend/service/PetService.java +++ b/src/main/java/com/petshop/backend/service/PetService.java @@ -81,12 +81,12 @@ public class PetService { private PetResponse mapToResponse(Pet pet) { return new PetResponse( - pet.getId(), + pet.getPetId(), pet.getPetName(), pet.getPetSpecies(), pet.getPetBreed(), pet.getPetAge(), - pet.getPetStatus() != null ? pet.getPetStatus().toString() : null, + pet.getPetStatus(), pet.getPetPrice(), pet.getCreatedAt(), pet.getUpdatedAt() diff --git a/src/main/java/com/petshop/backend/service/StoreService.java b/src/main/java/com/petshop/backend/service/StoreService.java index 79ab3c47..2d7564e7 100644 --- a/src/main/java/com/petshop/backend/service/StoreService.java +++ b/src/main/java/com/petshop/backend/service/StoreService.java @@ -1,7 +1,7 @@ package com.petshop.backend.service; import com.petshop.backend.dto.store.StoreResponse; -import com.petshop.backend.entity.Store; +import com.petshop.backend.entity.StoreLocation; import com.petshop.backend.repository.StoreRepository; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -17,7 +17,7 @@ public class StoreService { } public Page getAllStores(String query, Pageable pageable) { - Page stores; + Page stores; if (query != null && !query.trim().isEmpty()) { stores = storeRepository.searchStores(query, pageable); } else { @@ -26,11 +26,13 @@ public class StoreService { return stores.map(this::mapToResponse); } - private StoreResponse mapToResponse(Store store) { + private StoreResponse mapToResponse(StoreLocation store) { return new StoreResponse( - store.getId(), + store.getStoreId(), store.getStoreName(), - store.getStoreLocation(), + store.getAddress(), + store.getPhone(), + store.getEmail(), store.getCreatedAt() ); } From 3a00209e1f93882c0970929fbb06f6a3cfe5f55e Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Fri, 6 Mar 2026 09:58:09 -0700 Subject: [PATCH 28/84] Align backend schema with desktop application --- PHASE_7_CHECKLIST.md | 331 ------------------ docker-compose.yml | 4 +- sql/01_petstore_init.sql | 128 ++++--- .../backend/config/DataInitializer.java | 6 - .../backend/controller/AuthController.java | 3 - .../controller/DropdownController.java | 14 +- .../backend/dto/adoption/AdoptionRequest.java | 34 +- .../dto/adoption/AdoptionResponse.java | 46 +-- .../dto/appointment/AppointmentRequest.java | 33 +- .../dto/appointment/AppointmentResponse.java | 45 +-- .../backend/dto/auth/LoginResponse.java | 17 +- .../backend/dto/auth/UserInfoResponse.java | 28 +- .../backend/dto/category/CategoryRequest.java | 16 +- .../dto/category/CategoryResponse.java | 34 +- .../backend/dto/customer/CustomerRequest.java | 62 ++-- .../dto/customer/CustomerResponse.java | 76 ++-- .../dto/inventory/InventoryRequest.java | 29 +- .../dto/inventory/InventoryResponse.java | 78 +---- .../backend/dto/product/ProductRequest.java | 16 +- .../backend/dto/product/ProductResponse.java | 73 ++-- .../ProductSupplierRequest.java | 46 +-- .../ProductSupplierResponse.java | 42 +-- .../purchaseorder/PurchaseOrderResponse.java | 174 +-------- .../backend/dto/sale/SaleItemRequest.java | 17 +- .../petshop/backend/dto/sale/SaleRequest.java | 55 ++- .../backend/dto/sale/SaleResponse.java | 148 +++----- .../backend/dto/service/ServiceRequest.java | 42 +-- .../backend/dto/service/ServiceResponse.java | 59 ++-- .../backend/dto/supplier/SupplierRequest.java | 68 ++-- .../dto/supplier/SupplierResponse.java | 101 +++--- .../petshop/backend/entity/EmployeeStore.java | 33 +- .../backend/entity/PurchaseOrderItem.java | 117 ------- .../java/com/petshop/backend/entity/Sale.java | 17 +- .../com/petshop/backend/entity/SaleItem.java | 33 +- .../java/com/petshop/backend/entity/User.java | 43 +-- .../repository/AdoptionRepository.java | 3 +- .../repository/AppointmentRepository.java | 5 +- .../repository/CategoryRepository.java | 2 +- .../repository/CustomerRepository.java | 7 +- .../repository/InventoryRepository.java | 8 +- .../backend/repository/ProductRepository.java | 4 +- .../repository/ProductSupplierRepository.java | 4 +- .../repository/PurchaseOrderRepository.java | 3 +- .../backend/repository/SaleRepository.java | 4 +- .../backend/repository/ServiceRepository.java | 2 +- .../repository/SupplierRepository.java | 5 +- .../backend/repository/UserRepository.java | 4 +- .../security/UserDetailsServiceImpl.java | 4 - .../backend/service/AdoptionService.java | 17 +- .../backend/service/AnalyticsService.java | 44 ++- .../backend/service/AppointmentService.java | 19 +- .../backend/service/CategoryService.java | 8 +- .../backend/service/CustomerService.java | 26 +- .../backend/service/InventoryService.java | 36 +- .../backend/service/ProductService.java | 25 +- .../service/ProductSupplierService.java | 20 +- .../backend/service/PurchaseOrderService.java | 32 +- .../petshop/backend/service/SaleService.java | 83 ++--- .../backend/service/ServiceService.java | 17 +- .../backend/service/SupplierService.java | 35 +- .../petshop/backend/service/UserService.java | 21 +- src/main/resources/application.yml | 2 + 62 files changed, 746 insertions(+), 1762 deletions(-) delete mode 100644 PHASE_7_CHECKLIST.md delete mode 100644 src/main/java/com/petshop/backend/entity/PurchaseOrderItem.java diff --git a/PHASE_7_CHECKLIST.md b/PHASE_7_CHECKLIST.md deleted file mode 100644 index 304ad5b5..00000000 --- a/PHASE_7_CHECKLIST.md +++ /dev/null @@ -1,331 +0,0 @@ -# Phase 7: Service & DTO Update Checklist - -## Overview -All entity schemas have been updated. This checklist covers required changes to compile the application. - ---- - -## 1. Store → StoreLocation Updates - -### Repositories -- [x] `StoreRepository.java` - Updated to use StoreLocation - -### Services -- [ ] `StoreService.java` - - [ ] Change all `Store` → `StoreLocation` - - [ ] Update field references: `store.getStoreLocation()` → `store.getAddress()` - - [ ] Update DTOs to match StoreLocation fields - -- [ ] `SaleService.java` - - [ ] Change `Store store` → `StoreLocation store` - - [ ] Update `.getStore()` → returns StoreLocation - - [ ] Update DTO mappings - -- [ ] `InventoryService.java` - - [ ] Remove all Store/storeId references (inventory is now global) - - [ ] Update queries to remove store filtering - -- [ ] `AnalyticsService.java` - - [ ] Update Store references to StoreLocation - -### DTOs -- [ ] `dto/store/StoreRequest.java` → rename/update to StoreLocationRequest - - [ ] Update fields: add `address`, `phone`, `email` - - [ ] Remove `storeLocation` field - -- [ ] `dto/store/StoreResponse.java` → rename/update to StoreLocationResponse - - [ ] Change all field names to match StoreLocation entity - -- [ ] `dto/sale/SaleResponse.java` - - [ ] Update Store references to StoreLocation - -- [ ] `dto/inventory/InventoryResponse.java` - - [ ] Remove `storeId` and `storeName` fields - -### Controllers -- [ ] `StoreController.java` - - [ ] Update all Store references to StoreLocation - ---- - -## 2. User → Employee Updates (Sales) - -### Services -- [ ] `SaleService.java` - - [ ] Change `User employee` → `Employee employee` - - [ ] Update repository/entity references - - [ ] Map employee fields correctly (employeeId, firstName, lastName) - -### DTOs -- [ ] `dto/sale/SaleRequest.java` - - [ ] Change `userId` → `employeeId` - -- [ ] `dto/sale/SaleResponse.java` - - [ ] Add employee details (firstName, lastName, not username/fullName) - ---- - -## 3. ID Field Name Changes - -### All Services - Update getter/setter calls: - -- [ ] `PetService.java` - - [ ] `.getId()` → `.getPetId()` - - [ ] `.setId()` → `.setPetId()` - -- [ ] `CustomerService.java` - - [ ] `.getId()` → `.getCustomerId()` - - [ ] `.setId()` → `.setCustomerId()` - -- [ ] `ProductService.java` - - [ ] `.getId()` → `.getProdId()` - - [ ] `.setId()` → `.setProdId()` - -- [ ] `SupplierService.java` - - [ ] `.getId()` → `.getSupId()` - - [ ] `.setId()` → `.setSupId()` - -- [ ] `CategoryService.java` - - [ ] `.getId()` → `.getCategoryId()` - - [ ] `.setId()` → `.setCategoryId()` - -- [ ] `ServiceService.java` (service for Service entity) - - [ ] `.getId()` → `.getServiceId()` - - [ ] `.setId()` → `.setServiceId()` - -- [ ] `AdoptionService.java` - - [ ] `.getId()` → `.getAdoptionId()` - - [ ] `.setId()` → `.setAdoptionId()` - -- [ ] `AppointmentService.java` - - [ ] `.getId()` → `.getAppointmentId()` - - [ ] `.setId()` → `.setAppointmentId()` - -- [ ] `SaleService.java` - - [ ] `.getId()` → `.getSaleId()` - - [ ] `.setId()` → `.setSaleId()` - -- [ ] `SaleItemRepository.java` / `SaleService.java` - - [ ] `.getId()` → `.getSaleItemId()` - -- [ ] `InventoryService.java` - - [ ] `.getId()` → `.getInventoryId()` - -- [ ] `PurchaseOrderService.java` - - [ ] `.getId()` → `.getPurchaseOrderId()` - - [ ] `.setId()` → `.setPurchaseOrderId()` - -### All DTOs - Update field names: -- [ ] All Response DTOs: change `id` → specific ID field (`petId`, `customerId`, etc.) -- [ ] All Request DTOs: update ID references accordingly - ---- - -## 4. Enum → String Conversions - -### Services - -- [ ] `PetService.java` - - [ ] Remove `Pet.PetStatus` enum references - - [ ] Use `String` for status - - [ ] Update validation to check string values ("Available", "Adopted", etc.) - -- [ ] `AppointmentService.java` - - [ ] Remove `Appointment.AppointmentStatus` enum - - [ ] Use `String` for appointmentStatus - - [ ] Update validation to check string values ("Booked", "Completed", "Cancelled") - -- [ ] `PurchaseOrderService.java` - - [ ] Remove `PurchaseOrder.OrderStatus` enum - - [ ] Use `String` for status - - [ ] Update validation to check string values ("Pending", "Delivered", etc.) - -### DTOs - -- [ ] `dto/pet/PetRequest.java` & `PetResponse.java` - - [ ] Change `PetStatus status` → `String petStatus` - -- [ ] `dto/appointment/AppointmentRequest.java` & `AppointmentResponse.java` - - [ ] Change `AppointmentStatus status` → `String appointmentStatus` - -- [ ] `dto/purchaseorder/PurchaseOrderRequest.java` & `PurchaseOrderResponse.java` - - [ ] Change `OrderStatus status` → `String status` - ---- - -## 5. Removed Fields - Delete References - -### CustomerService.java & DTOs -- [ ] Remove `customerAddress` field -- [ ] Update `Customer` entity mappings - -### SupplierService.java & DTOs -- [ ] Remove `active` field -- [ ] Remove `supplierAddress` field -- [ ] Split contact: `supplierContact` → `supContactFirstName` + `supContactLastName` - -### ServiceService.java & DTOs -- [ ] Remove `active` field - -### ProductService.java & DTOs -- [ ] Remove `active` field - -### SaleService.java & DTOs -- [ ] Remove `customer` field (sales no longer track customer) -- [ ] Remove `subtotal` field -- [ ] Remove `tax` field -- [ ] Remove `notes` field -- [ ] Change `total` → `totalAmount` - -### SaleItemService/DTOs -- [ ] Remove `subtotal` field from SaleItem - -### InventoryService.java & DTOs -- [ ] Remove `reorderLevel` field -- [ ] Remove `lastRestocked` field - -### PurchaseOrderService.java & DTOs -- [ ] Remove `expectedDelivery` field -- [ ] Remove `totalAmount` field -- [ ] Remove `notes` field -- [ ] Remove `items` list (PurchaseOrderItem not in desktop schema) - -### AdoptionService.java & DTOs -- [ ] Remove `adoptionFee` field -- [ ] Remove `notes` field -- [ ] Add `adoptionStatus` field - ---- - -## 6. Renamed Fields (snake_case → camelCase) - -### Customer -- [ ] `customer_name` → split to `firstName` + `lastName` -- [ ] `customer_email` → `email` -- [ ] `customer_phone` → `phone` - -### Pet -- [ ] `pet_name` → `petName` -- [ ] `pet_species` → `petSpecies` -- [ ] `pet_breed` → `petBreed` -- [ ] `pet_age` → `petAge` -- [ ] `pet_status` → `petStatus` -- [ ] `pet_price` → `petPrice` - -### Product -- [ ] `product_name` → `prodName` -- [ ] `product_description` → `prodDesc` -- [ ] `product_price` → `prodPrice` -- [ ] `category_id` → `categoryId` (FK) - -### Supplier -- [ ] `supplier_name` → `supCompany` -- [ ] `supplier_contact` → `supContactFirstName` + `supContactLastName` -- [ ] `supplier_email` → `supEmail` -- [ ] `supplier_phone` → `supPhone` - -### Category -- [ ] `category_name` → `categoryName` -- [ ] `category_description` → `categoryType` - -### Service -- [ ] `service_name` → `serviceName` -- [ ] `service_description` → `serviceDesc` -- [ ] `service_price` → `servicePrice` -- [ ] `service_duration_minutes` → `serviceDuration` - -### Sale -- [ ] `sale_date` → `saleDate` -- [ ] `employee_id` → `employeeId` (FK) -- [ ] `store_id` → `storeId` (FK) -- [ ] `total` → `totalAmount` -- [ ] `payment_method` → `paymentMethod` -- [ ] Add `isRefund` field -- [ ] Add `originalSaleId` field (FK to sale) - -### SaleItem -- [ ] `sale_id` → `saleId` (FK) -- [ ] `product_id` → `prodId` (FK) -- [ ] `unit_price` → `unitPrice` - -### Appointment -- [ ] `appointment_date` → `appointmentDate` -- [ ] `appointment_time` → `appointmentTime` -- [ ] `status` → `appointmentStatus` -- [ ] Join table: `appointment_pets` → `appointmentPet` - -### Adoption -- [ ] `pet_id` → `petId` (FK) -- [ ] `customer_id` → `customerId` (FK) -- [ ] `adoption_date` → `adoptionDate` -- [ ] Add `adoptionStatus` - -### Inventory -- [ ] `product_id` → `prodId` (FK) - -### ProductSupplier -- [ ] `product_id` → `prodId` (FK) -- [ ] `supplier_id` → `supId` (FK) -- [ ] `cost_price` → `cost` - -### PurchaseOrder -- [ ] `supplier_id` → `supId` (FK) -- [ ] `order_date` → `orderDate` - ---- - -## 7. New Entities - Add Services/Controllers (Optional) - -These entities exist but may not need full CRUD yet: - -- [ ] `EmployeeService.java` - basic CRUD for employees -- [ ] `EmployeeController.java` - REST endpoints for employees -- [ ] `ActivityLogService.java` - logging service -- [ ] Employee/ActivityLog DTOs - ---- - -## 8. DropdownController Updates - -- [ ] `DropdownController.java` - - [ ] Update queries for renamed columns - - [ ] Update Store → StoreLocation references - - [ ] Fix any broken field references - ---- - -## 9. Validation & Error Messages - -After making all changes above: - -- [ ] Search codebase for any remaining references to removed fields -- [ ] Update validation error messages to use new field names -- [ ] Update API documentation (Swagger annotations) - ---- - -## Quick Search Commands - -Find remaining issues: -```bash -# Find Store references (should be StoreLocation) -grep -r "import.*\.Store;" src/main/java --include="*.java" - -# Find .getId() calls (should be specific IDs) -grep -r "\.getId()" src/main/java/com/petshop/backend/service - -# Find enum references -grep -r "PetStatus\|AppointmentStatus\|OrderStatus" src/main/java --include="*.java" - -# Find removed fields -grep -r "customerAddress\|subtotal\|active" src/main/java --include="*.java" -``` - ---- - -## Testing After Changes - -1. Verify compilation: `mvn clean compile` -2. Run tests: `mvn test` -3. Start services: `docker-compose up -d` -4. Test endpoints with updated field names diff --git a/docker-compose.yml b/docker-compose.yml index 6eb0cf4e..3e8a175b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -25,8 +25,8 @@ services: SPRING_DATASOURCE_URL: jdbc:mysql://db:3306/Petstoredb?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC SPRING_DATASOURCE_USERNAME: petshop SPRING_DATASOURCE_PASSWORD: petshop - # Change this in real use - JWT_SECRET: change_me_please + # Change this in real use (must be at least 32 characters) + JWT_SECRET: change_me_please_this_secret_key_is_long_enough_for_jwt_hmac_sha256 ports: - "8080:8080" depends_on: diff --git a/sql/01_petstore_init.sql b/sql/01_petstore_init.sql index 65d708c4..c8e8dbea 100644 --- a/sql/01_petstore_init.sql +++ b/sql/01_petstore_init.sql @@ -5,174 +5,204 @@ USE Petstoredb; -- Create Tables CREATE TABLE storeLocation ( - storeId INT AUTO_INCREMENT PRIMARY KEY, + storeId BIGINT AUTO_INCREMENT PRIMARY KEY, storeName VARCHAR(100) NOT NULL, address VARCHAR(255) NOT NULL, phone VARCHAR(20) NOT NULL, - email VARCHAR(100) NOT NULL + email VARCHAR(100) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ); CREATE TABLE employee ( - employeeId INT AUTO_INCREMENT PRIMARY KEY, + employeeId BIGINT AUTO_INCREMENT PRIMARY KEY, firstName VARCHAR(50) NOT NULL, lastName VARCHAR(50) NOT NULL, email VARCHAR(100) NOT NULL, phone VARCHAR(20) NOT NULL, role VARCHAR(50) NOT NULL, - isActive BOOLEAN DEFAULT TRUE NOT NULL + isActive BOOLEAN DEFAULT TRUE NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ); CREATE TABLE employeeStore ( - employeeId INT NOT NULL, - storeId INT NOT NULL, + employeeId BIGINT NOT NULL, + storeId BIGINT NOT NULL, PRIMARY KEY (employeeId, storeId), FOREIGN KEY (employeeId) REFERENCES employee(employeeId), FOREIGN KEY (storeId) REFERENCES storeLocation(storeId) ); CREATE TABLE customer ( - customerId INT AUTO_INCREMENT PRIMARY KEY, + customerId BIGINT AUTO_INCREMENT PRIMARY KEY, firstName VARCHAR(50) NOT NULL, lastName VARCHAR(50) NOT NULL, email VARCHAR(100) NOT NULL, - phone VARCHAR(20) NOT NULL + phone VARCHAR(20) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ); CREATE TABLE pet ( - petId INT AUTO_INCREMENT PRIMARY KEY, + petId BIGINT AUTO_INCREMENT PRIMARY KEY, petName VARCHAR(50) NOT NULL, petSpecies VARCHAR(50) NOT NULL, petBreed VARCHAR(50) NOT NULL, petAge INT NOT NULL, petStatus VARCHAR(20) NOT NULL, - petPrice DECIMAL(10, 2) NOT NULL + petPrice DECIMAL(10, 2) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ); CREATE TABLE adoption ( - adoptionId INT AUTO_INCREMENT PRIMARY KEY, - petId INT NOT NULL, - customerId INT NOT NULL, + adoptionId BIGINT AUTO_INCREMENT PRIMARY KEY, + petId BIGINT NOT NULL, + customerId BIGINT NOT NULL, adoptionDate DATE NOT NULL, adoptionStatus VARCHAR(20) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (petId) REFERENCES pet(petId), FOREIGN KEY (customerId) REFERENCES customer(customerId) ); CREATE TABLE supplier ( - supId INT AUTO_INCREMENT PRIMARY KEY, + supId BIGINT AUTO_INCREMENT PRIMARY KEY, supCompany VARCHAR(100) NOT NULL, supContactFirstName VARCHAR(50) NOT NULL, supContactLastName VARCHAR(50) NOT NULL, supEmail VARCHAR(100) NOT NULL, - supPhone VARCHAR(20) NOT NULL + supPhone VARCHAR(20) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ); CREATE TABLE category ( - categoryId INT AUTO_INCREMENT PRIMARY KEY, + categoryId BIGINT AUTO_INCREMENT PRIMARY KEY, categoryName VARCHAR(100) NOT NULL, - categoryType VARCHAR(50) NOT NULL + categoryType VARCHAR(50) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ); CREATE TABLE product ( - prodId INT AUTO_INCREMENT PRIMARY KEY, + prodId BIGINT AUTO_INCREMENT PRIMARY KEY, prodName VARCHAR(100) NOT NULL, prodPrice DECIMAL(10, 2) NOT NULL, - categoryId INT NOT NULL, + categoryId BIGINT NOT NULL, prodDesc TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (categoryId) REFERENCES category(categoryId) ); CREATE TABLE productSupplier ( - supId INT NOT NULL, - prodId INT NOT NULL, + supId BIGINT NOT NULL, + prodId BIGINT NOT NULL, cost DECIMAL(10, 2) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (supId, prodId), FOREIGN KEY (supId) REFERENCES supplier(supId), FOREIGN KEY (prodId) REFERENCES product(prodId) ); CREATE TABLE inventory ( - inventoryId INT AUTO_INCREMENT PRIMARY KEY, - prodId INT NOT NULL, + inventoryId BIGINT AUTO_INCREMENT PRIMARY KEY, + prodId BIGINT NOT NULL, quantity INT DEFAULT 0 NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (prodId) REFERENCES product(prodId) ); CREATE TABLE service ( - serviceId INT AUTO_INCREMENT PRIMARY KEY, + serviceId BIGINT AUTO_INCREMENT PRIMARY KEY, serviceName VARCHAR(100) NOT NULL, serviceDesc TEXT, serviceDuration INT NOT NULL, - servicePrice DECIMAL(10, 2) NOT NULL + servicePrice DECIMAL(10, 2) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ); CREATE TABLE appointment ( - appointmentId INT AUTO_INCREMENT PRIMARY KEY, - serviceId INT NOT NULL, - customerId INT NOT NULL, + appointmentId BIGINT AUTO_INCREMENT PRIMARY KEY, + serviceId BIGINT NOT NULL, + customerId BIGINT NOT NULL, appointmentDate DATE NOT NULL, appointmentTime TIME NOT NULL, appointmentStatus VARCHAR(20) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (serviceId) REFERENCES service(serviceId), FOREIGN KEY (customerId) REFERENCES customer(customerId) ); CREATE TABLE appointmentPet ( - appointmentId INT NOT NULL, - petId INT NOT NULL, + appointmentId BIGINT NOT NULL, + petId BIGINT NOT NULL, PRIMARY KEY (appointmentId, petId), FOREIGN KEY (appointmentId) REFERENCES appointment(appointmentId), FOREIGN KEY (petId) REFERENCES pet(petId) ); CREATE TABLE sale ( - saleId INT AUTO_INCREMENT PRIMARY KEY, + saleId BIGINT AUTO_INCREMENT PRIMARY KEY, saleDate DATETIME NOT NULL, totalAmount DECIMAL(10, 2) NOT NULL, paymentMethod VARCHAR(50) NOT NULL, - employeeId INT NOT NULL, - storeId INT NOT NULL, + employeeId BIGINT NOT NULL, + storeId BIGINT NOT NULL, isRefund BOOLEAN DEFAULT FALSE NOT NULL, - originalSaleId INT NULL, + originalSaleId BIGINT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (employeeId) REFERENCES employee(employeeId), FOREIGN KEY (storeId) REFERENCES storeLocation(storeId), FOREIGN KEY (originalSaleId) REFERENCES sale(saleId) ); CREATE TABLE saleItem ( - saleItemId INT AUTO_INCREMENT PRIMARY KEY, - saleId INT NOT NULL, - prodId INT NOT NULL, + saleItemId BIGINT AUTO_INCREMENT PRIMARY KEY, + saleId BIGINT NOT NULL, + prodId BIGINT NOT NULL, quantity INT NOT NULL, unitPrice DECIMAL(10, 2) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (saleId) REFERENCES sale(saleId), FOREIGN KEY (prodId) REFERENCES product(prodId) ); CREATE TABLE purchaseOrder ( - purchaseOrderId INT AUTO_INCREMENT PRIMARY KEY, - supId INT NOT NULL, + purchaseOrderId BIGINT AUTO_INCREMENT PRIMARY KEY, + supId BIGINT NOT NULL, orderDate DATE NOT NULL, status VARCHAR(50) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (supId) REFERENCES supplier(supId) ); CREATE TABLE activityLog ( - logId INT AUTO_INCREMENT PRIMARY KEY, - employeeId INT NOT NULL, + logId BIGINT AUTO_INCREMENT PRIMARY KEY, + employeeId BIGINT NOT NULL, activity TEXT NOT NULL, logTimestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, FOREIGN KEY (employeeId) REFERENCES employee(employeeId) ); --- Authentication table (not in desktop schema) +-- Backend-only table for API authentication CREATE TABLE users ( - userId INT AUTO_INCREMENT PRIMARY KEY, - username VARCHAR(50) NOT NULL UNIQUE, + id BIGINT AUTO_INCREMENT PRIMARY KEY, + username VARCHAR(50) UNIQUE NOT NULL, password VARCHAR(255) NOT NULL, role VARCHAR(20) NOT NULL, - createdAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ); -- Insert Sample Data @@ -403,8 +433,4 @@ VALUES (5, 'Conducted health checkup'), (1, 'Updated customer information'); --- Sample users for authentication (passwords are bcrypt hashes of "password123") -INSERT INTO users (username, password, role) -VALUES -('admin', '$2a$10$8.UnVuG9HHgffUDAlk8qfOuVGkqRzgVymGe07xd00DMxs.AQubh4a', 'ADMIN'), -('staff', '$2a$10$8.UnVuG9HHgffUDAlk8qfOuVGkqRzgVymGe07xd00DMxs.AQubh4a', 'STAFF'); +-- Sample users created by DataInitializer (admin/admin123, staff/staff123) diff --git a/src/main/java/com/petshop/backend/config/DataInitializer.java b/src/main/java/com/petshop/backend/config/DataInitializer.java index cf311496..a3db73ab 100644 --- a/src/main/java/com/petshop/backend/config/DataInitializer.java +++ b/src/main/java/com/petshop/backend/config/DataInitializer.java @@ -23,10 +23,7 @@ public class DataInitializer implements CommandLineRunner { User admin = new User(); admin.setUsername("admin"); admin.setPassword(passwordEncoder.encode("admin123")); - admin.setFullName("Admin User"); - admin.setEmail("admin@petshop.com"); admin.setRole(User.Role.ADMIN); - admin.setActive(true); userRepository.save(admin); } @@ -34,10 +31,7 @@ public class DataInitializer implements CommandLineRunner { User staff = new User(); staff.setUsername("staff"); staff.setPassword(passwordEncoder.encode("staff123")); - staff.setFullName("Staff User"); - staff.setEmail("staff@petshop.com"); staff.setRole(User.Role.STAFF); - staff.setActive(true); userRepository.save(staff); } } diff --git a/src/main/java/com/petshop/backend/controller/AuthController.java b/src/main/java/com/petshop/backend/controller/AuthController.java index 2b1faa98..d0a6cc39 100644 --- a/src/main/java/com/petshop/backend/controller/AuthController.java +++ b/src/main/java/com/petshop/backend/controller/AuthController.java @@ -59,7 +59,6 @@ public class AuthController { return ResponseEntity.ok(new LoginResponse( token, user.getUsername(), - user.getFullName(), user.getRole().name() )); @@ -81,8 +80,6 @@ public class AuthController { return ResponseEntity.ok(new UserInfoResponse( user.getId(), user.getUsername(), - user.getFullName(), - user.getEmail(), user.getRole().name() )); } diff --git a/src/main/java/com/petshop/backend/controller/DropdownController.java b/src/main/java/com/petshop/backend/controller/DropdownController.java index b0b60930..bf1d1c48 100644 --- a/src/main/java/com/petshop/backend/controller/DropdownController.java +++ b/src/main/java/com/petshop/backend/controller/DropdownController.java @@ -40,7 +40,7 @@ public class DropdownController { public ResponseEntity> getPets() { return ResponseEntity.ok( petRepository.findAll().stream() - .map(p -> new DropdownOption(p.getId(), p.getPetName())) + .map(p -> new DropdownOption(p.getPetId(), p.getPetName())) .collect(Collectors.toList()) ); } @@ -49,7 +49,7 @@ public class DropdownController { public ResponseEntity> getCustomers() { return ResponseEntity.ok( customerRepository.findAll().stream() - .map(c -> new DropdownOption(c.getId(), c.getCustomerName())) + .map(c -> new DropdownOption(c.getCustomerId(), c.getFirstName() + " " + c.getLastName())) .collect(Collectors.toList()) ); } @@ -58,7 +58,7 @@ public class DropdownController { public ResponseEntity> getServices() { return ResponseEntity.ok( serviceRepository.findAll().stream() - .map(s -> new DropdownOption(s.getId(), s.getServiceName())) + .map(s -> new DropdownOption(s.getServiceId(), s.getServiceName())) .collect(Collectors.toList()) ); } @@ -67,7 +67,7 @@ public class DropdownController { public ResponseEntity> getProducts() { return ResponseEntity.ok( productRepository.findAll().stream() - .map(p -> new DropdownOption(p.getId(), p.getProductName())) + .map(p -> new DropdownOption(p.getProdId(), p.getProdName())) .collect(Collectors.toList()) ); } @@ -76,7 +76,7 @@ public class DropdownController { public ResponseEntity> getCategories() { return ResponseEntity.ok( categoryRepository.findAll().stream() - .map(c -> new DropdownOption(c.getId(), c.getCategoryName())) + .map(c -> new DropdownOption(c.getCategoryId(), c.getCategoryName())) .collect(Collectors.toList()) ); } @@ -85,7 +85,7 @@ public class DropdownController { public ResponseEntity> getStores() { return ResponseEntity.ok( storeRepository.findAll().stream() - .map(s -> new DropdownOption(s.getId(), s.getStoreName())) + .map(s -> new DropdownOption(s.getStoreId(), s.getStoreName())) .collect(Collectors.toList()) ); } @@ -95,7 +95,7 @@ public class DropdownController { public ResponseEntity> getSuppliers() { return ResponseEntity.ok( supplierRepository.findAll().stream() - .map(s -> new DropdownOption(s.getId(), s.getSupplierName())) + .map(s -> new DropdownOption(s.getSupId(), s.getSupCompany())) .collect(Collectors.toList()) ); } diff --git a/src/main/java/com/petshop/backend/dto/adoption/AdoptionRequest.java b/src/main/java/com/petshop/backend/dto/adoption/AdoptionRequest.java index 417ef3a9..1807f5b4 100644 --- a/src/main/java/com/petshop/backend/dto/adoption/AdoptionRequest.java +++ b/src/main/java/com/petshop/backend/dto/adoption/AdoptionRequest.java @@ -1,8 +1,7 @@ package com.petshop.backend.dto.adoption; +import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Positive; -import java.math.BigDecimal; import java.time.LocalDate; import java.util.Objects; @@ -16,11 +15,8 @@ public class AdoptionRequest { @NotNull(message = "Adoption date is required") private LocalDate adoptionDate; - @NotNull(message = "Adoption fee is required") - @Positive(message = "Adoption fee must be positive") - private BigDecimal adoptionFee; - - private String notes; + @NotBlank(message = "Adoption status is required") + private String adoptionStatus; public Long getPetId() { return petId; @@ -46,20 +42,12 @@ public class AdoptionRequest { this.adoptionDate = adoptionDate; } - public BigDecimal getAdoptionFee() { - return adoptionFee; + public String getAdoptionStatus() { + return adoptionStatus; } - public void setAdoptionFee(BigDecimal adoptionFee) { - this.adoptionFee = adoptionFee; - } - - public String getNotes() { - return notes; - } - - public void setNotes(String notes) { - this.notes = notes; + public void setAdoptionStatus(String adoptionStatus) { + this.adoptionStatus = adoptionStatus; } @Override @@ -70,13 +58,12 @@ public class AdoptionRequest { return Objects.equals(petId, that.petId) && Objects.equals(customerId, that.customerId) && Objects.equals(adoptionDate, that.adoptionDate) && - Objects.equals(adoptionFee, that.adoptionFee) && - Objects.equals(notes, that.notes); + Objects.equals(adoptionStatus, that.adoptionStatus); } @Override public int hashCode() { - return Objects.hash(petId, customerId, adoptionDate, adoptionFee, notes); + return Objects.hash(petId, customerId, adoptionDate, adoptionStatus); } @Override @@ -85,8 +72,7 @@ public class AdoptionRequest { "petId=" + petId + ", customerId=" + customerId + ", adoptionDate=" + adoptionDate + - ", adoptionFee=" + adoptionFee + - ", notes='" + notes + '\'' + + ", adoptionStatus='" + adoptionStatus + '\'' + '}'; } } diff --git a/src/main/java/com/petshop/backend/dto/adoption/AdoptionResponse.java b/src/main/java/com/petshop/backend/dto/adoption/AdoptionResponse.java index e0c3faec..d26e88a1 100644 --- a/src/main/java/com/petshop/backend/dto/adoption/AdoptionResponse.java +++ b/src/main/java/com/petshop/backend/dto/adoption/AdoptionResponse.java @@ -1,44 +1,41 @@ package com.petshop.backend.dto.adoption; -import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.Objects; public class AdoptionResponse { - private Long id; + private Long adoptionId; private Long petId; private String petName; private Long customerId; private String customerName; private LocalDate adoptionDate; - private BigDecimal adoptionFee; - private String notes; + private String adoptionStatus; private LocalDateTime createdAt; private LocalDateTime updatedAt; public AdoptionResponse() { } - public AdoptionResponse(Long id, Long petId, String petName, Long customerId, String customerName, LocalDate adoptionDate, BigDecimal adoptionFee, String notes, LocalDateTime createdAt, LocalDateTime updatedAt) { - this.id = id; + public AdoptionResponse(Long adoptionId, Long petId, String petName, Long customerId, String customerName, LocalDate adoptionDate, String adoptionStatus, LocalDateTime createdAt, LocalDateTime updatedAt) { + this.adoptionId = adoptionId; this.petId = petId; this.petName = petName; this.customerId = customerId; this.customerName = customerName; this.adoptionDate = adoptionDate; - this.adoptionFee = adoptionFee; - this.notes = notes; + this.adoptionStatus = adoptionStatus; this.createdAt = createdAt; this.updatedAt = updatedAt; } - public Long getId() { - return id; + public Long getAdoptionId() { + return adoptionId; } - public void setId(Long id) { - this.id = id; + public void setAdoptionId(Long adoptionId) { + this.adoptionId = adoptionId; } public Long getPetId() { @@ -81,20 +78,12 @@ public class AdoptionResponse { this.adoptionDate = adoptionDate; } - public BigDecimal getAdoptionFee() { - return adoptionFee; + public String getAdoptionStatus() { + return adoptionStatus; } - public void setAdoptionFee(BigDecimal adoptionFee) { - this.adoptionFee = adoptionFee; - } - - public String getNotes() { - return notes; - } - - public void setNotes(String notes) { - this.notes = notes; + public void setAdoptionStatus(String adoptionStatus) { + this.adoptionStatus = adoptionStatus; } public LocalDateTime getCreatedAt() { @@ -118,25 +107,24 @@ public class AdoptionResponse { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; AdoptionResponse that = (AdoptionResponse) o; - return Objects.equals(id, that.id) && Objects.equals(petId, that.petId) && Objects.equals(petName, that.petName) && Objects.equals(customerId, that.customerId) && Objects.equals(customerName, that.customerName) && Objects.equals(adoptionDate, that.adoptionDate) && Objects.equals(adoptionFee, that.adoptionFee) && Objects.equals(notes, that.notes) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt); + return Objects.equals(adoptionId, that.adoptionId) && Objects.equals(petId, that.petId) && Objects.equals(petName, that.petName) && Objects.equals(customerId, that.customerId) && Objects.equals(customerName, that.customerName) && Objects.equals(adoptionDate, that.adoptionDate) && Objects.equals(adoptionStatus, that.adoptionStatus) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt); } @Override public int hashCode() { - return Objects.hash(id, petId, petName, customerId, customerName, adoptionDate, adoptionFee, notes, createdAt, updatedAt); + return Objects.hash(adoptionId, petId, petName, customerId, customerName, adoptionDate, adoptionStatus, createdAt, updatedAt); } @Override public String toString() { return "AdoptionResponse{" + - "id=" + id + + "adoptionId=" + adoptionId + ", petId=" + petId + ", petName='" + petName + '\'' + ", customerId=" + customerId + ", customerName='" + customerName + '\'' + ", adoptionDate=" + adoptionDate + - ", adoptionFee=" + adoptionFee + - ", notes='" + notes + '\'' + + ", adoptionStatus='" + adoptionStatus + '\'' + ", createdAt=" + createdAt + ", updatedAt=" + updatedAt + '}'; diff --git a/src/main/java/com/petshop/backend/dto/appointment/AppointmentRequest.java b/src/main/java/com/petshop/backend/dto/appointment/AppointmentRequest.java index 107e6856..bd2b2356 100644 --- a/src/main/java/com/petshop/backend/dto/appointment/AppointmentRequest.java +++ b/src/main/java/com/petshop/backend/dto/appointment/AppointmentRequest.java @@ -1,6 +1,5 @@ package com.petshop.backend.dto.appointment; -import com.petshop.backend.entity.Appointment; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import java.time.LocalDate; @@ -21,14 +20,12 @@ public class AppointmentRequest { @NotNull(message = "Appointment time is required") private LocalTime appointmentTime; - @NotNull(message = "Status is required") - private Appointment.AppointmentStatus status; + @NotNull(message = "Appointment status is required") + private String appointmentStatus; @NotEmpty(message = "At least one pet must be specified") private List petIds; - private String notes; - public Long getCustomerId() { return customerId; } @@ -61,12 +58,12 @@ public class AppointmentRequest { this.appointmentTime = appointmentTime; } - public Appointment.AppointmentStatus getStatus() { - return status; + public String getAppointmentStatus() { + return appointmentStatus; } - public void setStatus(Appointment.AppointmentStatus status) { - this.status = status; + public void setAppointmentStatus(String appointmentStatus) { + this.appointmentStatus = appointmentStatus; } public List getPetIds() { @@ -77,14 +74,6 @@ public class AppointmentRequest { this.petIds = petIds; } - public String getNotes() { - return notes; - } - - public void setNotes(String notes) { - this.notes = notes; - } - @Override public boolean equals(Object o) { if (this == o) return true; @@ -94,14 +83,13 @@ public class AppointmentRequest { Objects.equals(serviceId, that.serviceId) && Objects.equals(appointmentDate, that.appointmentDate) && Objects.equals(appointmentTime, that.appointmentTime) && - status == that.status && - Objects.equals(petIds, that.petIds) && - Objects.equals(notes, that.notes); + Objects.equals(appointmentStatus, that.appointmentStatus) && + Objects.equals(petIds, that.petIds); } @Override public int hashCode() { - return Objects.hash(customerId, serviceId, appointmentDate, appointmentTime, status, petIds, notes); + return Objects.hash(customerId, serviceId, appointmentDate, appointmentTime, appointmentStatus, petIds); } @Override @@ -111,9 +99,8 @@ public class AppointmentRequest { ", serviceId=" + serviceId + ", appointmentDate=" + appointmentDate + ", appointmentTime=" + appointmentTime + - ", status=" + status + + ", appointmentStatus='" + appointmentStatus + '\'' + ", petIds=" + petIds + - ", notes='" + notes + '\'' + '}'; } } diff --git a/src/main/java/com/petshop/backend/dto/appointment/AppointmentResponse.java b/src/main/java/com/petshop/backend/dto/appointment/AppointmentResponse.java index b84b4dad..a4077a87 100644 --- a/src/main/java/com/petshop/backend/dto/appointment/AppointmentResponse.java +++ b/src/main/java/com/petshop/backend/dto/appointment/AppointmentResponse.java @@ -7,45 +7,43 @@ import java.util.List; import java.util.Objects; public class AppointmentResponse { - private Long id; + private Long appointmentId; private Long customerId; private String customerName; private Long serviceId; private String serviceName; private LocalDate appointmentDate; private LocalTime appointmentTime; - private String status; + private String appointmentStatus; private List petNames; private List petIds; - private String notes; private LocalDateTime createdAt; private LocalDateTime updatedAt; public AppointmentResponse() { } - public AppointmentResponse(Long id, Long customerId, String customerName, Long serviceId, String serviceName, LocalDate appointmentDate, LocalTime appointmentTime, String status, List petNames, List petIds, String notes, LocalDateTime createdAt, LocalDateTime updatedAt) { - this.id = id; + public AppointmentResponse(Long appointmentId, Long customerId, String customerName, Long serviceId, String serviceName, LocalDate appointmentDate, LocalTime appointmentTime, String appointmentStatus, List petNames, List petIds, LocalDateTime createdAt, LocalDateTime updatedAt) { + this.appointmentId = appointmentId; this.customerId = customerId; this.customerName = customerName; this.serviceId = serviceId; this.serviceName = serviceName; this.appointmentDate = appointmentDate; this.appointmentTime = appointmentTime; - this.status = status; + this.appointmentStatus = appointmentStatus; this.petNames = petNames; this.petIds = petIds; - this.notes = notes; this.createdAt = createdAt; this.updatedAt = updatedAt; } - public Long getId() { - return id; + public Long getAppointmentId() { + return appointmentId; } - public void setId(Long id) { - this.id = id; + public void setAppointmentId(Long appointmentId) { + this.appointmentId = appointmentId; } public Long getCustomerId() { @@ -96,12 +94,12 @@ public class AppointmentResponse { this.appointmentTime = appointmentTime; } - public String getStatus() { - return status; + public String getAppointmentStatus() { + return appointmentStatus; } - public void setStatus(String status) { - this.status = status; + public void setAppointmentStatus(String appointmentStatus) { + this.appointmentStatus = appointmentStatus; } public List getPetNames() { @@ -120,14 +118,6 @@ public class AppointmentResponse { this.petIds = petIds; } - public String getNotes() { - return notes; - } - - public void setNotes(String notes) { - this.notes = notes; - } - public LocalDateTime getCreatedAt() { return createdAt; } @@ -149,28 +139,27 @@ public class AppointmentResponse { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; AppointmentResponse that = (AppointmentResponse) o; - return Objects.equals(id, that.id) && Objects.equals(customerId, that.customerId) && Objects.equals(customerName, that.customerName) && Objects.equals(serviceId, that.serviceId) && Objects.equals(serviceName, that.serviceName) && Objects.equals(appointmentDate, that.appointmentDate) && Objects.equals(appointmentTime, that.appointmentTime) && Objects.equals(status, that.status) && Objects.equals(petNames, that.petNames) && Objects.equals(petIds, that.petIds) && Objects.equals(notes, that.notes) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt); + return Objects.equals(appointmentId, that.appointmentId) && Objects.equals(customerId, that.customerId) && Objects.equals(customerName, that.customerName) && Objects.equals(serviceId, that.serviceId) && Objects.equals(serviceName, that.serviceName) && Objects.equals(appointmentDate, that.appointmentDate) && Objects.equals(appointmentTime, that.appointmentTime) && Objects.equals(appointmentStatus, that.appointmentStatus) && Objects.equals(petNames, that.petNames) && Objects.equals(petIds, that.petIds) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt); } @Override public int hashCode() { - return Objects.hash(id, customerId, customerName, serviceId, serviceName, appointmentDate, appointmentTime, status, petNames, petIds, notes, createdAt, updatedAt); + return Objects.hash(appointmentId, customerId, customerName, serviceId, serviceName, appointmentDate, appointmentTime, appointmentStatus, petNames, petIds, createdAt, updatedAt); } @Override public String toString() { return "AppointmentResponse{" + - "id=" + id + + "appointmentId=" + appointmentId + ", customerId=" + customerId + ", customerName='" + customerName + '\'' + ", serviceId=" + serviceId + ", serviceName='" + serviceName + '\'' + ", appointmentDate=" + appointmentDate + ", appointmentTime=" + appointmentTime + - ", status='" + status + '\'' + + ", appointmentStatus='" + appointmentStatus + '\'' + ", petNames=" + petNames + ", petIds=" + petIds + - ", notes='" + notes + '\'' + ", createdAt=" + createdAt + ", updatedAt=" + updatedAt + '}'; diff --git a/src/main/java/com/petshop/backend/dto/auth/LoginResponse.java b/src/main/java/com/petshop/backend/dto/auth/LoginResponse.java index 11141df6..e3d01c11 100644 --- a/src/main/java/com/petshop/backend/dto/auth/LoginResponse.java +++ b/src/main/java/com/petshop/backend/dto/auth/LoginResponse.java @@ -5,16 +5,14 @@ import java.util.Objects; public class LoginResponse { private String token; private String username; - private String fullName; private String role; public LoginResponse() { } - public LoginResponse(String token, String username, String fullName, String role) { + public LoginResponse(String token, String username, String role) { this.token = token; this.username = username; - this.fullName = fullName; this.role = role; } @@ -34,14 +32,6 @@ public class LoginResponse { this.username = username; } - public String getFullName() { - return fullName; - } - - public void setFullName(String fullName) { - this.fullName = fullName; - } - public String getRole() { return role; } @@ -55,12 +45,12 @@ public class LoginResponse { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; LoginResponse that = (LoginResponse) o; - return Objects.equals(token, that.token) && Objects.equals(username, that.username) && Objects.equals(fullName, that.fullName) && Objects.equals(role, that.role); + return Objects.equals(token, that.token) && Objects.equals(username, that.username) && Objects.equals(role, that.role); } @Override public int hashCode() { - return Objects.hash(token, username, fullName, role); + return Objects.hash(token, username, role); } @Override @@ -68,7 +58,6 @@ public class LoginResponse { return "LoginResponse{" + "token='" + token + '\'' + ", username='" + username + '\'' + - ", fullName='" + fullName + '\'' + ", role='" + role + '\'' + '}'; } diff --git a/src/main/java/com/petshop/backend/dto/auth/UserInfoResponse.java b/src/main/java/com/petshop/backend/dto/auth/UserInfoResponse.java index a3142040..006727c9 100644 --- a/src/main/java/com/petshop/backend/dto/auth/UserInfoResponse.java +++ b/src/main/java/com/petshop/backend/dto/auth/UserInfoResponse.java @@ -5,18 +5,14 @@ import java.util.Objects; public class UserInfoResponse { private Long id; private String username; - private String fullName; - private String email; private String role; public UserInfoResponse() { } - public UserInfoResponse(Long id, String username, String fullName, String email, String role) { + public UserInfoResponse(Long id, String username, String role) { this.id = id; this.username = username; - this.fullName = fullName; - this.email = email; this.role = role; } @@ -36,22 +32,6 @@ public class UserInfoResponse { this.username = username; } - public String getFullName() { - return fullName; - } - - public void setFullName(String fullName) { - this.fullName = fullName; - } - - public String getEmail() { - return email; - } - - public void setEmail(String email) { - this.email = email; - } - public String getRole() { return role; } @@ -65,12 +45,12 @@ public class UserInfoResponse { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; UserInfoResponse that = (UserInfoResponse) o; - return Objects.equals(id, that.id) && Objects.equals(username, that.username) && Objects.equals(fullName, that.fullName) && Objects.equals(email, that.email) && Objects.equals(role, that.role); + return Objects.equals(id, that.id) && Objects.equals(username, that.username) && Objects.equals(role, that.role); } @Override public int hashCode() { - return Objects.hash(id, username, fullName, email, role); + return Objects.hash(id, username, role); } @Override @@ -78,8 +58,6 @@ public class UserInfoResponse { return "UserInfoResponse{" + "id=" + id + ", username='" + username + '\'' + - ", fullName='" + fullName + '\'' + - ", email='" + email + '\'' + ", role='" + role + '\'' + '}'; } diff --git a/src/main/java/com/petshop/backend/dto/category/CategoryRequest.java b/src/main/java/com/petshop/backend/dto/category/CategoryRequest.java index e73cca59..c012ae21 100644 --- a/src/main/java/com/petshop/backend/dto/category/CategoryRequest.java +++ b/src/main/java/com/petshop/backend/dto/category/CategoryRequest.java @@ -7,7 +7,7 @@ public class CategoryRequest { @NotBlank(message = "Category name is required") private String categoryName; - private String categoryDescription; + private String categoryType; public String getCategoryName() { return categoryName; @@ -17,12 +17,12 @@ public class CategoryRequest { this.categoryName = categoryName; } - public String getCategoryDescription() { - return categoryDescription; + public String getCategoryType() { + return categoryType; } - public void setCategoryDescription(String categoryDescription) { - this.categoryDescription = categoryDescription; + public void setCategoryType(String categoryType) { + this.categoryType = categoryType; } @Override @@ -31,19 +31,19 @@ public class CategoryRequest { if (o == null || getClass() != o.getClass()) return false; CategoryRequest that = (CategoryRequest) o; return Objects.equals(categoryName, that.categoryName) && - Objects.equals(categoryDescription, that.categoryDescription); + Objects.equals(categoryType, that.categoryType); } @Override public int hashCode() { - return Objects.hash(categoryName, categoryDescription); + return Objects.hash(categoryName, categoryType); } @Override public String toString() { return "CategoryRequest{" + "categoryName='" + categoryName + '\'' + - ", categoryDescription='" + categoryDescription + '\'' + + ", categoryType='" + categoryType + '\'' + '}'; } } diff --git a/src/main/java/com/petshop/backend/dto/category/CategoryResponse.java b/src/main/java/com/petshop/backend/dto/category/CategoryResponse.java index fe008c16..6d9e1569 100644 --- a/src/main/java/com/petshop/backend/dto/category/CategoryResponse.java +++ b/src/main/java/com/petshop/backend/dto/category/CategoryResponse.java @@ -4,29 +4,29 @@ import java.time.LocalDateTime; import java.util.Objects; public class CategoryResponse { - private Long id; + private Long categoryId; private String categoryName; - private String categoryDescription; + private String categoryType; private LocalDateTime createdAt; private LocalDateTime updatedAt; public CategoryResponse() { } - public CategoryResponse(Long id, String categoryName, String categoryDescription, LocalDateTime createdAt, LocalDateTime updatedAt) { - this.id = id; + public CategoryResponse(Long categoryId, String categoryName, String categoryType, LocalDateTime createdAt, LocalDateTime updatedAt) { + this.categoryId = categoryId; this.categoryName = categoryName; - this.categoryDescription = categoryDescription; + this.categoryType = categoryType; this.createdAt = createdAt; this.updatedAt = updatedAt; } - public Long getId() { - return id; + public Long getCategoryId() { + return categoryId; } - public void setId(Long id) { - this.id = id; + public void setCategoryId(Long categoryId) { + this.categoryId = categoryId; } public String getCategoryName() { @@ -37,12 +37,12 @@ public class CategoryResponse { this.categoryName = categoryName; } - public String getCategoryDescription() { - return categoryDescription; + public String getCategoryType() { + return categoryType; } - public void setCategoryDescription(String categoryDescription) { - this.categoryDescription = categoryDescription; + public void setCategoryType(String categoryType) { + this.categoryType = categoryType; } public LocalDateTime getCreatedAt() { @@ -66,20 +66,20 @@ public class CategoryResponse { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; CategoryResponse that = (CategoryResponse) o; - return Objects.equals(id, that.id) && Objects.equals(categoryName, that.categoryName) && Objects.equals(categoryDescription, that.categoryDescription) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt); + return Objects.equals(categoryId, that.categoryId) && Objects.equals(categoryName, that.categoryName) && Objects.equals(categoryType, that.categoryType) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt); } @Override public int hashCode() { - return Objects.hash(id, categoryName, categoryDescription, createdAt, updatedAt); + return Objects.hash(categoryId, categoryName, categoryType, createdAt, updatedAt); } @Override public String toString() { return "CategoryResponse{" + - "id=" + id + + "categoryId=" + categoryId + ", categoryName='" + categoryName + '\'' + - ", categoryDescription='" + categoryDescription + '\'' + + ", categoryType='" + categoryType + '\'' + ", createdAt=" + createdAt + ", updatedAt=" + updatedAt + '}'; diff --git a/src/main/java/com/petshop/backend/dto/customer/CustomerRequest.java b/src/main/java/com/petshop/backend/dto/customer/CustomerRequest.java index 464c80b7..d982be81 100644 --- a/src/main/java/com/petshop/backend/dto/customer/CustomerRequest.java +++ b/src/main/java/com/petshop/backend/dto/customer/CustomerRequest.java @@ -5,45 +5,47 @@ import jakarta.validation.constraints.NotBlank; import java.util.Objects; public class CustomerRequest { - @NotBlank(message = "Customer name is required") - private String customerName; + @NotBlank(message = "First name is required") + private String firstName; + + @NotBlank(message = "Last name is required") + private String lastName; @Email(message = "Invalid email format") - private String customerEmail; + private String email; - private String customerPhone; - private String customerAddress; + private String phone; - public String getCustomerName() { - return customerName; + public String getFirstName() { + return firstName; } - public void setCustomerName(String customerName) { - this.customerName = customerName; + public void setFirstName(String firstName) { + this.firstName = firstName; } - public String getCustomerEmail() { - return customerEmail; + public String getLastName() { + return lastName; } - public void setCustomerEmail(String customerEmail) { - this.customerEmail = customerEmail; + public void setLastName(String lastName) { + this.lastName = lastName; } - public String getCustomerPhone() { - return customerPhone; + public String getEmail() { + return email; } - public void setCustomerPhone(String customerPhone) { - this.customerPhone = customerPhone; + public void setEmail(String email) { + this.email = email; } - public String getCustomerAddress() { - return customerAddress; + public String getPhone() { + return phone; } - public void setCustomerAddress(String customerAddress) { - this.customerAddress = customerAddress; + public void setPhone(String phone) { + this.phone = phone; } @Override @@ -51,24 +53,24 @@ public class CustomerRequest { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; CustomerRequest that = (CustomerRequest) o; - return Objects.equals(customerName, that.customerName) && - Objects.equals(customerEmail, that.customerEmail) && - Objects.equals(customerPhone, that.customerPhone) && - Objects.equals(customerAddress, that.customerAddress); + return Objects.equals(firstName, that.firstName) && + Objects.equals(lastName, that.lastName) && + Objects.equals(email, that.email) && + Objects.equals(phone, that.phone); } @Override public int hashCode() { - return Objects.hash(customerName, customerEmail, customerPhone, customerAddress); + return Objects.hash(firstName, lastName, email, phone); } @Override public String toString() { return "CustomerRequest{" + - "customerName='" + customerName + '\'' + - ", customerEmail='" + customerEmail + '\'' + - ", customerPhone='" + customerPhone + '\'' + - ", customerAddress='" + customerAddress + '\'' + + "firstName='" + firstName + '\'' + + ", lastName='" + lastName + '\'' + + ", email='" + email + '\'' + + ", phone='" + phone + '\'' + '}'; } } diff --git a/src/main/java/com/petshop/backend/dto/customer/CustomerResponse.java b/src/main/java/com/petshop/backend/dto/customer/CustomerResponse.java index 3e6be930..7b25f17a 100644 --- a/src/main/java/com/petshop/backend/dto/customer/CustomerResponse.java +++ b/src/main/java/com/petshop/backend/dto/customer/CustomerResponse.java @@ -4,65 +4,65 @@ import java.time.LocalDateTime; import java.util.Objects; public class CustomerResponse { - private Long id; - private String customerName; - private String customerEmail; - private String customerPhone; - private String customerAddress; + private Long customerId; + private String firstName; + private String lastName; + private String email; + private String phone; private LocalDateTime createdAt; private LocalDateTime updatedAt; public CustomerResponse() { } - public CustomerResponse(Long id, String customerName, String customerEmail, String customerPhone, String customerAddress, LocalDateTime createdAt, LocalDateTime updatedAt) { - this.id = id; - this.customerName = customerName; - this.customerEmail = customerEmail; - this.customerPhone = customerPhone; - this.customerAddress = customerAddress; + public CustomerResponse(Long customerId, String firstName, String lastName, String email, String phone, LocalDateTime createdAt, LocalDateTime updatedAt) { + this.customerId = customerId; + this.firstName = firstName; + this.lastName = lastName; + this.email = email; + this.phone = phone; this.createdAt = createdAt; this.updatedAt = updatedAt; } - public Long getId() { - return id; + public Long getCustomerId() { + return customerId; } - public void setId(Long id) { - this.id = id; + public void setCustomerId(Long customerId) { + this.customerId = customerId; } - public String getCustomerName() { - return customerName; + public String getFirstName() { + return firstName; } - public void setCustomerName(String customerName) { - this.customerName = customerName; + public void setFirstName(String firstName) { + this.firstName = firstName; } - public String getCustomerEmail() { - return customerEmail; + public String getLastName() { + return lastName; } - public void setCustomerEmail(String customerEmail) { - this.customerEmail = customerEmail; + public void setLastName(String lastName) { + this.lastName = lastName; } - public String getCustomerPhone() { - return customerPhone; + public String getEmail() { + return email; } - public void setCustomerPhone(String customerPhone) { - this.customerPhone = customerPhone; + public void setEmail(String email) { + this.email = email; } - public String getCustomerAddress() { - return customerAddress; + public String getPhone() { + return phone; } - public void setCustomerAddress(String customerAddress) { - this.customerAddress = customerAddress; + public void setPhone(String phone) { + this.phone = phone; } public LocalDateTime getCreatedAt() { @@ -86,22 +86,22 @@ public class CustomerResponse { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; CustomerResponse that = (CustomerResponse) o; - return Objects.equals(id, that.id) && Objects.equals(customerName, that.customerName) && Objects.equals(customerEmail, that.customerEmail) && Objects.equals(customerPhone, that.customerPhone) && Objects.equals(customerAddress, that.customerAddress) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt); + return Objects.equals(customerId, that.customerId) && Objects.equals(firstName, that.firstName) && Objects.equals(lastName, that.lastName) && Objects.equals(email, that.email) && Objects.equals(phone, that.phone) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt); } @Override public int hashCode() { - return Objects.hash(id, customerName, customerEmail, customerPhone, customerAddress, createdAt, updatedAt); + return Objects.hash(customerId, firstName, lastName, email, phone, createdAt, updatedAt); } @Override public String toString() { return "CustomerResponse{" + - "id=" + id + - ", customerName='" + customerName + '\'' + - ", customerEmail='" + customerEmail + '\'' + - ", customerPhone='" + customerPhone + '\'' + - ", customerAddress='" + customerAddress + '\'' + + "customerId=" + customerId + + ", firstName='" + firstName + '\'' + + ", lastName='" + lastName + '\'' + + ", email='" + email + '\'' + + ", phone='" + phone + '\'' + ", createdAt=" + createdAt + ", updatedAt=" + updatedAt + '}'; diff --git a/src/main/java/com/petshop/backend/dto/inventory/InventoryRequest.java b/src/main/java/com/petshop/backend/dto/inventory/InventoryRequest.java index 5ecf211a..2dd953c6 100644 --- a/src/main/java/com/petshop/backend/dto/inventory/InventoryRequest.java +++ b/src/main/java/com/petshop/backend/dto/inventory/InventoryRequest.java @@ -8,15 +8,10 @@ public class InventoryRequest { @NotNull(message = "Product ID is required") private Long prodId; - private Long storeId; - @NotNull(message = "Quantity is required") @PositiveOrZero(message = "Quantity must be zero or positive") private Integer quantity; - @PositiveOrZero(message = "Reorder level must be zero or positive") - private Integer reorderLevel = 10; - public Long getProdId() { return prodId; } @@ -25,14 +20,6 @@ public class InventoryRequest { this.prodId = prodId; } - public Long getStoreId() { - return storeId; - } - - public void setStoreId(Long storeId) { - this.storeId = storeId; - } - public Integer getQuantity() { return quantity; } @@ -41,37 +28,25 @@ public class InventoryRequest { this.quantity = quantity; } - public Integer getReorderLevel() { - return reorderLevel; - } - - public void setReorderLevel(Integer reorderLevel) { - this.reorderLevel = reorderLevel; - } - @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; InventoryRequest that = (InventoryRequest) o; return Objects.equals(prodId, that.prodId) && - Objects.equals(storeId, that.storeId) && - Objects.equals(quantity, that.quantity) && - Objects.equals(reorderLevel, that.reorderLevel); + Objects.equals(quantity, that.quantity); } @Override public int hashCode() { - return Objects.hash(prodId, storeId, quantity, reorderLevel); + return Objects.hash(prodId, quantity); } @Override public String toString() { return "InventoryRequest{" + "prodId=" + prodId + - ", storeId=" + storeId + ", quantity=" + quantity + - ", reorderLevel=" + reorderLevel + '}'; } } diff --git a/src/main/java/com/petshop/backend/dto/inventory/InventoryResponse.java b/src/main/java/com/petshop/backend/dto/inventory/InventoryResponse.java index cb618959..710dcbf4 100644 --- a/src/main/java/com/petshop/backend/dto/inventory/InventoryResponse.java +++ b/src/main/java/com/petshop/backend/dto/inventory/InventoryResponse.java @@ -4,49 +4,41 @@ import java.time.LocalDateTime; import java.util.Objects; public class InventoryResponse { - private Long id; - private Long productId; + private Long inventoryId; + private Long prodId; private String productName; private String categoryName; - private Long storeId; - private String storeName; private Integer quantity; - private Integer reorderLevel; - private LocalDateTime lastRestocked; private LocalDateTime createdAt; private LocalDateTime updatedAt; public InventoryResponse() { } - public InventoryResponse(Long id, Long productId, String productName, String categoryName, Long storeId, String storeName, Integer quantity, Integer reorderLevel, LocalDateTime lastRestocked, LocalDateTime createdAt, LocalDateTime updatedAt) { - this.id = id; - this.productId = productId; + public InventoryResponse(Long inventoryId, Long prodId, String productName, String categoryName, Integer quantity, LocalDateTime createdAt, LocalDateTime updatedAt) { + this.inventoryId = inventoryId; + this.prodId = prodId; this.productName = productName; this.categoryName = categoryName; - this.storeId = storeId; - this.storeName = storeName; this.quantity = quantity; - this.reorderLevel = reorderLevel; - this.lastRestocked = lastRestocked; this.createdAt = createdAt; this.updatedAt = updatedAt; } - public Long getId() { - return id; + public Long getInventoryId() { + return inventoryId; } - public void setId(Long id) { - this.id = id; + public void setInventoryId(Long inventoryId) { + this.inventoryId = inventoryId; } - public Long getProductId() { - return productId; + public Long getProdId() { + return prodId; } - public void setProductId(Long productId) { - this.productId = productId; + public void setProdId(Long prodId) { + this.prodId = prodId; } public String getProductName() { @@ -65,22 +57,6 @@ public class InventoryResponse { this.categoryName = categoryName; } - public Long getStoreId() { - return storeId; - } - - public void setStoreId(Long storeId) { - this.storeId = storeId; - } - - public String getStoreName() { - return storeName; - } - - public void setStoreName(String storeName) { - this.storeName = storeName; - } - public Integer getQuantity() { return quantity; } @@ -89,22 +65,6 @@ public class InventoryResponse { this.quantity = quantity; } - public Integer getReorderLevel() { - return reorderLevel; - } - - public void setReorderLevel(Integer reorderLevel) { - this.reorderLevel = reorderLevel; - } - - public LocalDateTime getLastRestocked() { - return lastRestocked; - } - - public void setLastRestocked(LocalDateTime lastRestocked) { - this.lastRestocked = lastRestocked; - } - public LocalDateTime getCreatedAt() { return createdAt; } @@ -126,26 +86,22 @@ public class InventoryResponse { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; InventoryResponse that = (InventoryResponse) o; - return Objects.equals(id, that.id) && Objects.equals(productId, that.productId) && Objects.equals(productName, that.productName) && Objects.equals(categoryName, that.categoryName) && Objects.equals(storeId, that.storeId) && Objects.equals(storeName, that.storeName) && Objects.equals(quantity, that.quantity) && Objects.equals(reorderLevel, that.reorderLevel) && Objects.equals(lastRestocked, that.lastRestocked) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt); + return Objects.equals(inventoryId, that.inventoryId) && Objects.equals(prodId, that.prodId) && Objects.equals(productName, that.productName) && Objects.equals(categoryName, that.categoryName) && Objects.equals(quantity, that.quantity) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt); } @Override public int hashCode() { - return Objects.hash(id, productId, productName, categoryName, storeId, storeName, quantity, reorderLevel, lastRestocked, createdAt, updatedAt); + return Objects.hash(inventoryId, prodId, productName, categoryName, quantity, createdAt, updatedAt); } @Override public String toString() { return "InventoryResponse{" + - "id=" + id + - ", productId=" + productId + + "inventoryId=" + inventoryId + + ", prodId=" + prodId + ", productName='" + productName + '\'' + ", categoryName='" + categoryName + '\'' + - ", storeId=" + storeId + - ", storeName='" + storeName + '\'' + ", quantity=" + quantity + - ", reorderLevel=" + reorderLevel + - ", lastRestocked=" + lastRestocked + ", createdAt=" + createdAt + ", updatedAt=" + updatedAt + '}'; diff --git a/src/main/java/com/petshop/backend/dto/product/ProductRequest.java b/src/main/java/com/petshop/backend/dto/product/ProductRequest.java index 3e44a803..71dd600f 100644 --- a/src/main/java/com/petshop/backend/dto/product/ProductRequest.java +++ b/src/main/java/com/petshop/backend/dto/product/ProductRequest.java @@ -19,8 +19,6 @@ public class ProductRequest { @Positive(message = "Price must be positive") private BigDecimal prodPrice; - private Boolean active = true; - public String getProdName() { return prodName; } @@ -53,14 +51,6 @@ public class ProductRequest { this.prodPrice = prodPrice; } - public Boolean getActive() { - return active; - } - - public void setActive(Boolean active) { - this.active = active; - } - @Override public boolean equals(Object o) { if (this == o) return true; @@ -69,13 +59,12 @@ public class ProductRequest { return Objects.equals(prodName, that.prodName) && Objects.equals(categoryId, that.categoryId) && Objects.equals(prodDesc, that.prodDesc) && - Objects.equals(prodPrice, that.prodPrice) && - Objects.equals(active, that.active); + Objects.equals(prodPrice, that.prodPrice); } @Override public int hashCode() { - return Objects.hash(prodName, categoryId, prodDesc, prodPrice, active); + return Objects.hash(prodName, categoryId, prodDesc, prodPrice); } @Override @@ -85,7 +74,6 @@ public class ProductRequest { ", categoryId=" + categoryId + ", prodDesc='" + prodDesc + '\'' + ", prodPrice=" + prodPrice + - ", active=" + active + '}'; } } diff --git a/src/main/java/com/petshop/backend/dto/product/ProductResponse.java b/src/main/java/com/petshop/backend/dto/product/ProductResponse.java index d54632c4..96baa5ce 100644 --- a/src/main/java/com/petshop/backend/dto/product/ProductResponse.java +++ b/src/main/java/com/petshop/backend/dto/product/ProductResponse.java @@ -5,45 +5,43 @@ import java.time.LocalDateTime; import java.util.Objects; public class ProductResponse { - private Long id; - private String productName; + private Long prodId; + private String prodName; private Long categoryId; private String categoryName; - private String productDescription; - private BigDecimal productPrice; - private Boolean active; + private String prodDesc; + private BigDecimal prodPrice; private LocalDateTime createdAt; private LocalDateTime updatedAt; public ProductResponse() { } - public ProductResponse(Long id, String productName, Long categoryId, String categoryName, String productDescription, BigDecimal productPrice, Boolean active, LocalDateTime createdAt, LocalDateTime updatedAt) { - this.id = id; - this.productName = productName; + public ProductResponse(Long prodId, String prodName, Long categoryId, String categoryName, String prodDesc, BigDecimal prodPrice, LocalDateTime createdAt, LocalDateTime updatedAt) { + this.prodId = prodId; + this.prodName = prodName; this.categoryId = categoryId; this.categoryName = categoryName; - this.productDescription = productDescription; - this.productPrice = productPrice; - this.active = active; + this.prodDesc = prodDesc; + this.prodPrice = prodPrice; this.createdAt = createdAt; this.updatedAt = updatedAt; } - public Long getId() { - return id; + public Long getProdId() { + return prodId; } - public void setId(Long id) { - this.id = id; + public void setProdId(Long prodId) { + this.prodId = prodId; } - public String getProductName() { - return productName; + public String getProdName() { + return prodName; } - public void setProductName(String productName) { - this.productName = productName; + public void setProdName(String prodName) { + this.prodName = prodName; } public Long getCategoryId() { @@ -62,28 +60,20 @@ public class ProductResponse { this.categoryName = categoryName; } - public String getProductDescription() { - return productDescription; + public String getProdDesc() { + return prodDesc; } - public void setProductDescription(String productDescription) { - this.productDescription = productDescription; + public void setProdDesc(String prodDesc) { + this.prodDesc = prodDesc; } - public BigDecimal getProductPrice() { - return productPrice; + public BigDecimal getProdPrice() { + return prodPrice; } - public void setProductPrice(BigDecimal productPrice) { - this.productPrice = productPrice; - } - - public Boolean getActive() { - return active; - } - - public void setActive(Boolean active) { - this.active = active; + public void setProdPrice(BigDecimal prodPrice) { + this.prodPrice = prodPrice; } public LocalDateTime getCreatedAt() { @@ -107,24 +97,23 @@ public class ProductResponse { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ProductResponse that = (ProductResponse) o; - return Objects.equals(id, that.id) && Objects.equals(productName, that.productName) && Objects.equals(categoryId, that.categoryId) && Objects.equals(categoryName, that.categoryName) && Objects.equals(productDescription, that.productDescription) && Objects.equals(productPrice, that.productPrice) && Objects.equals(active, that.active) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt); + return Objects.equals(prodId, that.prodId) && Objects.equals(prodName, that.prodName) && Objects.equals(categoryId, that.categoryId) && Objects.equals(categoryName, that.categoryName) && Objects.equals(prodDesc, that.prodDesc) && Objects.equals(prodPrice, that.prodPrice) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt); } @Override public int hashCode() { - return Objects.hash(id, productName, categoryId, categoryName, productDescription, productPrice, active, createdAt, updatedAt); + return Objects.hash(prodId, prodName, categoryId, categoryName, prodDesc, prodPrice, createdAt, updatedAt); } @Override public String toString() { return "ProductResponse{" + - "id=" + id + - ", productName='" + productName + '\'' + + "prodId=" + prodId + + ", prodName='" + prodName + '\'' + ", categoryId=" + categoryId + ", categoryName='" + categoryName + '\'' + - ", productDescription='" + productDescription + '\'' + - ", productPrice=" + productPrice + - ", active=" + active + + ", prodDesc='" + prodDesc + '\'' + + ", prodPrice=" + prodPrice + ", createdAt=" + createdAt + ", updatedAt=" + updatedAt + '}'; diff --git a/src/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierRequest.java b/src/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierRequest.java index 7854b37e..1bc05210 100644 --- a/src/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierRequest.java +++ b/src/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierRequest.java @@ -2,7 +2,6 @@ package com.petshop.backend.dto.productsupplier; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Positive; -import jakarta.validation.constraints.PositiveOrZero; import java.math.BigDecimal; import java.util.Objects; @@ -13,14 +12,9 @@ public class ProductSupplierRequest { @NotNull(message = "Supplier ID is required") private Long supplierId; - @NotNull(message = "Cost price is required") - @Positive(message = "Cost price must be positive") - private BigDecimal costPrice; - - @PositiveOrZero(message = "Lead time must be zero or positive") - private Integer leadTimeDays; - - private Boolean isPreferred = false; + @NotNull(message = "Cost is required") + @Positive(message = "Cost must be positive") + private BigDecimal cost; public Long getProductId() { return productId; @@ -38,28 +32,12 @@ public class ProductSupplierRequest { this.supplierId = supplierId; } - public BigDecimal getCostPrice() { - return costPrice; + public BigDecimal getCost() { + return cost; } - public void setCostPrice(BigDecimal costPrice) { - this.costPrice = costPrice; - } - - public Integer getLeadTimeDays() { - return leadTimeDays; - } - - public void setLeadTimeDays(Integer leadTimeDays) { - this.leadTimeDays = leadTimeDays; - } - - public Boolean getIsPreferred() { - return isPreferred; - } - - public void setIsPreferred(Boolean isPreferred) { - this.isPreferred = isPreferred; + public void setCost(BigDecimal cost) { + this.cost = cost; } @Override @@ -69,14 +47,12 @@ public class ProductSupplierRequest { ProductSupplierRequest that = (ProductSupplierRequest) o; return Objects.equals(productId, that.productId) && Objects.equals(supplierId, that.supplierId) && - Objects.equals(costPrice, that.costPrice) && - Objects.equals(leadTimeDays, that.leadTimeDays) && - Objects.equals(isPreferred, that.isPreferred); + Objects.equals(cost, that.cost); } @Override public int hashCode() { - return Objects.hash(productId, supplierId, costPrice, leadTimeDays, isPreferred); + return Objects.hash(productId, supplierId, cost); } @Override @@ -84,9 +60,7 @@ public class ProductSupplierRequest { return "ProductSupplierRequest{" + "productId=" + productId + ", supplierId=" + supplierId + - ", costPrice=" + costPrice + - ", leadTimeDays=" + leadTimeDays + - ", isPreferred=" + isPreferred + + ", cost=" + cost + '}'; } } diff --git a/src/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierResponse.java b/src/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierResponse.java index 44cafe3c..4190dabb 100644 --- a/src/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierResponse.java +++ b/src/main/java/com/petshop/backend/dto/productsupplier/ProductSupplierResponse.java @@ -9,23 +9,19 @@ public class ProductSupplierResponse { private String productName; private Long supplierId; private String supplierName; - private BigDecimal costPrice; - private Integer leadTimeDays; - private Boolean isPreferred; + private BigDecimal cost; private LocalDateTime createdAt; private LocalDateTime updatedAt; public ProductSupplierResponse() { } - public ProductSupplierResponse(Long productId, String productName, Long supplierId, String supplierName, BigDecimal costPrice, Integer leadTimeDays, Boolean isPreferred, LocalDateTime createdAt, LocalDateTime updatedAt) { + public ProductSupplierResponse(Long productId, String productName, Long supplierId, String supplierName, BigDecimal cost, LocalDateTime createdAt, LocalDateTime updatedAt) { this.productId = productId; this.productName = productName; this.supplierId = supplierId; this.supplierName = supplierName; - this.costPrice = costPrice; - this.leadTimeDays = leadTimeDays; - this.isPreferred = isPreferred; + this.cost = cost; this.createdAt = createdAt; this.updatedAt = updatedAt; } @@ -62,28 +58,12 @@ public class ProductSupplierResponse { this.supplierName = supplierName; } - public BigDecimal getCostPrice() { - return costPrice; + public BigDecimal getCost() { + return cost; } - public void setCostPrice(BigDecimal costPrice) { - this.costPrice = costPrice; - } - - public Integer getLeadTimeDays() { - return leadTimeDays; - } - - public void setLeadTimeDays(Integer leadTimeDays) { - this.leadTimeDays = leadTimeDays; - } - - public Boolean getIsPreferred() { - return isPreferred; - } - - public void setIsPreferred(Boolean isPreferred) { - this.isPreferred = isPreferred; + public void setCost(BigDecimal cost) { + this.cost = cost; } public LocalDateTime getCreatedAt() { @@ -107,12 +87,12 @@ public class ProductSupplierResponse { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ProductSupplierResponse that = (ProductSupplierResponse) o; - return Objects.equals(productId, that.productId) && Objects.equals(productName, that.productName) && Objects.equals(supplierId, that.supplierId) && Objects.equals(supplierName, that.supplierName) && Objects.equals(costPrice, that.costPrice) && Objects.equals(leadTimeDays, that.leadTimeDays) && Objects.equals(isPreferred, that.isPreferred) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt); + return Objects.equals(productId, that.productId) && Objects.equals(productName, that.productName) && Objects.equals(supplierId, that.supplierId) && Objects.equals(supplierName, that.supplierName) && Objects.equals(cost, that.cost) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt); } @Override public int hashCode() { - return Objects.hash(productId, productName, supplierId, supplierName, costPrice, leadTimeDays, isPreferred, createdAt, updatedAt); + return Objects.hash(productId, productName, supplierId, supplierName, cost, createdAt, updatedAt); } @Override @@ -122,9 +102,7 @@ public class ProductSupplierResponse { ", productName='" + productName + '\'' + ", supplierId=" + supplierId + ", supplierName='" + supplierName + '\'' + - ", costPrice=" + costPrice + - ", leadTimeDays=" + leadTimeDays + - ", isPreferred=" + isPreferred + + ", cost=" + cost + ", createdAt=" + createdAt + ", updatedAt=" + updatedAt + '}'; diff --git a/src/main/java/com/petshop/backend/dto/purchaseorder/PurchaseOrderResponse.java b/src/main/java/com/petshop/backend/dto/purchaseorder/PurchaseOrderResponse.java index 403aa274..bf575d9d 100644 --- a/src/main/java/com/petshop/backend/dto/purchaseorder/PurchaseOrderResponse.java +++ b/src/main/java/com/petshop/backend/dto/purchaseorder/PurchaseOrderResponse.java @@ -1,55 +1,45 @@ package com.petshop.backend.dto.purchaseorder; -import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; -import java.util.List; import java.util.Objects; public class PurchaseOrderResponse { - private Long id; - private Long supplierId; + private Long purchaseOrderId; + private Long supId; private String supplierName; private LocalDate orderDate; - private LocalDate expectedDelivery; private String status; - private BigDecimal totalAmount; - private String notes; - private List items; private LocalDateTime createdAt; private LocalDateTime updatedAt; public PurchaseOrderResponse() { } - public PurchaseOrderResponse(Long id, Long supplierId, String supplierName, LocalDate orderDate, LocalDate expectedDelivery, String status, BigDecimal totalAmount, String notes, List items, LocalDateTime createdAt, LocalDateTime updatedAt) { - this.id = id; - this.supplierId = supplierId; + public PurchaseOrderResponse(Long purchaseOrderId, Long supId, String supplierName, LocalDate orderDate, String status, LocalDateTime createdAt, LocalDateTime updatedAt) { + this.purchaseOrderId = purchaseOrderId; + this.supId = supId; this.supplierName = supplierName; this.orderDate = orderDate; - this.expectedDelivery = expectedDelivery; this.status = status; - this.totalAmount = totalAmount; - this.notes = notes; - this.items = items; this.createdAt = createdAt; this.updatedAt = updatedAt; } - public Long getId() { - return id; + public Long getPurchaseOrderId() { + return purchaseOrderId; } - public void setId(Long id) { - this.id = id; + public void setPurchaseOrderId(Long purchaseOrderId) { + this.purchaseOrderId = purchaseOrderId; } - public Long getSupplierId() { - return supplierId; + public Long getSupId() { + return supId; } - public void setSupplierId(Long supplierId) { - this.supplierId = supplierId; + public void setSupId(Long supId) { + this.supId = supId; } public String getSupplierName() { @@ -68,14 +58,6 @@ public class PurchaseOrderResponse { this.orderDate = orderDate; } - public LocalDate getExpectedDelivery() { - return expectedDelivery; - } - - public void setExpectedDelivery(LocalDate expectedDelivery) { - this.expectedDelivery = expectedDelivery; - } - public String getStatus() { return status; } @@ -84,30 +66,6 @@ public class PurchaseOrderResponse { this.status = status; } - public BigDecimal getTotalAmount() { - return totalAmount; - } - - public void setTotalAmount(BigDecimal totalAmount) { - this.totalAmount = totalAmount; - } - - public String getNotes() { - return notes; - } - - public void setNotes(String notes) { - this.notes = notes; - } - - public List getItems() { - return items; - } - - public void setItems(List items) { - this.items = items; - } - public LocalDateTime getCreatedAt() { return createdAt; } @@ -129,122 +87,24 @@ public class PurchaseOrderResponse { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; PurchaseOrderResponse that = (PurchaseOrderResponse) o; - return Objects.equals(id, that.id) && Objects.equals(supplierId, that.supplierId) && Objects.equals(supplierName, that.supplierName) && Objects.equals(orderDate, that.orderDate) && Objects.equals(expectedDelivery, that.expectedDelivery) && Objects.equals(status, that.status) && Objects.equals(totalAmount, that.totalAmount) && Objects.equals(notes, that.notes) && Objects.equals(items, that.items) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt); + return Objects.equals(purchaseOrderId, that.purchaseOrderId) && Objects.equals(supId, that.supId) && Objects.equals(supplierName, that.supplierName) && Objects.equals(orderDate, that.orderDate) && Objects.equals(status, that.status) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt); } @Override public int hashCode() { - return Objects.hash(id, supplierId, supplierName, orderDate, expectedDelivery, status, totalAmount, notes, items, createdAt, updatedAt); + return Objects.hash(purchaseOrderId, supId, supplierName, orderDate, status, createdAt, updatedAt); } @Override public String toString() { return "PurchaseOrderResponse{" + - "id=" + id + - ", supplierId=" + supplierId + + "purchaseOrderId=" + purchaseOrderId + + ", supId=" + supId + ", supplierName='" + supplierName + '\'' + ", orderDate=" + orderDate + - ", expectedDelivery=" + expectedDelivery + ", status='" + status + '\'' + - ", totalAmount=" + totalAmount + - ", notes='" + notes + '\'' + - ", items=" + items + ", createdAt=" + createdAt + ", updatedAt=" + updatedAt + '}'; } - - public static class PurchaseOrderItemResponse { - private Long id; - private Long productId; - private String productName; - private Integer quantity; - private BigDecimal unitCost; - private BigDecimal subtotal; - - public PurchaseOrderItemResponse() { - } - - public PurchaseOrderItemResponse(Long id, Long productId, String productName, Integer quantity, BigDecimal unitCost, BigDecimal subtotal) { - this.id = id; - this.productId = productId; - this.productName = productName; - this.quantity = quantity; - this.unitCost = unitCost; - this.subtotal = subtotal; - } - - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public Long getProductId() { - return productId; - } - - public void setProductId(Long productId) { - this.productId = productId; - } - - public String getProductName() { - return productName; - } - - public void setProductName(String productName) { - this.productName = productName; - } - - public Integer getQuantity() { - return quantity; - } - - public void setQuantity(Integer quantity) { - this.quantity = quantity; - } - - public BigDecimal getUnitCost() { - return unitCost; - } - - public void setUnitCost(BigDecimal unitCost) { - this.unitCost = unitCost; - } - - public BigDecimal getSubtotal() { - return subtotal; - } - - public void setSubtotal(BigDecimal subtotal) { - this.subtotal = subtotal; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - PurchaseOrderItemResponse that = (PurchaseOrderItemResponse) o; - return Objects.equals(id, that.id) && Objects.equals(productId, that.productId) && Objects.equals(productName, that.productName) && Objects.equals(quantity, that.quantity) && Objects.equals(unitCost, that.unitCost) && Objects.equals(subtotal, that.subtotal); - } - - @Override - public int hashCode() { - return Objects.hash(id, productId, productName, quantity, unitCost, subtotal); - } - - @Override - public String toString() { - return "PurchaseOrderItemResponse{" + - "id=" + id + - ", productId=" + productId + - ", productName='" + productName + '\'' + - ", quantity=" + quantity + - ", unitCost=" + unitCost + - ", subtotal=" + subtotal + - '}'; - } - } } diff --git a/src/main/java/com/petshop/backend/dto/sale/SaleItemRequest.java b/src/main/java/com/petshop/backend/dto/sale/SaleItemRequest.java index 985aa0c4..94e094c3 100644 --- a/src/main/java/com/petshop/backend/dto/sale/SaleItemRequest.java +++ b/src/main/java/com/petshop/backend/dto/sale/SaleItemRequest.java @@ -6,18 +6,17 @@ import java.util.Objects; public class SaleItemRequest { @NotNull(message = "Product ID is required") - private Long productId; + private Long prodId; @NotNull(message = "Quantity is required") - @Positive(message = "Quantity must be positive") private Integer quantity; - public Long getProductId() { - return productId; + public Long getProdId() { + return prodId; } - public void setProductId(Long productId) { - this.productId = productId; + public void setProdId(Long prodId) { + this.prodId = prodId; } public Integer getQuantity() { @@ -33,19 +32,19 @@ public class SaleItemRequest { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; SaleItemRequest that = (SaleItemRequest) o; - return Objects.equals(productId, that.productId) && + return Objects.equals(prodId, that.prodId) && Objects.equals(quantity, that.quantity); } @Override public int hashCode() { - return Objects.hash(productId, quantity); + return Objects.hash(prodId, quantity); } @Override public String toString() { return "SaleItemRequest{" + - "productId=" + productId + + "prodId=" + prodId + ", quantity=" + quantity + '}'; } diff --git a/src/main/java/com/petshop/backend/dto/sale/SaleRequest.java b/src/main/java/com/petshop/backend/dto/sale/SaleRequest.java index bece02f5..6ea9ff73 100644 --- a/src/main/java/com/petshop/backend/dto/sale/SaleRequest.java +++ b/src/main/java/com/petshop/backend/dto/sale/SaleRequest.java @@ -3,33 +3,22 @@ package com.petshop.backend.dto.sale; import jakarta.validation.Valid; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; -import java.math.BigDecimal; import java.util.List; import java.util.Objects; public class SaleRequest { - private Long customerId; - @NotNull(message = "Store ID is required") private Long storeId; private String paymentMethod; - private BigDecimal tax = BigDecimal.ZERO; - @NotEmpty(message = "At least one item is required") @Valid private List items; - private String notes; + private Boolean isRefund = false; - public Long getCustomerId() { - return customerId; - } - - public void setCustomerId(Long customerId) { - this.customerId = customerId; - } + private Long originalSaleId; public Long getStoreId() { return storeId; @@ -47,14 +36,6 @@ public class SaleRequest { this.paymentMethod = paymentMethod; } - public BigDecimal getTax() { - return tax; - } - - public void setTax(BigDecimal tax) { - this.tax = tax; - } - public List getItems() { return items; } @@ -63,12 +44,20 @@ public class SaleRequest { this.items = items; } - public String getNotes() { - return notes; + public Boolean getIsRefund() { + return isRefund; } - public void setNotes(String notes) { - this.notes = notes; + public void setIsRefund(Boolean isRefund) { + this.isRefund = isRefund; + } + + public Long getOriginalSaleId() { + return originalSaleId; + } + + public void setOriginalSaleId(Long originalSaleId) { + this.originalSaleId = originalSaleId; } @Override @@ -76,28 +65,26 @@ public class SaleRequest { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; SaleRequest that = (SaleRequest) o; - return Objects.equals(customerId, that.customerId) && - Objects.equals(storeId, that.storeId) && + return Objects.equals(storeId, that.storeId) && Objects.equals(paymentMethod, that.paymentMethod) && - Objects.equals(tax, that.tax) && Objects.equals(items, that.items) && - Objects.equals(notes, that.notes); + Objects.equals(isRefund, that.isRefund) && + Objects.equals(originalSaleId, that.originalSaleId); } @Override public int hashCode() { - return Objects.hash(customerId, storeId, paymentMethod, tax, items, notes); + return Objects.hash(storeId, paymentMethod, items, isRefund, originalSaleId); } @Override public String toString() { return "SaleRequest{" + - "customerId=" + customerId + - ", storeId=" + storeId + + "storeId=" + storeId + ", paymentMethod='" + paymentMethod + '\'' + - ", tax=" + tax + ", items=" + items + - ", notes='" + notes + '\'' + + ", isRefund=" + isRefund + + ", originalSaleId=" + originalSaleId + '}'; } } diff --git a/src/main/java/com/petshop/backend/dto/sale/SaleResponse.java b/src/main/java/com/petshop/backend/dto/sale/SaleResponse.java index b8634308..969b28d3 100644 --- a/src/main/java/com/petshop/backend/dto/sale/SaleResponse.java +++ b/src/main/java/com/petshop/backend/dto/sale/SaleResponse.java @@ -6,49 +6,43 @@ import java.util.List; import java.util.Objects; public class SaleResponse { - private Long id; + private Long saleId; private LocalDateTime saleDate; private Long employeeId; private String employeeName; - private Long customerId; - private String customerName; private Long storeId; private String storeName; - private BigDecimal subtotal; - private BigDecimal tax; - private BigDecimal total; + private BigDecimal totalAmount; private String paymentMethod; - private String notes; + private Boolean isRefund; + private Long originalSaleId; private List items; private LocalDateTime createdAt; public SaleResponse() { } - public SaleResponse(Long id, LocalDateTime saleDate, Long employeeId, String employeeName, Long customerId, String customerName, Long storeId, String storeName, BigDecimal subtotal, BigDecimal tax, BigDecimal total, String paymentMethod, String notes, List items, LocalDateTime createdAt) { - this.id = id; + public SaleResponse(Long saleId, LocalDateTime saleDate, Long employeeId, String employeeName, Long storeId, String storeName, BigDecimal totalAmount, String paymentMethod, Boolean isRefund, Long originalSaleId, List items, LocalDateTime createdAt) { + this.saleId = saleId; this.saleDate = saleDate; this.employeeId = employeeId; this.employeeName = employeeName; - this.customerId = customerId; - this.customerName = customerName; this.storeId = storeId; this.storeName = storeName; - this.subtotal = subtotal; - this.tax = tax; - this.total = total; + this.totalAmount = totalAmount; this.paymentMethod = paymentMethod; - this.notes = notes; + this.isRefund = isRefund; + this.originalSaleId = originalSaleId; this.items = items; this.createdAt = createdAt; } - public Long getId() { - return id; + public Long getSaleId() { + return saleId; } - public void setId(Long id) { - this.id = id; + public void setSaleId(Long saleId) { + this.saleId = saleId; } public LocalDateTime getSaleDate() { @@ -75,22 +69,6 @@ public class SaleResponse { this.employeeName = employeeName; } - public Long getCustomerId() { - return customerId; - } - - public void setCustomerId(Long customerId) { - this.customerId = customerId; - } - - public String getCustomerName() { - return customerName; - } - - public void setCustomerName(String customerName) { - this.customerName = customerName; - } - public Long getStoreId() { return storeId; } @@ -107,28 +85,12 @@ public class SaleResponse { this.storeName = storeName; } - public BigDecimal getSubtotal() { - return subtotal; + public BigDecimal getTotalAmount() { + return totalAmount; } - public void setSubtotal(BigDecimal subtotal) { - this.subtotal = subtotal; - } - - public BigDecimal getTax() { - return tax; - } - - public void setTax(BigDecimal tax) { - this.tax = tax; - } - - public BigDecimal getTotal() { - return total; - } - - public void setTotal(BigDecimal total) { - this.total = total; + public void setTotalAmount(BigDecimal totalAmount) { + this.totalAmount = totalAmount; } public String getPaymentMethod() { @@ -139,12 +101,20 @@ public class SaleResponse { this.paymentMethod = paymentMethod; } - public String getNotes() { - return notes; + public Boolean getIsRefund() { + return isRefund; } - public void setNotes(String notes) { - this.notes = notes; + public void setIsRefund(Boolean isRefund) { + this.isRefund = isRefund; + } + + public Long getOriginalSaleId() { + return originalSaleId; + } + + public void setOriginalSaleId(Long originalSaleId) { + this.originalSaleId = originalSaleId; } public List getItems() { @@ -168,69 +138,64 @@ public class SaleResponse { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; SaleResponse that = (SaleResponse) o; - return Objects.equals(id, that.id) && Objects.equals(saleDate, that.saleDate) && Objects.equals(employeeId, that.employeeId) && Objects.equals(employeeName, that.employeeName) && Objects.equals(customerId, that.customerId) && Objects.equals(customerName, that.customerName) && Objects.equals(storeId, that.storeId) && Objects.equals(storeName, that.storeName) && Objects.equals(subtotal, that.subtotal) && Objects.equals(tax, that.tax) && Objects.equals(total, that.total) && Objects.equals(paymentMethod, that.paymentMethod) && Objects.equals(notes, that.notes) && Objects.equals(items, that.items) && Objects.equals(createdAt, that.createdAt); + return Objects.equals(saleId, that.saleId) && Objects.equals(saleDate, that.saleDate) && Objects.equals(employeeId, that.employeeId) && Objects.equals(employeeName, that.employeeName) && Objects.equals(storeId, that.storeId) && Objects.equals(storeName, that.storeName) && Objects.equals(totalAmount, that.totalAmount) && Objects.equals(paymentMethod, that.paymentMethod) && Objects.equals(isRefund, that.isRefund) && Objects.equals(originalSaleId, that.originalSaleId) && Objects.equals(items, that.items) && Objects.equals(createdAt, that.createdAt); } @Override public int hashCode() { - return Objects.hash(id, saleDate, employeeId, employeeName, customerId, customerName, storeId, storeName, subtotal, tax, total, paymentMethod, notes, items, createdAt); + return Objects.hash(saleId, saleDate, employeeId, employeeName, storeId, storeName, totalAmount, paymentMethod, isRefund, originalSaleId, items, createdAt); } @Override public String toString() { return "SaleResponse{" + - "id=" + id + + "saleId=" + saleId + ", saleDate=" + saleDate + ", employeeId=" + employeeId + ", employeeName='" + employeeName + '\'' + - ", customerId=" + customerId + - ", customerName='" + customerName + '\'' + ", storeId=" + storeId + ", storeName='" + storeName + '\'' + - ", subtotal=" + subtotal + - ", tax=" + tax + - ", total=" + total + + ", totalAmount=" + totalAmount + ", paymentMethod='" + paymentMethod + '\'' + - ", notes='" + notes + '\'' + + ", isRefund=" + isRefund + + ", originalSaleId=" + originalSaleId + ", items=" + items + ", createdAt=" + createdAt + '}'; } public static class SaleItemResponse { - private Long id; - private Long productId; + private Long saleItemId; + private Long prodId; private String productName; private Integer quantity; private BigDecimal unitPrice; - private BigDecimal subtotal; public SaleItemResponse() { } - public SaleItemResponse(Long id, Long productId, String productName, Integer quantity, BigDecimal unitPrice, BigDecimal subtotal) { - this.id = id; - this.productId = productId; + public SaleItemResponse(Long saleItemId, Long prodId, String productName, Integer quantity, BigDecimal unitPrice) { + this.saleItemId = saleItemId; + this.prodId = prodId; this.productName = productName; this.quantity = quantity; this.unitPrice = unitPrice; - this.subtotal = subtotal; } - public Long getId() { - return id; + public Long getSaleItemId() { + return saleItemId; } - public void setId(Long id) { - this.id = id; + public void setSaleItemId(Long saleItemId) { + this.saleItemId = saleItemId; } - public Long getProductId() { - return productId; + public Long getProdId() { + return prodId; } - public void setProductId(Long productId) { - this.productId = productId; + public void setProdId(Long prodId) { + this.prodId = prodId; } public String getProductName() { @@ -257,36 +222,27 @@ public class SaleResponse { this.unitPrice = unitPrice; } - public BigDecimal getSubtotal() { - return subtotal; - } - - public void setSubtotal(BigDecimal subtotal) { - this.subtotal = subtotal; - } - @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; SaleItemResponse that = (SaleItemResponse) o; - return Objects.equals(id, that.id) && Objects.equals(productId, that.productId) && Objects.equals(productName, that.productName) && Objects.equals(quantity, that.quantity) && Objects.equals(unitPrice, that.unitPrice) && Objects.equals(subtotal, that.subtotal); + return Objects.equals(saleItemId, that.saleItemId) && Objects.equals(prodId, that.prodId) && Objects.equals(productName, that.productName) && Objects.equals(quantity, that.quantity) && Objects.equals(unitPrice, that.unitPrice); } @Override public int hashCode() { - return Objects.hash(id, productId, productName, quantity, unitPrice, subtotal); + return Objects.hash(saleItemId, prodId, productName, quantity, unitPrice); } @Override public String toString() { return "SaleItemResponse{" + - "id=" + id + - ", productId=" + productId + + "saleItemId=" + saleItemId + + ", prodId=" + prodId + ", productName='" + productName + '\'' + ", quantity=" + quantity + ", unitPrice=" + unitPrice + - ", subtotal=" + subtotal + '}'; } } diff --git a/src/main/java/com/petshop/backend/dto/service/ServiceRequest.java b/src/main/java/com/petshop/backend/dto/service/ServiceRequest.java index 123d704e..6b4550ec 100644 --- a/src/main/java/com/petshop/backend/dto/service/ServiceRequest.java +++ b/src/main/java/com/petshop/backend/dto/service/ServiceRequest.java @@ -10,16 +10,14 @@ public class ServiceRequest { @NotBlank(message = "Service name is required") private String serviceName; - private String serviceDescription; + private String serviceDesc; @NotNull(message = "Service price is required") @Positive(message = "Price must be positive") private BigDecimal servicePrice; @Positive(message = "Duration must be positive") - private Integer serviceDurationMinutes; - - private Boolean active = true; + private Integer serviceDuration; public String getServiceName() { return serviceName; @@ -29,12 +27,12 @@ public class ServiceRequest { this.serviceName = serviceName; } - public String getServiceDescription() { - return serviceDescription; + public String getServiceDesc() { + return serviceDesc; } - public void setServiceDescription(String serviceDescription) { - this.serviceDescription = serviceDescription; + public void setServiceDesc(String serviceDesc) { + this.serviceDesc = serviceDesc; } public BigDecimal getServicePrice() { @@ -45,20 +43,12 @@ public class ServiceRequest { this.servicePrice = servicePrice; } - public Integer getServiceDurationMinutes() { - return serviceDurationMinutes; + public Integer getServiceDuration() { + return serviceDuration; } - public void setServiceDurationMinutes(Integer serviceDurationMinutes) { - this.serviceDurationMinutes = serviceDurationMinutes; - } - - public Boolean getActive() { - return active; - } - - public void setActive(Boolean active) { - this.active = active; + public void setServiceDuration(Integer serviceDuration) { + this.serviceDuration = serviceDuration; } @Override @@ -67,25 +57,23 @@ public class ServiceRequest { if (o == null || getClass() != o.getClass()) return false; ServiceRequest that = (ServiceRequest) o; return Objects.equals(serviceName, that.serviceName) && - Objects.equals(serviceDescription, that.serviceDescription) && + Objects.equals(serviceDesc, that.serviceDesc) && Objects.equals(servicePrice, that.servicePrice) && - Objects.equals(serviceDurationMinutes, that.serviceDurationMinutes) && - Objects.equals(active, that.active); + Objects.equals(serviceDuration, that.serviceDuration); } @Override public int hashCode() { - return Objects.hash(serviceName, serviceDescription, servicePrice, serviceDurationMinutes, active); + return Objects.hash(serviceName, serviceDesc, servicePrice, serviceDuration); } @Override public String toString() { return "ServiceRequest{" + "serviceName='" + serviceName + '\'' + - ", serviceDescription='" + serviceDescription + '\'' + + ", serviceDesc='" + serviceDesc + '\'' + ", servicePrice=" + servicePrice + - ", serviceDurationMinutes=" + serviceDurationMinutes + - ", active=" + active + + ", serviceDuration=" + serviceDuration + '}'; } } diff --git a/src/main/java/com/petshop/backend/dto/service/ServiceResponse.java b/src/main/java/com/petshop/backend/dto/service/ServiceResponse.java index fd7e12ad..53a2be5b 100644 --- a/src/main/java/com/petshop/backend/dto/service/ServiceResponse.java +++ b/src/main/java/com/petshop/backend/dto/service/ServiceResponse.java @@ -5,35 +5,33 @@ import java.time.LocalDateTime; import java.util.Objects; public class ServiceResponse { - private Long id; + private Long serviceId; private String serviceName; - private String serviceDescription; + private String serviceDesc; private BigDecimal servicePrice; - private Integer serviceDurationMinutes; - private Boolean active; + private Integer serviceDuration; private LocalDateTime createdAt; private LocalDateTime updatedAt; public ServiceResponse() { } - public ServiceResponse(Long id, String serviceName, String serviceDescription, BigDecimal servicePrice, Integer serviceDurationMinutes, Boolean active, LocalDateTime createdAt, LocalDateTime updatedAt) { - this.id = id; + public ServiceResponse(Long serviceId, String serviceName, String serviceDesc, BigDecimal servicePrice, Integer serviceDuration, LocalDateTime createdAt, LocalDateTime updatedAt) { + this.serviceId = serviceId; this.serviceName = serviceName; - this.serviceDescription = serviceDescription; + this.serviceDesc = serviceDesc; this.servicePrice = servicePrice; - this.serviceDurationMinutes = serviceDurationMinutes; - this.active = active; + this.serviceDuration = serviceDuration; this.createdAt = createdAt; this.updatedAt = updatedAt; } - public Long getId() { - return id; + public Long getServiceId() { + return serviceId; } - public void setId(Long id) { - this.id = id; + public void setServiceId(Long serviceId) { + this.serviceId = serviceId; } public String getServiceName() { @@ -44,12 +42,12 @@ public class ServiceResponse { this.serviceName = serviceName; } - public String getServiceDescription() { - return serviceDescription; + public String getServiceDesc() { + return serviceDesc; } - public void setServiceDescription(String serviceDescription) { - this.serviceDescription = serviceDescription; + public void setServiceDesc(String serviceDesc) { + this.serviceDesc = serviceDesc; } public BigDecimal getServicePrice() { @@ -60,20 +58,12 @@ public class ServiceResponse { this.servicePrice = servicePrice; } - public Integer getServiceDurationMinutes() { - return serviceDurationMinutes; + public Integer getServiceDuration() { + return serviceDuration; } - public void setServiceDurationMinutes(Integer serviceDurationMinutes) { - this.serviceDurationMinutes = serviceDurationMinutes; - } - - public Boolean getActive() { - return active; - } - - public void setActive(Boolean active) { - this.active = active; + public void setServiceDuration(Integer serviceDuration) { + this.serviceDuration = serviceDuration; } public LocalDateTime getCreatedAt() { @@ -97,23 +87,22 @@ public class ServiceResponse { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ServiceResponse that = (ServiceResponse) o; - return Objects.equals(id, that.id) && Objects.equals(serviceName, that.serviceName) && Objects.equals(serviceDescription, that.serviceDescription) && Objects.equals(servicePrice, that.servicePrice) && Objects.equals(serviceDurationMinutes, that.serviceDurationMinutes) && Objects.equals(active, that.active) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt); + return Objects.equals(serviceId, that.serviceId) && Objects.equals(serviceName, that.serviceName) && Objects.equals(serviceDesc, that.serviceDesc) && Objects.equals(servicePrice, that.servicePrice) && Objects.equals(serviceDuration, that.serviceDuration) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt); } @Override public int hashCode() { - return Objects.hash(id, serviceName, serviceDescription, servicePrice, serviceDurationMinutes, active, createdAt, updatedAt); + return Objects.hash(serviceId, serviceName, serviceDesc, servicePrice, serviceDuration, createdAt, updatedAt); } @Override public String toString() { return "ServiceResponse{" + - "id=" + id + + "serviceId=" + serviceId + ", serviceName='" + serviceName + '\'' + - ", serviceDescription='" + serviceDescription + '\'' + + ", serviceDesc='" + serviceDesc + '\'' + ", servicePrice=" + servicePrice + - ", serviceDurationMinutes=" + serviceDurationMinutes + - ", active=" + active + + ", serviceDuration=" + serviceDuration + ", createdAt=" + createdAt + ", updatedAt=" + updatedAt + '}'; diff --git a/src/main/java/com/petshop/backend/dto/supplier/SupplierRequest.java b/src/main/java/com/petshop/backend/dto/supplier/SupplierRequest.java index f7881ffa..b7ae7efb 100644 --- a/src/main/java/com/petshop/backend/dto/supplier/SupplierRequest.java +++ b/src/main/java/com/petshop/backend/dto/supplier/SupplierRequest.java @@ -5,32 +5,40 @@ import jakarta.validation.constraints.NotBlank; import java.util.Objects; public class SupplierRequest { - @NotBlank(message = "Supplier name is required") - private String supName; + @NotBlank(message = "Supplier company is required") + private String supCompany; - private String supContact; + private String supContactFirstName; + + private String supContactLastName; @Email(message = "Invalid email format") private String supEmail; private String supPhone; - private String supAddress; - private Boolean active = true; - public String getSupName() { - return supName; + public String getSupCompany() { + return supCompany; } - public void setSupName(String supName) { - this.supName = supName; + public void setSupCompany(String supCompany) { + this.supCompany = supCompany; } - public String getSupContact() { - return supContact; + public String getSupContactFirstName() { + return supContactFirstName; } - public void setSupContact(String supContact) { - this.supContact = supContact; + public void setSupContactFirstName(String supContactFirstName) { + this.supContactFirstName = supContactFirstName; + } + + public String getSupContactLastName() { + return supContactLastName; + } + + public void setSupContactLastName(String supContactLastName) { + this.supContactLastName = supContactLastName; } public String getSupEmail() { @@ -49,49 +57,31 @@ public class SupplierRequest { this.supPhone = supPhone; } - public String getSupAddress() { - return supAddress; - } - - public void setSupAddress(String supAddress) { - this.supAddress = supAddress; - } - - public Boolean getActive() { - return active; - } - - public void setActive(Boolean active) { - this.active = active; - } - @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; SupplierRequest that = (SupplierRequest) o; - return Objects.equals(supName, that.supName) && - Objects.equals(supContact, that.supContact) && + return Objects.equals(supCompany, that.supCompany) && + Objects.equals(supContactFirstName, that.supContactFirstName) && + Objects.equals(supContactLastName, that.supContactLastName) && Objects.equals(supEmail, that.supEmail) && - Objects.equals(supPhone, that.supPhone) && - Objects.equals(supAddress, that.supAddress) && - Objects.equals(active, that.active); + Objects.equals(supPhone, that.supPhone); } @Override public int hashCode() { - return Objects.hash(supName, supContact, supEmail, supPhone, supAddress, active); + return Objects.hash(supCompany, supContactFirstName, supContactLastName, supEmail, supPhone); } @Override public String toString() { return "SupplierRequest{" + - "supName='" + supName + '\'' + - ", supContact='" + supContact + '\'' + + "supCompany='" + supCompany + '\'' + + ", supContactFirstName='" + supContactFirstName + '\'' + + ", supContactLastName='" + supContactLastName + '\'' + ", supEmail='" + supEmail + '\'' + ", supPhone='" + supPhone + '\'' + - ", supAddress='" + supAddress + '\'' + - ", active=" + active + '}'; } } diff --git a/src/main/java/com/petshop/backend/dto/supplier/SupplierResponse.java b/src/main/java/com/petshop/backend/dto/supplier/SupplierResponse.java index 348b7f81..d23b4a2b 100644 --- a/src/main/java/com/petshop/backend/dto/supplier/SupplierResponse.java +++ b/src/main/java/com/petshop/backend/dto/supplier/SupplierResponse.java @@ -4,85 +4,75 @@ import java.time.LocalDateTime; import java.util.Objects; public class SupplierResponse { - private Long id; - private String supplierName; - private String supplierContact; - private String supplierEmail; - private String supplierPhone; - private String supplierAddress; - private Boolean active; + private Long supId; + private String supCompany; + private String supContactFirstName; + private String supContactLastName; + private String supEmail; + private String supPhone; private LocalDateTime createdAt; private LocalDateTime updatedAt; public SupplierResponse() { } - public SupplierResponse(Long id, String supplierName, String supplierContact, String supplierEmail, String supplierPhone, String supplierAddress, Boolean active, LocalDateTime createdAt, LocalDateTime updatedAt) { - this.id = id; - this.supplierName = supplierName; - this.supplierContact = supplierContact; - this.supplierEmail = supplierEmail; - this.supplierPhone = supplierPhone; - this.supplierAddress = supplierAddress; - this.active = active; + public SupplierResponse(Long supId, String supCompany, String supContactFirstName, String supContactLastName, String supEmail, String supPhone, LocalDateTime createdAt, LocalDateTime updatedAt) { + this.supId = supId; + this.supCompany = supCompany; + this.supContactFirstName = supContactFirstName; + this.supContactLastName = supContactLastName; + this.supEmail = supEmail; + this.supPhone = supPhone; this.createdAt = createdAt; this.updatedAt = updatedAt; } - public Long getId() { - return id; + public Long getSupId() { + return supId; } - public void setId(Long id) { - this.id = id; + public void setSupId(Long supId) { + this.supId = supId; } - public String getSupplierName() { - return supplierName; + public String getSupCompany() { + return supCompany; } - public void setSupplierName(String supplierName) { - this.supplierName = supplierName; + public void setSupCompany(String supCompany) { + this.supCompany = supCompany; } - public String getSupplierContact() { - return supplierContact; + public String getSupContactFirstName() { + return supContactFirstName; } - public void setSupplierContact(String supplierContact) { - this.supplierContact = supplierContact; + public void setSupContactFirstName(String supContactFirstName) { + this.supContactFirstName = supContactFirstName; } - public String getSupplierEmail() { - return supplierEmail; + public String getSupContactLastName() { + return supContactLastName; } - public void setSupplierEmail(String supplierEmail) { - this.supplierEmail = supplierEmail; + public void setSupContactLastName(String supContactLastName) { + this.supContactLastName = supContactLastName; } - public String getSupplierPhone() { - return supplierPhone; + public String getSupEmail() { + return supEmail; } - public void setSupplierPhone(String supplierPhone) { - this.supplierPhone = supplierPhone; + public void setSupEmail(String supEmail) { + this.supEmail = supEmail; } - public String getSupplierAddress() { - return supplierAddress; + public String getSupPhone() { + return supPhone; } - public void setSupplierAddress(String supplierAddress) { - this.supplierAddress = supplierAddress; - } - - public Boolean getActive() { - return active; - } - - public void setActive(Boolean active) { - this.active = active; + public void setSupPhone(String supPhone) { + this.supPhone = supPhone; } public LocalDateTime getCreatedAt() { @@ -106,24 +96,23 @@ public class SupplierResponse { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; SupplierResponse that = (SupplierResponse) o; - return Objects.equals(id, that.id) && Objects.equals(supplierName, that.supplierName) && Objects.equals(supplierContact, that.supplierContact) && Objects.equals(supplierEmail, that.supplierEmail) && Objects.equals(supplierPhone, that.supplierPhone) && Objects.equals(supplierAddress, that.supplierAddress) && Objects.equals(active, that.active) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt); + return Objects.equals(supId, that.supId) && Objects.equals(supCompany, that.supCompany) && Objects.equals(supContactFirstName, that.supContactFirstName) && Objects.equals(supContactLastName, that.supContactLastName) && Objects.equals(supEmail, that.supEmail) && Objects.equals(supPhone, that.supPhone) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt); } @Override public int hashCode() { - return Objects.hash(id, supplierName, supplierContact, supplierEmail, supplierPhone, supplierAddress, active, createdAt, updatedAt); + return Objects.hash(supId, supCompany, supContactFirstName, supContactLastName, supEmail, supPhone, createdAt, updatedAt); } @Override public String toString() { return "SupplierResponse{" + - "id=" + id + - ", supplierName='" + supplierName + '\'' + - ", supplierContact='" + supplierContact + '\'' + - ", supplierEmail='" + supplierEmail + '\'' + - ", supplierPhone='" + supplierPhone + '\'' + - ", supplierAddress='" + supplierAddress + '\'' + - ", active=" + active + + "supId=" + supId + + ", supCompany='" + supCompany + '\'' + + ", supContactFirstName='" + supContactFirstName + '\'' + + ", supContactLastName='" + supContactLastName + '\'' + + ", supEmail='" + supEmail + '\'' + + ", supPhone='" + supPhone + '\'' + ", createdAt=" + createdAt + ", updatedAt=" + updatedAt + '}'; diff --git a/src/main/java/com/petshop/backend/entity/EmployeeStore.java b/src/main/java/com/petshop/backend/entity/EmployeeStore.java index d6d29b98..daa2a2e2 100644 --- a/src/main/java/com/petshop/backend/entity/EmployeeStore.java +++ b/src/main/java/com/petshop/backend/entity/EmployeeStore.java @@ -1,11 +1,8 @@ package com.petshop.backend.entity; import jakarta.persistence.*; -import org.hibernate.annotations.CreationTimestamp; -import org.hibernate.annotations.UpdateTimestamp; import java.io.Serializable; -import java.time.LocalDateTime; import java.util.Objects; @Entity @@ -23,22 +20,12 @@ public class EmployeeStore { @JoinColumn(name = "storeId", nullable = false) private StoreLocation store; - @CreationTimestamp - @Column(name = "created_at", updatable = false) - private LocalDateTime createdAt; - - @UpdateTimestamp - @Column(name = "updated_at") - private LocalDateTime updatedAt; - public EmployeeStore() { } - public EmployeeStore(Employee employee, StoreLocation store, LocalDateTime createdAt, LocalDateTime updatedAt) { + public EmployeeStore(Employee employee, StoreLocation store) { this.employee = employee; this.store = store; - this.createdAt = createdAt; - this.updatedAt = updatedAt; } public Employee getEmployee() { @@ -57,22 +44,6 @@ public class EmployeeStore { this.store = store; } - public LocalDateTime getCreatedAt() { - return createdAt; - } - - public void setCreatedAt(LocalDateTime createdAt) { - this.createdAt = createdAt; - } - - public LocalDateTime getUpdatedAt() { - return updatedAt; - } - - public void setUpdatedAt(LocalDateTime updatedAt) { - this.updatedAt = updatedAt; - } - @Override public boolean equals(Object o) { if (this == o) return true; @@ -91,8 +62,6 @@ public class EmployeeStore { return "EmployeeStore{" + "employee=" + employee + ", store=" + store + - ", createdAt=" + createdAt + - ", updatedAt=" + updatedAt + '}'; } diff --git a/src/main/java/com/petshop/backend/entity/PurchaseOrderItem.java b/src/main/java/com/petshop/backend/entity/PurchaseOrderItem.java deleted file mode 100644 index 6278d3f9..00000000 --- a/src/main/java/com/petshop/backend/entity/PurchaseOrderItem.java +++ /dev/null @@ -1,117 +0,0 @@ -package com.petshop.backend.entity; - -import jakarta.persistence.*; - -import java.math.BigDecimal; -import java.util.Objects; - -@Entity -@Table(name = "purchase_order_items") -public class PurchaseOrderItem { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @ManyToOne - @JoinColumn(name = "purchase_order_id", nullable = false) - private PurchaseOrder purchaseOrder; - - @ManyToOne - @JoinColumn(name = "product_id", nullable = false) - private Product product; - - @Column(nullable = false) - private Integer quantity; - - @Column(name = "unit_cost", nullable = false, precision = 10, scale = 2) - private BigDecimal unitCost; - - @Column(nullable = false, precision = 10, scale = 2) - private BigDecimal subtotal; - - public PurchaseOrderItem() { - } - - public PurchaseOrderItem(Long id, PurchaseOrder purchaseOrder, Product product, Integer quantity, BigDecimal unitCost, BigDecimal subtotal) { - this.id = id; - this.purchaseOrder = purchaseOrder; - this.product = product; - this.quantity = quantity; - this.unitCost = unitCost; - this.subtotal = subtotal; - } - - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public PurchaseOrder getPurchaseOrder() { - return purchaseOrder; - } - - public void setPurchaseOrder(PurchaseOrder purchaseOrder) { - this.purchaseOrder = purchaseOrder; - } - - public Product getProduct() { - return product; - } - - public void setProduct(Product product) { - this.product = product; - } - - public Integer getQuantity() { - return quantity; - } - - public void setQuantity(Integer quantity) { - this.quantity = quantity; - } - - public BigDecimal getUnitCost() { - return unitCost; - } - - public void setUnitCost(BigDecimal unitCost) { - this.unitCost = unitCost; - } - - public BigDecimal getSubtotal() { - return subtotal; - } - - public void setSubtotal(BigDecimal subtotal) { - this.subtotal = subtotal; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - PurchaseOrderItem that = (PurchaseOrderItem) o; - return Objects.equals(id, that.id); - } - - @Override - public int hashCode() { - return Objects.hash(id); - } - - @Override - public String toString() { - return "PurchaseOrderItem{" + - "id=" + id + - ", purchaseOrder=" + purchaseOrder + - ", product=" + product + - ", quantity=" + quantity + - ", unitCost=" + unitCost + - ", subtotal=" + subtotal + - '}'; - } -} diff --git a/src/main/java/com/petshop/backend/entity/Sale.java b/src/main/java/com/petshop/backend/entity/Sale.java index 1874d439..7f844c39 100644 --- a/src/main/java/com/petshop/backend/entity/Sale.java +++ b/src/main/java/com/petshop/backend/entity/Sale.java @@ -2,6 +2,7 @@ package com.petshop.backend.entity; import jakarta.persistence.*; import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; import java.math.BigDecimal; import java.time.LocalDateTime; @@ -48,10 +49,14 @@ public class Sale { @Column(name = "created_at", updatable = false) private LocalDateTime createdAt; + @UpdateTimestamp + @Column(name = "updated_at") + private LocalDateTime updatedAt; + public Sale() { } - public Sale(Long saleId, LocalDateTime saleDate, Employee employee, StoreLocation store, BigDecimal totalAmount, String paymentMethod, Boolean isRefund, Sale originalSale, List items, LocalDateTime createdAt) { + public Sale(Long saleId, LocalDateTime saleDate, Employee employee, StoreLocation store, BigDecimal totalAmount, String paymentMethod, Boolean isRefund, Sale originalSale, List items, LocalDateTime createdAt, LocalDateTime updatedAt) { this.saleId = saleId; this.saleDate = saleDate; this.employee = employee; @@ -62,6 +67,7 @@ public class Sale { this.originalSale = originalSale; this.items = items; this.createdAt = createdAt; + this.updatedAt = updatedAt; } public Long getSaleId() { @@ -144,6 +150,14 @@ public class Sale { this.createdAt = createdAt; } + public LocalDateTime getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(LocalDateTime updatedAt) { + this.updatedAt = updatedAt; + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -170,6 +184,7 @@ public class Sale { ", originalSale=" + originalSale + ", items=" + items + ", createdAt=" + createdAt + + ", updatedAt=" + updatedAt + '}'; } } diff --git a/src/main/java/com/petshop/backend/entity/SaleItem.java b/src/main/java/com/petshop/backend/entity/SaleItem.java index 3738b29a..b80ab370 100644 --- a/src/main/java/com/petshop/backend/entity/SaleItem.java +++ b/src/main/java/com/petshop/backend/entity/SaleItem.java @@ -1,8 +1,11 @@ package com.petshop.backend.entity; import jakarta.persistence.*; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; import java.math.BigDecimal; +import java.time.LocalDateTime; import java.util.Objects; @Entity @@ -27,15 +30,25 @@ public class SaleItem { @Column(nullable = false, precision = 10, scale = 2) private BigDecimal unitPrice; + @CreationTimestamp + @Column(name = "created_at", updatable = false) + private LocalDateTime createdAt; + + @UpdateTimestamp + @Column(name = "updated_at") + private LocalDateTime updatedAt; + public SaleItem() { } - public SaleItem(Long saleItemId, Sale sale, Product product, Integer quantity, BigDecimal unitPrice) { + public SaleItem(Long saleItemId, Sale sale, Product product, Integer quantity, BigDecimal unitPrice, LocalDateTime createdAt, LocalDateTime updatedAt) { this.saleItemId = saleItemId; this.sale = sale; this.product = product; this.quantity = quantity; this.unitPrice = unitPrice; + this.createdAt = createdAt; + this.updatedAt = updatedAt; } public Long getSaleItemId() { @@ -78,6 +91,22 @@ public class SaleItem { this.unitPrice = unitPrice; } + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(LocalDateTime createdAt) { + this.createdAt = createdAt; + } + + public LocalDateTime getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(LocalDateTime updatedAt) { + this.updatedAt = updatedAt; + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -99,6 +128,8 @@ public class SaleItem { ", product=" + product + ", quantity=" + quantity + ", unitPrice=" + unitPrice + + ", createdAt=" + createdAt + + ", updatedAt=" + updatedAt + '}'; } } diff --git a/src/main/java/com/petshop/backend/entity/User.java b/src/main/java/com/petshop/backend/entity/User.java index 7d6901a8..a8c42d8f 100644 --- a/src/main/java/com/petshop/backend/entity/User.java +++ b/src/main/java/com/petshop/backend/entity/User.java @@ -21,19 +21,10 @@ public class User { @Column(nullable = false) private String password; - @Column(name = "full_name", nullable = false, length = 100) - private String fullName; - - @Column(length = 100) - private String email; - @Enumerated(EnumType.STRING) - @Column(nullable = false) + @Column(nullable = false, length = 20, columnDefinition = "VARCHAR(20)") private Role role; - @Column(nullable = false) - private Boolean active = true; - @CreationTimestamp @Column(name = "created_at", updatable = false) private LocalDateTime createdAt; @@ -49,14 +40,11 @@ public class User { public User() { } - public User(Long id, String username, String password, String fullName, String email, Role role, Boolean active, LocalDateTime createdAt, LocalDateTime updatedAt) { + public User(Long id, String username, String password, Role role, LocalDateTime createdAt, LocalDateTime updatedAt) { this.id = id; this.username = username; this.password = password; - this.fullName = fullName; - this.email = email; this.role = role; - this.active = active; this.createdAt = createdAt; this.updatedAt = updatedAt; } @@ -85,22 +73,6 @@ public class User { this.password = password; } - public String getFullName() { - return fullName; - } - - public void setFullName(String fullName) { - this.fullName = fullName; - } - - public String getEmail() { - return email; - } - - public void setEmail(String email) { - this.email = email; - } - public Role getRole() { return role; } @@ -109,14 +81,6 @@ public class User { this.role = role; } - public Boolean getActive() { - return active; - } - - public void setActive(Boolean active) { - this.active = active; - } - public LocalDateTime getCreatedAt() { return createdAt; } @@ -152,10 +116,7 @@ public class User { "id=" + id + ", username='" + username + '\'' + ", password='" + password + '\'' + - ", fullName='" + fullName + '\'' + - ", email='" + email + '\'' + ", role=" + role + - ", active=" + active + ", createdAt=" + createdAt + ", updatedAt=" + updatedAt + '}'; diff --git a/src/main/java/com/petshop/backend/repository/AdoptionRepository.java b/src/main/java/com/petshop/backend/repository/AdoptionRepository.java index 92bf2cb2..3dd488d6 100644 --- a/src/main/java/com/petshop/backend/repository/AdoptionRepository.java +++ b/src/main/java/com/petshop/backend/repository/AdoptionRepository.java @@ -12,7 +12,8 @@ import org.springframework.stereotype.Repository; public interface AdoptionRepository extends JpaRepository { @Query("SELECT a FROM Adoption a WHERE " + - "LOWER(a.customer.customerName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + + "LOWER(a.customer.firstName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + + "LOWER(a.customer.lastName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + "LOWER(a.pet.petName) LIKE LOWER(CONCAT('%', :q, '%'))") Page searchAdoptions(@Param("q") String query, Pageable pageable); } diff --git a/src/main/java/com/petshop/backend/repository/AppointmentRepository.java b/src/main/java/com/petshop/backend/repository/AppointmentRepository.java index 4040d08c..779bee9a 100644 --- a/src/main/java/com/petshop/backend/repository/AppointmentRepository.java +++ b/src/main/java/com/petshop/backend/repository/AppointmentRepository.java @@ -18,11 +18,12 @@ public interface AppointmentRepository extends JpaRepository @Query("SELECT a FROM Appointment a WHERE a.appointmentDate = :date AND a.appointmentTime = :time") List findByDateAndTime(@Param("date") LocalDate date, @Param("time") LocalTime time); - @Query("SELECT a FROM Appointment a WHERE a.service.id = :serviceId AND a.appointmentDate = :date AND a.status != 'Cancelled'") + @Query("SELECT a FROM Appointment a WHERE a.service.serviceId = :serviceId AND a.appointmentDate = :date AND a.appointmentStatus != 'Cancelled'") List findByServiceAndDate(@Param("serviceId") Long serviceId, @Param("date") LocalDate date); @Query("SELECT DISTINCT a FROM Appointment a LEFT JOIN a.pets p WHERE " + - "LOWER(a.customer.customerName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + + "LOWER(a.customer.firstName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + + "LOWER(a.customer.lastName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + "LOWER(a.service.serviceName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + "LOWER(p.petName) LIKE LOWER(CONCAT('%', :q, '%'))") Page searchAppointments(@Param("q") String query, Pageable pageable); diff --git a/src/main/java/com/petshop/backend/repository/CategoryRepository.java b/src/main/java/com/petshop/backend/repository/CategoryRepository.java index ae20d6a0..ceb30e53 100644 --- a/src/main/java/com/petshop/backend/repository/CategoryRepository.java +++ b/src/main/java/com/petshop/backend/repository/CategoryRepository.java @@ -17,6 +17,6 @@ public interface CategoryRepository extends JpaRepository { @Query("SELECT c FROM Category c WHERE " + "LOWER(c.categoryName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + - "LOWER(c.categoryDescription) LIKE LOWER(CONCAT('%', :q, '%'))") + "LOWER(c.categoryType) LIKE LOWER(CONCAT('%', :q, '%'))") Page searchCategories(@Param("q") String query, Pageable pageable); } diff --git a/src/main/java/com/petshop/backend/repository/CustomerRepository.java b/src/main/java/com/petshop/backend/repository/CustomerRepository.java index a0bebc69..a1885993 100644 --- a/src/main/java/com/petshop/backend/repository/CustomerRepository.java +++ b/src/main/java/com/petshop/backend/repository/CustomerRepository.java @@ -12,8 +12,9 @@ import org.springframework.stereotype.Repository; public interface CustomerRepository extends JpaRepository { @Query("SELECT c FROM Customer c WHERE " + - "LOWER(c.customerName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + - "LOWER(c.customerEmail) LIKE LOWER(CONCAT('%', :q, '%')) OR " + - "LOWER(c.customerPhone) LIKE LOWER(CONCAT('%', :q, '%'))") + "LOWER(c.firstName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + + "LOWER(c.lastName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + + "LOWER(c.email) LIKE LOWER(CONCAT('%', :q, '%')) OR " + + "LOWER(c.phone) LIKE LOWER(CONCAT('%', :q, '%'))") Page searchCustomers(@Param("q") String query, Pageable pageable); } diff --git a/src/main/java/com/petshop/backend/repository/InventoryRepository.java b/src/main/java/com/petshop/backend/repository/InventoryRepository.java index e7e4d673..0e9d358c 100644 --- a/src/main/java/com/petshop/backend/repository/InventoryRepository.java +++ b/src/main/java/com/petshop/backend/repository/InventoryRepository.java @@ -13,11 +13,11 @@ import java.util.Optional; @Repository public interface InventoryRepository extends JpaRepository { - @Query("SELECT i FROM Inventory i WHERE i.product.id = :productId AND i.store.id = :storeId") - Optional findByProductIdAndStoreId(@Param("productId") Long productId, @Param("storeId") Long storeId); + @Query("SELECT i FROM Inventory i WHERE i.product.prodId = :productId") + Optional findByProductId(@Param("productId") Long productId); @Query("SELECT i FROM Inventory i WHERE " + - "LOWER(i.product.productName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + - "LOWER(i.store.storeName) LIKE LOWER(CONCAT('%', :q, '%'))") + "LOWER(i.product.prodName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + + "LOWER(i.product.category.categoryName) LIKE LOWER(CONCAT('%', :q, '%'))") Page searchInventory(@Param("q") String query, Pageable pageable); } diff --git a/src/main/java/com/petshop/backend/repository/ProductRepository.java b/src/main/java/com/petshop/backend/repository/ProductRepository.java index 896602e9..94f7fb81 100644 --- a/src/main/java/com/petshop/backend/repository/ProductRepository.java +++ b/src/main/java/com/petshop/backend/repository/ProductRepository.java @@ -12,7 +12,7 @@ import org.springframework.stereotype.Repository; public interface ProductRepository extends JpaRepository { @Query("SELECT p FROM Product p WHERE " + - "LOWER(p.productName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + - "LOWER(p.productDescription) LIKE LOWER(CONCAT('%', :q, '%'))") + "LOWER(p.prodName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + + "LOWER(p.prodDesc) LIKE LOWER(CONCAT('%', :q, '%'))") Page searchProducts(@Param("q") String query, Pageable pageable); } diff --git a/src/main/java/com/petshop/backend/repository/ProductSupplierRepository.java b/src/main/java/com/petshop/backend/repository/ProductSupplierRepository.java index 8b5e8720..46e87945 100644 --- a/src/main/java/com/petshop/backend/repository/ProductSupplierRepository.java +++ b/src/main/java/com/petshop/backend/repository/ProductSupplierRepository.java @@ -12,7 +12,7 @@ import org.springframework.stereotype.Repository; public interface ProductSupplierRepository extends JpaRepository { @Query("SELECT ps FROM ProductSupplier ps WHERE " + - "LOWER(ps.product.productName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + - "LOWER(ps.supplier.supplierName) LIKE LOWER(CONCAT('%', :q, '%'))") + "LOWER(ps.product.prodName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + + "LOWER(ps.supplier.supCompany) LIKE LOWER(CONCAT('%', :q, '%'))") Page searchProductSuppliers(@Param("q") String query, Pageable pageable); } diff --git a/src/main/java/com/petshop/backend/repository/PurchaseOrderRepository.java b/src/main/java/com/petshop/backend/repository/PurchaseOrderRepository.java index e6087bef..d3b445c4 100644 --- a/src/main/java/com/petshop/backend/repository/PurchaseOrderRepository.java +++ b/src/main/java/com/petshop/backend/repository/PurchaseOrderRepository.java @@ -12,7 +12,6 @@ import org.springframework.stereotype.Repository; public interface PurchaseOrderRepository extends JpaRepository { @Query("SELECT po FROM PurchaseOrder po WHERE " + - "LOWER(po.supplier.supplierName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + - "LOWER(po.notes) LIKE LOWER(CONCAT('%', :q, '%'))") + "LOWER(po.supplier.supCompany) LIKE LOWER(CONCAT('%', :q, '%'))") Page searchPurchaseOrders(@Param("q") String query, Pageable pageable); } diff --git a/src/main/java/com/petshop/backend/repository/SaleRepository.java b/src/main/java/com/petshop/backend/repository/SaleRepository.java index 9d5392a5..56b31289 100644 --- a/src/main/java/com/petshop/backend/repository/SaleRepository.java +++ b/src/main/java/com/petshop/backend/repository/SaleRepository.java @@ -12,8 +12,8 @@ import org.springframework.stereotype.Repository; public interface SaleRepository extends JpaRepository { @Query("SELECT s FROM Sale s WHERE " + - "LOWER(s.customer.customerName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + - "LOWER(s.employee.fullName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + + "LOWER(s.employee.firstName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + + "LOWER(s.employee.lastName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + "LOWER(s.store.storeName) LIKE LOWER(CONCAT('%', :q, '%'))") Page searchSales(@Param("q") String query, Pageable pageable); } diff --git a/src/main/java/com/petshop/backend/repository/ServiceRepository.java b/src/main/java/com/petshop/backend/repository/ServiceRepository.java index a6c0a084..7b057856 100644 --- a/src/main/java/com/petshop/backend/repository/ServiceRepository.java +++ b/src/main/java/com/petshop/backend/repository/ServiceRepository.java @@ -13,6 +13,6 @@ public interface ServiceRepository extends JpaRepository { @Query("SELECT s FROM Service s WHERE " + "LOWER(s.serviceName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + - "LOWER(s.serviceDescription) LIKE LOWER(CONCAT('%', :q, '%'))") + "LOWER(s.serviceDesc) LIKE LOWER(CONCAT('%', :q, '%'))") Page searchServices(@Param("q") String query, Pageable pageable); } diff --git a/src/main/java/com/petshop/backend/repository/SupplierRepository.java b/src/main/java/com/petshop/backend/repository/SupplierRepository.java index 443455b8..c7dd2307 100644 --- a/src/main/java/com/petshop/backend/repository/SupplierRepository.java +++ b/src/main/java/com/petshop/backend/repository/SupplierRepository.java @@ -12,7 +12,8 @@ import org.springframework.stereotype.Repository; public interface SupplierRepository extends JpaRepository { @Query("SELECT s FROM Supplier s WHERE " + - "LOWER(s.supplierName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + - "LOWER(s.supplierContact) LIKE LOWER(CONCAT('%', :q, '%'))") + "LOWER(s.supCompany) LIKE LOWER(CONCAT('%', :q, '%')) OR " + + "LOWER(s.supContactFirstName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + + "LOWER(s.supContactLastName) LIKE LOWER(CONCAT('%', :q, '%'))") Page searchSuppliers(@Param("q") String query, Pageable pageable); } diff --git a/src/main/java/com/petshop/backend/repository/UserRepository.java b/src/main/java/com/petshop/backend/repository/UserRepository.java index 486d8f1a..187d73c4 100644 --- a/src/main/java/com/petshop/backend/repository/UserRepository.java +++ b/src/main/java/com/petshop/backend/repository/UserRepository.java @@ -16,8 +16,6 @@ public interface UserRepository extends JpaRepository { boolean existsByUsername(String username); @Query("SELECT u FROM User u WHERE " + - "LOWER(u.username) LIKE LOWER(CONCAT('%', :q, '%')) OR " + - "LOWER(u.fullName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + - "LOWER(u.email) LIKE LOWER(CONCAT('%', :q, '%'))") + "LOWER(u.username) LIKE LOWER(CONCAT('%', :q, '%'))") Page searchUsers(@Param("q") String query, Pageable pageable); } diff --git a/src/main/java/com/petshop/backend/security/UserDetailsServiceImpl.java b/src/main/java/com/petshop/backend/security/UserDetailsServiceImpl.java index d2bdcfff..06c3870b 100644 --- a/src/main/java/com/petshop/backend/security/UserDetailsServiceImpl.java +++ b/src/main/java/com/petshop/backend/security/UserDetailsServiceImpl.java @@ -24,10 +24,6 @@ public class UserDetailsServiceImpl implements UserDetailsService { User user = userRepository.findByUsername(username) .orElseThrow(() -> new UsernameNotFoundException("User not found: " + username)); - if (!user.getActive()) { - throw new UsernameNotFoundException("User is inactive: " + username); - } - return new org.springframework.security.core.userdetails.User( user.getUsername(), user.getPassword(), diff --git a/src/main/java/com/petshop/backend/service/AdoptionService.java b/src/main/java/com/petshop/backend/service/AdoptionService.java index d5ecc890..fea336cd 100644 --- a/src/main/java/com/petshop/backend/service/AdoptionService.java +++ b/src/main/java/com/petshop/backend/service/AdoptionService.java @@ -56,8 +56,7 @@ public class AdoptionService { adoption.setPet(pet); adoption.setCustomer(customer); adoption.setAdoptionDate(request.getAdoptionDate()); - adoption.setAdoptionFee(request.getAdoptionFee()); - adoption.setNotes(request.getNotes()); + adoption.setAdoptionStatus(request.getAdoptionStatus()); adoption = adoptionRepository.save(adoption); return mapToResponse(adoption); @@ -77,8 +76,7 @@ public class AdoptionService { adoption.setPet(pet); adoption.setCustomer(customer); adoption.setAdoptionDate(request.getAdoptionDate()); - adoption.setAdoptionFee(request.getAdoptionFee()); - adoption.setNotes(request.getNotes()); + adoption.setAdoptionStatus(request.getAdoptionStatus()); adoption = adoptionRepository.save(adoption); return mapToResponse(adoption); @@ -99,14 +97,13 @@ public class AdoptionService { private AdoptionResponse mapToResponse(Adoption adoption) { return new AdoptionResponse( - adoption.getId(), - adoption.getPet().getId(), + adoption.getAdoptionId(), + adoption.getPet().getPetId(), adoption.getPet().getPetName(), - adoption.getCustomer().getId(), - adoption.getCustomer().getCustomerName(), + adoption.getCustomer().getCustomerId(), + adoption.getCustomer().getFirstName() + " " + adoption.getCustomer().getLastName(), adoption.getAdoptionDate(), - adoption.getAdoptionFee(), - adoption.getNotes(), + adoption.getAdoptionStatus(), adoption.getCreatedAt(), adoption.getUpdatedAt() ); diff --git a/src/main/java/com/petshop/backend/service/AnalyticsService.java b/src/main/java/com/petshop/backend/service/AnalyticsService.java index 47e27d17..18c9c9b8 100644 --- a/src/main/java/com/petshop/backend/service/AnalyticsService.java +++ b/src/main/java/com/petshop/backend/service/AnalyticsService.java @@ -3,11 +3,9 @@ package com.petshop.backend.service; import com.petshop.backend.dto.analytics.DashboardResponse; import com.petshop.backend.entity.Inventory; import com.petshop.backend.entity.Product; -import com.petshop.backend.entity.Refund; import com.petshop.backend.entity.Sale; import com.petshop.backend.repository.InventoryRepository; import com.petshop.backend.repository.ProductRepository; -import com.petshop.backend.repository.RefundRepository; import com.petshop.backend.repository.SaleRepository; import org.springframework.stereotype.Service; @@ -22,14 +20,12 @@ import java.util.stream.Collectors; public class AnalyticsService { private final SaleRepository saleRepository; - private final RefundRepository refundRepository; private final InventoryRepository inventoryRepository; private final ProductRepository productRepository; - public AnalyticsService(SaleRepository saleRepository, RefundRepository refundRepository, + public AnalyticsService(SaleRepository saleRepository, InventoryRepository inventoryRepository, ProductRepository productRepository) { this.saleRepository = saleRepository; - this.refundRepository = refundRepository; this.inventoryRepository = inventoryRepository; this.productRepository = productRepository; } @@ -41,11 +37,7 @@ public class AnalyticsService { .filter(sale -> sale.getSaleDate().isAfter(startDate)) .collect(Collectors.toList()); - List refunds = refundRepository.findAll().stream() - .filter(refund -> refund.getRefundDate().isAfter(startDate)) - .collect(Collectors.toList()); - - DashboardResponse.SalesSummary salesSummary = calculateSalesSummary(sales, refunds); + DashboardResponse.SalesSummary salesSummary = calculateSalesSummary(sales); DashboardResponse.InventorySummary inventorySummary = calculateInventorySummary(); List topProducts = calculateTopProducts(sales, top); List dailySales = calculateDailySales(sales, days); @@ -53,18 +45,24 @@ public class AnalyticsService { return new DashboardResponse(salesSummary, inventorySummary, topProducts, dailySales); } - private DashboardResponse.SalesSummary calculateSalesSummary(List sales, List refunds) { + private DashboardResponse.SalesSummary calculateSalesSummary(List sales) { BigDecimal totalRevenue = sales.stream() - .map(Sale::getTotal) + .filter(sale -> !sale.getIsRefund()) + .map(Sale::getTotalAmount) .reduce(BigDecimal.ZERO, BigDecimal::add); - Long totalSales = (long) sales.size(); + Long totalSales = sales.stream() + .filter(sale -> !sale.getIsRefund()) + .count(); - BigDecimal totalRefunds = refunds.stream() - .map(Refund::getRefundAmount) + BigDecimal totalRefunds = sales.stream() + .filter(Sale::getIsRefund) + .map(Sale::getTotalAmount) .reduce(BigDecimal.ZERO, BigDecimal::add); - Long totalRefundCount = (long) refunds.size(); + Long totalRefundCount = sales.stream() + .filter(Sale::getIsRefund) + .count(); return new DashboardResponse.SalesSummary(totalRevenue, totalSales, totalRefunds, totalRefundCount); } @@ -75,14 +73,14 @@ public class AnalyticsService { Long totalProducts = productRepository.count(); Long lowStockProducts = allInventory.stream() - .filter(inv -> inv.getQuantity() > 0 && inv.getQuantity() <= inv.getReorderLevel()) - .map(inv -> inv.getProduct().getId()) + .filter(inv -> inv.getQuantity() > 0 && inv.getQuantity() <= 10) + .map(inv -> inv.getProduct().getProdId()) .distinct() .count(); Long outOfStockProducts = allInventory.stream() .filter(inv -> inv.getQuantity() == 0) - .map(inv -> inv.getProduct().getId()) + .map(inv -> inv.getProduct().getProdId()) .distinct() .count(); @@ -94,10 +92,10 @@ public class AnalyticsService { for (Sale sale : sales) { for (var item : sale.getItems()) { - Long productId = item.getProduct().getId(); - String productName = item.getProduct().getProductName(); + Long productId = item.getProduct().getProdId(); + String productName = item.getProduct().getProdName(); Long quantitySold = Long.valueOf(item.getQuantity()); - BigDecimal revenue = item.getSubtotal(); + BigDecimal revenue = item.getUnitPrice().multiply(BigDecimal.valueOf(item.getQuantity())); productSalesMap.compute(productId, (key, existing) -> { if (existing == null) { @@ -131,7 +129,7 @@ public class AnalyticsService { LocalDate saleDate = sale.getSaleDate().toLocalDate(); if (dailySalesMap.containsKey(saleDate)) { DashboardResponse.DailySales dailySale = dailySalesMap.get(saleDate); - dailySale.setRevenue(dailySale.getRevenue().add(sale.getTotal())); + dailySale.setRevenue(dailySale.getRevenue().add(sale.getTotalAmount())); dailySale.setSalesCount(dailySale.getSalesCount() + 1); } } diff --git a/src/main/java/com/petshop/backend/service/AppointmentService.java b/src/main/java/com/petshop/backend/service/AppointmentService.java index 8cc52b2c..1fae451c 100644 --- a/src/main/java/com/petshop/backend/service/AppointmentService.java +++ b/src/main/java/com/petshop/backend/service/AppointmentService.java @@ -70,9 +70,8 @@ public class AppointmentService { appointment.setService(service); appointment.setAppointmentDate(request.getAppointmentDate()); appointment.setAppointmentTime(request.getAppointmentTime()); - appointment.setStatus(request.getStatus()); + appointment.setAppointmentStatus(request.getAppointmentStatus()); appointment.setPets(pets); - appointment.setNotes(request.getNotes()); appointment = appointmentRepository.save(appointment); return mapToResponse(appointment); @@ -95,9 +94,8 @@ public class AppointmentService { appointment.setService(service); appointment.setAppointmentDate(request.getAppointmentDate()); appointment.setAppointmentTime(request.getAppointmentTime()); - appointment.setStatus(request.getStatus()); + appointment.setAppointmentStatus(request.getAppointmentStatus()); appointment.setPets(pets); - appointment.setNotes(request.getNotes()); appointment = appointmentRepository.save(appointment); return mapToResponse(appointment); @@ -156,21 +154,20 @@ public class AppointmentService { .collect(Collectors.toList()); List petIds = appointment.getPets().stream() - .map(Pet::getId) + .map(Pet::getPetId) .collect(Collectors.toList()); return new AppointmentResponse( - appointment.getId(), - appointment.getCustomer().getId(), - appointment.getCustomer().getCustomerName(), - appointment.getService().getId(), + appointment.getAppointmentId(), + appointment.getCustomer().getCustomerId(), + appointment.getCustomer().getFirstName() + " " + appointment.getCustomer().getLastName(), + appointment.getService().getServiceId(), appointment.getService().getServiceName(), appointment.getAppointmentDate(), appointment.getAppointmentTime(), - appointment.getStatus() != null ? appointment.getStatus().toString() : null, + appointment.getAppointmentStatus(), petNames, petIds, - appointment.getNotes(), appointment.getCreatedAt(), appointment.getUpdatedAt() ); diff --git a/src/main/java/com/petshop/backend/service/CategoryService.java b/src/main/java/com/petshop/backend/service/CategoryService.java index b575a8eb..3da87dd0 100644 --- a/src/main/java/com/petshop/backend/service/CategoryService.java +++ b/src/main/java/com/petshop/backend/service/CategoryService.java @@ -40,7 +40,7 @@ public class CategoryService { public CategoryResponse createCategory(CategoryRequest request) { Category category = new Category(); category.setCategoryName(request.getCategoryName()); - category.setCategoryDescription(request.getCategoryDescription()); + category.setCategoryType(request.getCategoryType()); category = categoryRepository.save(category); return mapToResponse(category); @@ -52,7 +52,7 @@ public class CategoryService { .orElseThrow(() -> new ResourceNotFoundException("Category not found with id: " + id)); category.setCategoryName(request.getCategoryName()); - category.setCategoryDescription(request.getCategoryDescription()); + category.setCategoryType(request.getCategoryType()); category = categoryRepository.save(category); return mapToResponse(category); @@ -73,9 +73,9 @@ public class CategoryService { private CategoryResponse mapToResponse(Category category) { return new CategoryResponse( - category.getId(), + category.getCategoryId(), category.getCategoryName(), - category.getCategoryDescription(), + category.getCategoryType(), category.getCreatedAt(), category.getUpdatedAt() ); diff --git a/src/main/java/com/petshop/backend/service/CustomerService.java b/src/main/java/com/petshop/backend/service/CustomerService.java index 7b826a72..47fa3c4c 100644 --- a/src/main/java/com/petshop/backend/service/CustomerService.java +++ b/src/main/java/com/petshop/backend/service/CustomerService.java @@ -39,10 +39,10 @@ public class CustomerService { @Transactional public CustomerResponse createCustomer(CustomerRequest request) { Customer customer = new Customer(); - customer.setCustomerName(request.getCustomerName()); - customer.setCustomerEmail(request.getCustomerEmail()); - customer.setCustomerPhone(request.getCustomerPhone()); - customer.setCustomerAddress(request.getCustomerAddress()); + customer.setFirstName(request.getFirstName()); + customer.setLastName(request.getLastName()); + customer.setEmail(request.getEmail()); + customer.setPhone(request.getPhone()); customer = customerRepository.save(customer); return mapToResponse(customer); @@ -53,10 +53,10 @@ public class CustomerService { Customer customer = customerRepository.findById(id) .orElseThrow(() -> new ResourceNotFoundException("Customer not found with id: " + id)); - customer.setCustomerName(request.getCustomerName()); - customer.setCustomerEmail(request.getCustomerEmail()); - customer.setCustomerPhone(request.getCustomerPhone()); - customer.setCustomerAddress(request.getCustomerAddress()); + customer.setFirstName(request.getFirstName()); + customer.setLastName(request.getLastName()); + customer.setEmail(request.getEmail()); + customer.setPhone(request.getPhone()); customer = customerRepository.save(customer); return mapToResponse(customer); @@ -77,11 +77,11 @@ public class CustomerService { private CustomerResponse mapToResponse(Customer customer) { return new CustomerResponse( - customer.getId(), - customer.getCustomerName(), - customer.getCustomerEmail(), - customer.getCustomerPhone(), - customer.getCustomerAddress(), + customer.getCustomerId(), + customer.getFirstName(), + customer.getLastName(), + customer.getEmail(), + customer.getPhone(), customer.getCreatedAt(), customer.getUpdatedAt() ); diff --git a/src/main/java/com/petshop/backend/service/InventoryService.java b/src/main/java/com/petshop/backend/service/InventoryService.java index c601cf1a..ee63aea7 100644 --- a/src/main/java/com/petshop/backend/service/InventoryService.java +++ b/src/main/java/com/petshop/backend/service/InventoryService.java @@ -5,29 +5,23 @@ import com.petshop.backend.dto.inventory.InventoryRequest; import com.petshop.backend.dto.inventory.InventoryResponse; import com.petshop.backend.entity.Inventory; import com.petshop.backend.entity.Product; -import com.petshop.backend.entity.Store; import com.petshop.backend.exception.ResourceNotFoundException; import com.petshop.backend.repository.InventoryRepository; import com.petshop.backend.repository.ProductRepository; -import com.petshop.backend.repository.StoreRepository; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.time.LocalDateTime; - @Service public class InventoryService { private final InventoryRepository inventoryRepository; private final ProductRepository productRepository; - private final StoreRepository storeRepository; - public InventoryService(InventoryRepository inventoryRepository, ProductRepository productRepository, StoreRepository storeRepository) { + public InventoryService(InventoryRepository inventoryRepository, ProductRepository productRepository) { this.inventoryRepository = inventoryRepository; this.productRepository = productRepository; - this.storeRepository = storeRepository; } public Page getAllInventory(String query, Pageable pageable) { @@ -51,18 +45,9 @@ public class InventoryService { Product product = productRepository.findById(request.getProdId()) .orElseThrow(() -> new ResourceNotFoundException("Product not found with id: " + request.getProdId())); - Store store = null; - if (request.getStoreId() != null) { - store = storeRepository.findById(request.getStoreId()) - .orElseThrow(() -> new ResourceNotFoundException("Store not found with id: " + request.getStoreId())); - } - Inventory inventory = new Inventory(); inventory.setProduct(product); - inventory.setStore(store); inventory.setQuantity(request.getQuantity()); - inventory.setReorderLevel(request.getReorderLevel()); - inventory.setLastRestocked(LocalDateTime.now()); inventory = inventoryRepository.save(inventory); return mapToResponse(inventory); @@ -76,17 +61,8 @@ public class InventoryService { Product product = productRepository.findById(request.getProdId()) .orElseThrow(() -> new ResourceNotFoundException("Product not found with id: " + request.getProdId())); - Store store = null; - if (request.getStoreId() != null) { - store = storeRepository.findById(request.getStoreId()) - .orElseThrow(() -> new ResourceNotFoundException("Store not found with id: " + request.getStoreId())); - } - inventory.setProduct(product); - inventory.setStore(store); inventory.setQuantity(request.getQuantity()); - inventory.setReorderLevel(request.getReorderLevel()); - inventory.setLastRestocked(LocalDateTime.now()); inventory = inventoryRepository.save(inventory); return mapToResponse(inventory); @@ -107,15 +83,11 @@ public class InventoryService { private InventoryResponse mapToResponse(Inventory inventory) { return new InventoryResponse( - inventory.getId(), - inventory.getProduct().getId(), - inventory.getProduct().getProductName(), + inventory.getInventoryId(), + inventory.getProduct().getProdId(), + inventory.getProduct().getProdName(), inventory.getProduct().getCategory().getCategoryName(), - inventory.getStore() != null ? inventory.getStore().getId() : null, - inventory.getStore() != null ? inventory.getStore().getStoreName() : null, inventory.getQuantity(), - inventory.getReorderLevel(), - inventory.getLastRestocked(), inventory.getCreatedAt(), inventory.getUpdatedAt() ); diff --git a/src/main/java/com/petshop/backend/service/ProductService.java b/src/main/java/com/petshop/backend/service/ProductService.java index 26f13320..b907e38f 100644 --- a/src/main/java/com/petshop/backend/service/ProductService.java +++ b/src/main/java/com/petshop/backend/service/ProductService.java @@ -46,11 +46,10 @@ public class ProductService { .orElseThrow(() -> new ResourceNotFoundException("Category not found with id: " + request.getCategoryId())); Product product = new Product(); - product.setProductName(request.getProdName()); + product.setProdName(request.getProdName()); product.setCategory(category); - product.setProductDescription(request.getProdDesc()); - product.setProductPrice(request.getProdPrice()); - product.setActive(request.getActive() != null ? request.getActive() : true); + product.setProdDesc(request.getProdDesc()); + product.setProdPrice(request.getProdPrice()); product = productRepository.save(product); return mapToResponse(product); @@ -64,11 +63,10 @@ public class ProductService { Category category = categoryRepository.findById(request.getCategoryId()) .orElseThrow(() -> new ResourceNotFoundException("Category not found with id: " + request.getCategoryId())); - product.setProductName(request.getProdName()); + product.setProdName(request.getProdName()); product.setCategory(category); - product.setProductDescription(request.getProdDesc()); - product.setProductPrice(request.getProdPrice()); - product.setActive(request.getActive() != null ? request.getActive() : true); + product.setProdDesc(request.getProdDesc()); + product.setProdPrice(request.getProdPrice()); product = productRepository.save(product); return mapToResponse(product); @@ -89,13 +87,12 @@ public class ProductService { private ProductResponse mapToResponse(Product product) { return new ProductResponse( - product.getId(), - product.getProductName(), - product.getCategory().getId(), + product.getProdId(), + product.getProdName(), + product.getCategory().getCategoryId(), product.getCategory().getCategoryName(), - product.getProductDescription(), - product.getProductPrice(), - product.getActive(), + product.getProdDesc(), + product.getProdPrice(), product.getCreatedAt(), product.getUpdatedAt() ); diff --git a/src/main/java/com/petshop/backend/service/ProductSupplierService.java b/src/main/java/com/petshop/backend/service/ProductSupplierService.java index 2e60b3e4..7e3677a9 100644 --- a/src/main/java/com/petshop/backend/service/ProductSupplierService.java +++ b/src/main/java/com/petshop/backend/service/ProductSupplierService.java @@ -57,9 +57,7 @@ public class ProductSupplierService { ProductSupplier productSupplier = new ProductSupplier(); productSupplier.setProduct(product); productSupplier.setSupplier(supplier); - productSupplier.setCostPrice(request.getCostPrice()); - productSupplier.setLeadTimeDays(request.getLeadTimeDays()); - productSupplier.setIsPreferred(request.getIsPreferred()); + productSupplier.setCost(request.getCost()); productSupplier = productSupplierRepository.save(productSupplier); return mapToResponse(productSupplier); @@ -72,9 +70,7 @@ public class ProductSupplierService { .orElseThrow(() -> new ResourceNotFoundException( "ProductSupplier not found with productId: " + productId + " and supplierId: " + supplierId)); - productSupplier.setCostPrice(request.getCostPrice()); - productSupplier.setLeadTimeDays(request.getLeadTimeDays()); - productSupplier.setIsPreferred(request.getIsPreferred()); + productSupplier.setCost(request.getCost()); productSupplier = productSupplierRepository.save(productSupplier); return mapToResponse(productSupplier); @@ -101,13 +97,11 @@ public class ProductSupplierService { private ProductSupplierResponse mapToResponse(ProductSupplier productSupplier) { return new ProductSupplierResponse( - productSupplier.getProduct().getId(), - productSupplier.getProduct().getProductName(), - productSupplier.getSupplier().getId(), - productSupplier.getSupplier().getSupplierName(), - productSupplier.getCostPrice(), - productSupplier.getLeadTimeDays(), - productSupplier.getIsPreferred(), + productSupplier.getProduct().getProdId(), + productSupplier.getProduct().getProdName(), + productSupplier.getSupplier().getSupId(), + productSupplier.getSupplier().getSupCompany(), + productSupplier.getCost(), productSupplier.getCreatedAt(), productSupplier.getUpdatedAt() ); diff --git a/src/main/java/com/petshop/backend/service/PurchaseOrderService.java b/src/main/java/com/petshop/backend/service/PurchaseOrderService.java index efda04d6..97286a9c 100644 --- a/src/main/java/com/petshop/backend/service/PurchaseOrderService.java +++ b/src/main/java/com/petshop/backend/service/PurchaseOrderService.java @@ -1,18 +1,13 @@ package com.petshop.backend.service; import com.petshop.backend.dto.purchaseorder.PurchaseOrderResponse; -import com.petshop.backend.dto.purchaseorder.PurchaseOrderResponse.PurchaseOrderItemResponse; import com.petshop.backend.entity.PurchaseOrder; -import com.petshop.backend.entity.PurchaseOrderItem; import com.petshop.backend.exception.ResourceNotFoundException; import com.petshop.backend.repository.PurchaseOrderRepository; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; -import java.util.List; -import java.util.stream.Collectors; - @Service public class PurchaseOrderService { @@ -39,33 +34,14 @@ public class PurchaseOrderService { } private PurchaseOrderResponse mapToResponse(PurchaseOrder purchaseOrder) { - List items = purchaseOrder.getItems().stream() - .map(this::mapItemToResponse) - .collect(Collectors.toList()); - return new PurchaseOrderResponse( - purchaseOrder.getId(), - purchaseOrder.getSupplier().getId(), - purchaseOrder.getSupplier().getSupplierName(), + purchaseOrder.getPurchaseOrderId(), + purchaseOrder.getSupplier().getSupId(), + purchaseOrder.getSupplier().getSupCompany(), purchaseOrder.getOrderDate(), - purchaseOrder.getExpectedDelivery(), - purchaseOrder.getStatus().toString(), - purchaseOrder.getTotalAmount(), - purchaseOrder.getNotes(), - items, + purchaseOrder.getStatus(), purchaseOrder.getCreatedAt(), purchaseOrder.getUpdatedAt() ); } - - private PurchaseOrderItemResponse mapItemToResponse(PurchaseOrderItem item) { - return new PurchaseOrderItemResponse( - item.getId(), - item.getProduct().getId(), - item.getProduct().getProductName(), - item.getQuantity(), - item.getUnitCost(), - item.getSubtotal() - ); - } } diff --git a/src/main/java/com/petshop/backend/service/SaleService.java b/src/main/java/com/petshop/backend/service/SaleService.java index 698a6c66..65b62f3e 100644 --- a/src/main/java/com/petshop/backend/service/SaleService.java +++ b/src/main/java/com/petshop/backend/service/SaleService.java @@ -22,18 +22,16 @@ public class SaleService { private final SaleRepository saleRepository; private final ProductRepository productRepository; - private final CustomerRepository customerRepository; private final StoreRepository storeRepository; private final InventoryRepository inventoryRepository; - private final UserRepository userRepository; + private final EmployeeRepository employeeRepository; - public SaleService(SaleRepository saleRepository, ProductRepository productRepository, CustomerRepository customerRepository, StoreRepository storeRepository, InventoryRepository inventoryRepository, UserRepository userRepository) { + public SaleService(SaleRepository saleRepository, ProductRepository productRepository, StoreRepository storeRepository, InventoryRepository inventoryRepository, EmployeeRepository employeeRepository) { this.saleRepository = saleRepository; this.productRepository = productRepository; - this.customerRepository = customerRepository; this.storeRepository = storeRepository; this.inventoryRepository = inventoryRepository; - this.userRepository = userRepository; + this.employeeRepository = employeeRepository; } public Page getAllSales(String query, Pageable pageable) { @@ -54,62 +52,58 @@ public class SaleService { @Transactional public SaleResponse createSale(SaleRequest request) { - String username = SecurityContextHolder.getContext().getAuthentication().getName(); - User employee = userRepository.findByUsername(username) - .orElseThrow(() -> new ResourceNotFoundException("User not found: " + username)); + Employee employee = employeeRepository.findAll().stream() + .findFirst() + .orElseThrow(() -> new ResourceNotFoundException("No employees found")); - Store store = storeRepository.findById(request.getStoreId()) + StoreLocation store = storeRepository.findById(request.getStoreId()) .orElseThrow(() -> new ResourceNotFoundException("Store not found with id: " + request.getStoreId())); - Customer customer = null; - if (request.getCustomerId() != null) { - customer = customerRepository.findById(request.getCustomerId()) - .orElseThrow(() -> new ResourceNotFoundException("Customer not found with id: " + request.getCustomerId())); - } - Sale sale = new Sale(); sale.setSaleDate(LocalDateTime.now()); sale.setEmployee(employee); - sale.setCustomer(customer); sale.setStore(store); sale.setPaymentMethod(request.getPaymentMethod()); - sale.setTax(request.getTax()); - sale.setNotes(request.getNotes()); + sale.setIsRefund(request.getIsRefund() != null ? request.getIsRefund() : false); - BigDecimal subtotal = BigDecimal.ZERO; + if (sale.getIsRefund() && request.getOriginalSaleId() != null) { + Sale originalSale = saleRepository.findById(request.getOriginalSaleId()) + .orElseThrow(() -> new ResourceNotFoundException("Original sale not found with id: " + request.getOriginalSaleId())); + sale.setOriginalSale(originalSale); + } + + BigDecimal totalAmount = BigDecimal.ZERO; List saleItems = new ArrayList<>(); for (var itemRequest : request.getItems()) { - Product product = productRepository.findById(itemRequest.getProductId()) - .orElseThrow(() -> new ResourceNotFoundException("Product not found with id: " + itemRequest.getProductId())); + Product product = productRepository.findById(itemRequest.getProdId()) + .orElseThrow(() -> new ResourceNotFoundException("Product not found with id: " + itemRequest.getProdId())); - Inventory inventory = inventoryRepository.findByProductIdAndStoreId(itemRequest.getProductId(), request.getStoreId()) - .orElseThrow(() -> new ResourceNotFoundException("Inventory not found for product " + itemRequest.getProductId() + " at store " + request.getStoreId())); + Inventory inventory = inventoryRepository.findByProductId(itemRequest.getProdId()) + .orElseThrow(() -> new ResourceNotFoundException("Inventory not found for product " + itemRequest.getProdId())); if (inventory.getQuantity() < itemRequest.getQuantity()) { - throw new BusinessException("Insufficient stock for product: " + product.getProductName() + + throw new BusinessException("Insufficient stock for product: " + product.getProdName() + ". Available: " + inventory.getQuantity() + ", requested: " + itemRequest.getQuantity()); } inventory.setQuantity(inventory.getQuantity() - itemRequest.getQuantity()); inventoryRepository.save(inventory); - BigDecimal unitPrice = product.getProductPrice(); - BigDecimal itemSubtotal = unitPrice.multiply(BigDecimal.valueOf(itemRequest.getQuantity())); + BigDecimal unitPrice = product.getProdPrice(); + BigDecimal itemTotal = unitPrice.multiply(BigDecimal.valueOf(itemRequest.getQuantity())); SaleItem saleItem = new SaleItem(); saleItem.setSale(sale); saleItem.setProduct(product); saleItem.setQuantity(itemRequest.getQuantity()); saleItem.setUnitPrice(unitPrice); - saleItem.setSubtotal(itemSubtotal); saleItems.add(saleItem); - subtotal = subtotal.add(itemSubtotal); + totalAmount = totalAmount.add(itemTotal); } - sale.setSubtotal(subtotal); - sale.setTotal(subtotal.add(sale.getTax())); + sale.setTotalAmount(totalAmount); sale.setItems(saleItems); Sale savedSale = saleRepository.save(sale); @@ -118,37 +112,32 @@ public class SaleService { private SaleResponse mapToResponse(Sale sale) { SaleResponse response = new SaleResponse(); - response.setId(sale.getId()); + response.setSaleId(sale.getSaleId()); response.setSaleDate(sale.getSaleDate()); - response.setEmployeeId(sale.getEmployee().getId()); - response.setEmployeeName(sale.getEmployee().getFullName()); - - if (sale.getCustomer() != null) { - response.setCustomerId(sale.getCustomer().getId()); - response.setCustomerName(sale.getCustomer().getCustomerName()); - } + response.setEmployeeId(sale.getEmployee().getEmployeeId()); + response.setEmployeeName(sale.getEmployee().getFirstName() + " " + sale.getEmployee().getLastName()); if (sale.getStore() != null) { - response.setStoreId(sale.getStore().getId()); + response.setStoreId(sale.getStore().getStoreId()); response.setStoreName(sale.getStore().getStoreName()); } - response.setSubtotal(sale.getSubtotal()); - response.setTax(sale.getTax()); - response.setTotal(sale.getTotal()); + response.setTotalAmount(sale.getTotalAmount()); response.setPaymentMethod(sale.getPaymentMethod()); - response.setNotes(sale.getNotes()); + response.setIsRefund(sale.getIsRefund()); + if (sale.getOriginalSale() != null) { + response.setOriginalSaleId(sale.getOriginalSale().getSaleId()); + } response.setCreatedAt(sale.getCreatedAt()); List itemResponses = new ArrayList<>(); for (SaleItem item : sale.getItems()) { SaleResponse.SaleItemResponse itemResponse = new SaleResponse.SaleItemResponse(); - itemResponse.setId(item.getId()); - itemResponse.setProductId(item.getProduct().getId()); - itemResponse.setProductName(item.getProduct().getProductName()); + itemResponse.setSaleItemId(item.getSaleItemId()); + itemResponse.setProdId(item.getProduct().getProdId()); + itemResponse.setProductName(item.getProduct().getProdName()); itemResponse.setQuantity(item.getQuantity()); itemResponse.setUnitPrice(item.getUnitPrice()); - itemResponse.setSubtotal(item.getSubtotal()); itemResponses.add(itemResponse); } response.setItems(itemResponses); diff --git a/src/main/java/com/petshop/backend/service/ServiceService.java b/src/main/java/com/petshop/backend/service/ServiceService.java index 72e5662f..5243f101 100644 --- a/src/main/java/com/petshop/backend/service/ServiceService.java +++ b/src/main/java/com/petshop/backend/service/ServiceService.java @@ -39,10 +39,9 @@ public class ServiceService { public ServiceResponse createService(ServiceRequest request) { com.petshop.backend.entity.Service service = new com.petshop.backend.entity.Service(); service.setServiceName(request.getServiceName()); - service.setServiceDescription(request.getServiceDescription()); + service.setServiceDesc(request.getServiceDesc()); service.setServicePrice(request.getServicePrice()); - service.setServiceDurationMinutes(request.getServiceDurationMinutes()); - service.setActive(request.getActive() != null ? request.getActive() : true); + service.setServiceDuration(request.getServiceDuration()); service = serviceRepository.save(service); return mapToResponse(service); @@ -54,10 +53,9 @@ public class ServiceService { .orElseThrow(() -> new ResourceNotFoundException("Service not found with id: " + id)); service.setServiceName(request.getServiceName()); - service.setServiceDescription(request.getServiceDescription()); + service.setServiceDesc(request.getServiceDesc()); service.setServicePrice(request.getServicePrice()); - service.setServiceDurationMinutes(request.getServiceDurationMinutes()); - service.setActive(request.getActive() != null ? request.getActive() : true); + service.setServiceDuration(request.getServiceDuration()); service = serviceRepository.save(service); return mapToResponse(service); @@ -78,12 +76,11 @@ public class ServiceService { private ServiceResponse mapToResponse(com.petshop.backend.entity.Service service) { return new ServiceResponse( - service.getId(), + service.getServiceId(), service.getServiceName(), - service.getServiceDescription(), + service.getServiceDesc(), service.getServicePrice(), - service.getServiceDurationMinutes(), - service.getActive(), + service.getServiceDuration(), service.getCreatedAt(), service.getUpdatedAt() ); diff --git a/src/main/java/com/petshop/backend/service/SupplierService.java b/src/main/java/com/petshop/backend/service/SupplierService.java index b234b759..2e80eeaa 100644 --- a/src/main/java/com/petshop/backend/service/SupplierService.java +++ b/src/main/java/com/petshop/backend/service/SupplierService.java @@ -39,12 +39,11 @@ public class SupplierService { @Transactional public SupplierResponse createSupplier(SupplierRequest request) { Supplier supplier = new Supplier(); - supplier.setSupplierName(request.getSupName()); - supplier.setSupplierContact(request.getSupContact()); - supplier.setSupplierEmail(request.getSupEmail()); - supplier.setSupplierPhone(request.getSupPhone()); - supplier.setSupplierAddress(request.getSupAddress()); - supplier.setActive(request.getActive()); + supplier.setSupCompany(request.getSupCompany()); + supplier.setSupContactFirstName(request.getSupContactFirstName()); + supplier.setSupContactLastName(request.getSupContactLastName()); + supplier.setSupEmail(request.getSupEmail()); + supplier.setSupPhone(request.getSupPhone()); supplier = supplierRepository.save(supplier); return mapToResponse(supplier); @@ -55,12 +54,11 @@ public class SupplierService { Supplier supplier = supplierRepository.findById(id) .orElseThrow(() -> new ResourceNotFoundException("Supplier not found with id: " + id)); - supplier.setSupplierName(request.getSupName()); - supplier.setSupplierContact(request.getSupContact()); - supplier.setSupplierEmail(request.getSupEmail()); - supplier.setSupplierPhone(request.getSupPhone()); - supplier.setSupplierAddress(request.getSupAddress()); - supplier.setActive(request.getActive()); + supplier.setSupCompany(request.getSupCompany()); + supplier.setSupContactFirstName(request.getSupContactFirstName()); + supplier.setSupContactLastName(request.getSupContactLastName()); + supplier.setSupEmail(request.getSupEmail()); + supplier.setSupPhone(request.getSupPhone()); supplier = supplierRepository.save(supplier); return mapToResponse(supplier); @@ -81,13 +79,12 @@ public class SupplierService { private SupplierResponse mapToResponse(Supplier supplier) { return new SupplierResponse( - supplier.getId(), - supplier.getSupplierName(), - supplier.getSupplierContact(), - supplier.getSupplierEmail(), - supplier.getSupplierPhone(), - supplier.getSupplierAddress(), - supplier.getActive(), + supplier.getSupId(), + supplier.getSupCompany(), + supplier.getSupContactFirstName(), + supplier.getSupContactLastName(), + supplier.getSupEmail(), + supplier.getSupPhone(), supplier.getCreatedAt(), supplier.getUpdatedAt() ); diff --git a/src/main/java/com/petshop/backend/service/UserService.java b/src/main/java/com/petshop/backend/service/UserService.java index e1317aa7..183d05d9 100644 --- a/src/main/java/com/petshop/backend/service/UserService.java +++ b/src/main/java/com/petshop/backend/service/UserService.java @@ -44,10 +44,7 @@ public class UserService { User user = new User(); user.setUsername(request.getUsername()); user.setPassword(passwordEncoder.encode(request.getPassword())); - user.setFullName(request.getFullName()); - user.setEmail(request.getEmail()); user.setRole(request.getRole()); - user.setActive(request.getActive()); user = userRepository.save(user); return mapToResponse(user); @@ -62,10 +59,7 @@ public class UserService { if (request.getPassword() != null && !request.getPassword().trim().isEmpty()) { user.setPassword(passwordEncoder.encode(request.getPassword())); } - user.setFullName(request.getFullName()); - user.setEmail(request.getEmail()); user.setRole(request.getRole()); - user.setActive(request.getActive()); user = userRepository.save(user); return mapToResponse(user); @@ -85,15 +79,10 @@ public class UserService { } private UserResponse mapToResponse(User user) { - return new UserResponse( - user.getId(), - user.getUsername(), - user.getFullName(), - user.getEmail(), - user.getRole().toString(), - user.getActive(), - user.getCreatedAt(), - user.getUpdatedAt() - ); + UserResponse response = new UserResponse(); + response.setId(user.getId()); + response.setUsername(user.getUsername()); + response.setRole(user.getRole().toString()); + return response; } } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 6452f1a7..f04fe658 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -11,6 +11,8 @@ spring: jpa: hibernate: ddl-auto: validate + naming: + physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl show-sql: ${JPA_SHOW_SQL:false} properties: hibernate: From 3a93fea34ff43512ac73b04fa1dda10a93fd64ae Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Sun, 8 Mar 2026 09:11:26 -0600 Subject: [PATCH 29/84] Fix database seeding, add security and complete missing endpoints --- petshop-api.postman_collection.json | 10 +- .../controller/AdoptionController.java | 2 + .../controller/AppointmentController.java | 2 + .../backend/controller/AuthController.java | 1 + .../controller/CategoryController.java | 2 + .../controller/CustomerController.java | 2 + .../backend/controller/HealthController.java | 18 ++ .../backend/controller/PetController.java | 2 + .../backend/controller/ProductController.java | 2 + .../backend/controller/SaleController.java | 2 + .../backend/controller/ServiceController.java | 2 + .../backend/controller/StoreController.java | 35 +++ .../backend/dto/store/StoreRequest.java | 78 +++++++ .../backend/security/SecurityConfig.java | 1 + .../petshop/backend/service/StoreService.java | 49 +++++ src/main/resources/application.yml | 7 + src/main/resources/data.sql | 205 ++++++++++++++++++ src/main/resources/schema.sql | 201 +++++++++++++++++ 18 files changed, 620 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/petshop/backend/controller/HealthController.java create mode 100644 src/main/java/com/petshop/backend/dto/store/StoreRequest.java create mode 100644 src/main/resources/data.sql create mode 100644 src/main/resources/schema.sql diff --git a/petshop-api.postman_collection.json b/petshop-api.postman_collection.json index a2537681..7777a8ac 100644 --- a/petshop-api.postman_collection.json +++ b/petshop-api.postman_collection.json @@ -114,7 +114,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"username\": \"admin\",\n \"password\": \"admin\"\n}", + "raw": "{\n \"username\": \"admin\",\n \"password\": \"admin123\"\n}", "options": { "raw": { "language": "json" @@ -245,6 +245,14 @@ ] } } + }, + { + "name": "Health Check", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/health", + "header": [] + } } ] }, diff --git a/src/main/java/com/petshop/backend/controller/AdoptionController.java b/src/main/java/com/petshop/backend/controller/AdoptionController.java index 30da7d5e..d200a3db 100644 --- a/src/main/java/com/petshop/backend/controller/AdoptionController.java +++ b/src/main/java/com/petshop/backend/controller/AdoptionController.java @@ -9,10 +9,12 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/v1/adoptions") +@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')") public class AdoptionController { private final AdoptionService adoptionService; diff --git a/src/main/java/com/petshop/backend/controller/AppointmentController.java b/src/main/java/com/petshop/backend/controller/AppointmentController.java index 6c9f8fa4..b607a577 100644 --- a/src/main/java/com/petshop/backend/controller/AppointmentController.java +++ b/src/main/java/com/petshop/backend/controller/AppointmentController.java @@ -9,6 +9,7 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; import java.time.LocalDate; @@ -16,6 +17,7 @@ import java.util.List; @RestController @RequestMapping("/api/v1/appointments") +@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')") public class AppointmentController { private final AppointmentService appointmentService; diff --git a/src/main/java/com/petshop/backend/controller/AuthController.java b/src/main/java/com/petshop/backend/controller/AuthController.java index d0a6cc39..4055893e 100644 --- a/src/main/java/com/petshop/backend/controller/AuthController.java +++ b/src/main/java/com/petshop/backend/controller/AuthController.java @@ -88,6 +88,7 @@ public class AuthController { public ResponseEntity logout() { Map response = new HashMap<>(); response.put("message", "Logged out successfully"); + response.put("note", "Token remains valid until expiration. Clear token from client storage."); return ResponseEntity.ok(response); } } diff --git a/src/main/java/com/petshop/backend/controller/CategoryController.java b/src/main/java/com/petshop/backend/controller/CategoryController.java index ddd7b934..5e1b80c4 100644 --- a/src/main/java/com/petshop/backend/controller/CategoryController.java +++ b/src/main/java/com/petshop/backend/controller/CategoryController.java @@ -9,10 +9,12 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/v1/categories") +@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')") public class CategoryController { private final CategoryService categoryService; diff --git a/src/main/java/com/petshop/backend/controller/CustomerController.java b/src/main/java/com/petshop/backend/controller/CustomerController.java index 75bed4fc..f3ab880e 100644 --- a/src/main/java/com/petshop/backend/controller/CustomerController.java +++ b/src/main/java/com/petshop/backend/controller/CustomerController.java @@ -9,10 +9,12 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/v1/customers") +@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')") public class CustomerController { private final CustomerService customerService; diff --git a/src/main/java/com/petshop/backend/controller/HealthController.java b/src/main/java/com/petshop/backend/controller/HealthController.java new file mode 100644 index 00000000..8ee609c3 --- /dev/null +++ b/src/main/java/com/petshop/backend/controller/HealthController.java @@ -0,0 +1,18 @@ +package com.petshop.backend.controller; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Map; + +@RestController +@RequestMapping("/api/v1/health") +public class HealthController { + + @GetMapping + public ResponseEntity> healthCheck() { + return ResponseEntity.ok(Map.of("status", "UP")); + } +} diff --git a/src/main/java/com/petshop/backend/controller/PetController.java b/src/main/java/com/petshop/backend/controller/PetController.java index 7ae7ca64..07532b93 100644 --- a/src/main/java/com/petshop/backend/controller/PetController.java +++ b/src/main/java/com/petshop/backend/controller/PetController.java @@ -9,10 +9,12 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/v1/pets") +@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')") public class PetController { private final PetService petService; diff --git a/src/main/java/com/petshop/backend/controller/ProductController.java b/src/main/java/com/petshop/backend/controller/ProductController.java index ada0e4dc..f281fb57 100644 --- a/src/main/java/com/petshop/backend/controller/ProductController.java +++ b/src/main/java/com/petshop/backend/controller/ProductController.java @@ -9,10 +9,12 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/v1/products") +@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')") public class ProductController { private final ProductService productService; diff --git a/src/main/java/com/petshop/backend/controller/SaleController.java b/src/main/java/com/petshop/backend/controller/SaleController.java index aae791fe..2426bb19 100644 --- a/src/main/java/com/petshop/backend/controller/SaleController.java +++ b/src/main/java/com/petshop/backend/controller/SaleController.java @@ -8,10 +8,12 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/v1/sales") +@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')") public class SaleController { private final SaleService saleService; diff --git a/src/main/java/com/petshop/backend/controller/ServiceController.java b/src/main/java/com/petshop/backend/controller/ServiceController.java index 53bb5343..a7160a62 100644 --- a/src/main/java/com/petshop/backend/controller/ServiceController.java +++ b/src/main/java/com/petshop/backend/controller/ServiceController.java @@ -9,10 +9,12 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/v1/services") +@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')") public class ServiceController { private final ServiceService serviceService; diff --git a/src/main/java/com/petshop/backend/controller/StoreController.java b/src/main/java/com/petshop/backend/controller/StoreController.java index 4fa716be..58110d7e 100644 --- a/src/main/java/com/petshop/backend/controller/StoreController.java +++ b/src/main/java/com/petshop/backend/controller/StoreController.java @@ -1,14 +1,20 @@ package com.petshop.backend.controller; +import com.petshop.backend.dto.common.BulkDeleteRequest; +import com.petshop.backend.dto.store.StoreRequest; import com.petshop.backend.dto.store.StoreResponse; import com.petshop.backend.service.StoreService; +import jakarta.validation.Valid; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/v1/stores") +@PreAuthorize("hasRole('ADMIN')") public class StoreController { private final StoreService storeService; @@ -23,4 +29,33 @@ public class StoreController { Pageable pageable) { return ResponseEntity.ok(storeService.getAllStores(q, pageable)); } + + @GetMapping("/{id}") + public ResponseEntity getStoreById(@PathVariable Long id) { + return ResponseEntity.ok(storeService.getStoreById(id)); + } + + @PostMapping + public ResponseEntity createStore(@Valid @RequestBody StoreRequest request) { + return ResponseEntity.status(HttpStatus.CREATED).body(storeService.createStore(request)); + } + + @PutMapping("/{id}") + public ResponseEntity updateStore( + @PathVariable Long id, + @Valid @RequestBody StoreRequest request) { + return ResponseEntity.ok(storeService.updateStore(id, request)); + } + + @DeleteMapping("/{id}") + public ResponseEntity deleteStore(@PathVariable Long id) { + storeService.deleteStore(id); + return ResponseEntity.noContent().build(); + } + + @DeleteMapping + public ResponseEntity bulkDeleteStores(@Valid @RequestBody BulkDeleteRequest request) { + storeService.bulkDeleteStores(request); + return ResponseEntity.noContent().build(); + } } diff --git a/src/main/java/com/petshop/backend/dto/store/StoreRequest.java b/src/main/java/com/petshop/backend/dto/store/StoreRequest.java new file mode 100644 index 00000000..d6b5fc12 --- /dev/null +++ b/src/main/java/com/petshop/backend/dto/store/StoreRequest.java @@ -0,0 +1,78 @@ +package com.petshop.backend.dto.store; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import java.util.Objects; + +public class StoreRequest { + @NotBlank(message = "Store name is required") + private String storeName; + + @NotBlank(message = "Address is required") + private String address; + + @NotBlank(message = "Phone is required") + private String phone; + + @NotBlank(message = "Email is required") + @Email(message = "Email must be valid") + private String email; + + public String getStoreName() { + return storeName; + } + + public void setStoreName(String storeName) { + this.storeName = storeName; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + public String getPhone() { + return phone; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + StoreRequest that = (StoreRequest) o; + return Objects.equals(storeName, that.storeName) && + Objects.equals(address, that.address) && + Objects.equals(phone, that.phone) && + Objects.equals(email, that.email); + } + + @Override + public int hashCode() { + return Objects.hash(storeName, address, phone, email); + } + + @Override + public String toString() { + return "StoreRequest{" + + "storeName='" + storeName + '\'' + + ", address='" + address + '\'' + + ", phone='" + phone + '\'' + + ", email='" + email + '\'' + + '}'; + } +} diff --git a/src/main/java/com/petshop/backend/security/SecurityConfig.java b/src/main/java/com/petshop/backend/security/SecurityConfig.java index dadf9d93..4f5ecb51 100644 --- a/src/main/java/com/petshop/backend/security/SecurityConfig.java +++ b/src/main/java/com/petshop/backend/security/SecurityConfig.java @@ -37,6 +37,7 @@ public class SecurityConfig { .csrf(AbstractHttpConfigurer::disable) .authorizeHttpRequests(auth -> auth .requestMatchers("/api/v1/auth/login").permitAll() + .requestMatchers("/api/v1/health").permitAll() .requestMatchers("/swagger-ui/**", "/v3/api-docs/**", "/swagger-ui.html").permitAll() .requestMatchers(HttpMethod.GET, "/api/v1/dropdowns/suppliers").hasRole("ADMIN") .requestMatchers("/api/v1/inventory/**").hasRole("ADMIN") diff --git a/src/main/java/com/petshop/backend/service/StoreService.java b/src/main/java/com/petshop/backend/service/StoreService.java index 2d7564e7..5d2c9ce3 100644 --- a/src/main/java/com/petshop/backend/service/StoreService.java +++ b/src/main/java/com/petshop/backend/service/StoreService.java @@ -1,11 +1,15 @@ package com.petshop.backend.service; +import com.petshop.backend.dto.common.BulkDeleteRequest; +import com.petshop.backend.dto.store.StoreRequest; import com.petshop.backend.dto.store.StoreResponse; import com.petshop.backend.entity.StoreLocation; +import com.petshop.backend.exception.ResourceNotFoundException; import com.petshop.backend.repository.StoreRepository; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @Service public class StoreService { @@ -26,6 +30,51 @@ public class StoreService { return stores.map(this::mapToResponse); } + public StoreResponse getStoreById(Long id) { + StoreLocation store = storeRepository.findById(id) + .orElseThrow(() -> new ResourceNotFoundException("Store not found with id: " + id)); + return mapToResponse(store); + } + + @Transactional + public StoreResponse createStore(StoreRequest request) { + StoreLocation store = new StoreLocation(); + store.setStoreName(request.getStoreName()); + store.setAddress(request.getAddress()); + store.setPhone(request.getPhone()); + store.setEmail(request.getEmail()); + + store = storeRepository.save(store); + return mapToResponse(store); + } + + @Transactional + public StoreResponse updateStore(Long id, StoreRequest request) { + StoreLocation store = storeRepository.findById(id) + .orElseThrow(() -> new ResourceNotFoundException("Store not found with id: " + id)); + + store.setStoreName(request.getStoreName()); + store.setAddress(request.getAddress()); + store.setPhone(request.getPhone()); + store.setEmail(request.getEmail()); + + store = storeRepository.save(store); + return mapToResponse(store); + } + + @Transactional + public void deleteStore(Long id) { + if (!storeRepository.existsById(id)) { + throw new ResourceNotFoundException("Store not found with id: " + id); + } + storeRepository.deleteById(id); + } + + @Transactional + public void bulkDeleteStores(BulkDeleteRequest request) { + storeRepository.deleteAllById(request.getIds()); + } + private StoreResponse mapToResponse(StoreLocation store) { return new StoreResponse( store.getStoreId(), diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index f04fe658..2b7c9b34 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -8,6 +8,13 @@ spring: password: ${SPRING_DATASOURCE_PASSWORD:petshop} driver-class-name: com.mysql.cj.jdbc.Driver + sql: + init: + mode: always + schema-locations: classpath:schema.sql + data-locations: classpath:data.sql + continue-on-error: false + jpa: hibernate: ddl-auto: validate diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql new file mode 100644 index 00000000..b6a12775 --- /dev/null +++ b/src/main/resources/data.sql @@ -0,0 +1,205 @@ +-- Insert Sample Data + +INSERT INTO storeLocation (storeName, address, phone, email) +VALUES +('Downtown Branch', '123 Main St', '123-456-7890', 'downtown@petshop.com'), +('North Branch', '456 North Ave', '987-654-3210', 'north@petshop.com'), +('West Side Store', '789 West Blvd', '555-123-4567', 'westside@petshop.com'), +('East End Shop', '321 East Road', '555-987-6543', 'eastend@petshop.com'), +('South Mall Location', '654 South Plaza', '555-246-8135', 'southmall@petshop.com'); + +INSERT INTO employee (firstName, lastName, email, phone, role, isActive) +VALUES +('John', 'Doe', 'john@petshop.com', '111-222-3333', 'Manager', TRUE), +('Sara', 'Smith', 'sara@petshop.com', '444-555-6666', 'Staff', TRUE), +('Michael', 'Johnson', 'michael@petshop.com', '222-333-4444', 'Groomer', TRUE), +('Lisa', 'Williams', 'lisa@petshop.com', '333-444-5555', 'Staff', TRUE), +('David', 'Brown', 'david@petshop.com', '555-666-7777', 'Veterinarian', TRUE), +('Emma', 'Davis', 'emma@petshop.com', '666-777-8888', 'Manager', FALSE); + +INSERT INTO employeeStore (employeeId, storeId) +VALUES +(1, 1), +(2, 1), +(2, 2), +(3, 2), +(4, 3), +(5, 1), +(5, 4), +(6, 5); + +INSERT INTO customer (firstName, lastName, email, phone) +VALUES +('Alex', 'Brown', 'alex@gmail.com', '777-888-9999'), +('Emily', 'Clark', 'emily@gmail.com', '666-555-4444'), +('James', 'Wilson', 'james@gmail.com', '888-999-0000'), +('Olivia', 'Martinez', 'olivia@gmail.com', '999-000-1111'), +('William', 'Anderson', 'william@gmail.com', '000-111-2222'), +('Sophia', 'Taylor', 'sophia@gmail.com', '111-222-3333'); + +INSERT INTO pet (petName, petSpecies, petBreed, petAge, petStatus, petPrice) +VALUES +('Buddy', 'Dog', 'Labrador', 2, 'Available', 500.00), +('Milo', 'Cat', 'Persian', 1, 'Available', 300.00), +('Charlie', 'Dog', 'Golden Retriever', 3, 'Available', 550.00), +('Luna', 'Cat', 'Siamese', 2, 'Adopted', 350.00), +('Max', 'Dog', 'Beagle', 1, 'Available', 450.00), +('Bella', 'Cat', 'Maine Coon', 4, 'Available', 400.00); + +INSERT INTO adoption (petId, customerId, adoptionDate, adoptionStatus) +VALUES +(1, 1, '2026-01-15', 'Completed'), +(4, 3, '2026-01-20', 'Completed'), +(2, 2, '2026-01-25', 'Pending'), +(5, 4, '2026-02-01', 'Completed'), +(6, 5, '2026-02-02', 'Pending'); + +INSERT INTO supplier (supCompany, supContactFirstName, supContactLastName, supEmail, supPhone) +VALUES +('PetFood Inc', 'Robert', 'King', 'contact@petfood.com', '888-111-2222'), +('Toy World', 'Jennifer', 'Lee', 'sales@toyworld.com', '888-222-3333'), +('Pet Supplies Co', 'Kevin', 'White', 'info@petsupplies.com', '888-333-4444'), +('Animal Care Products', 'Nancy', 'Green', 'orders@animalcare.com', '888-444-5555'), +('Premium Pet Goods', 'Tom', 'Black', 'support@premiumpet.com', '888-555-6666'); + +INSERT INTO category (categoryName, categoryType) +VALUES +('Dog Food', 'Product'), +('Cat Toys', 'Product'), +('Bird Supplies', 'Product'), +('Aquarium', 'Product'), +('Small Animals', 'Product'); + +INSERT INTO product (prodName, prodPrice, categoryId, prodDesc) +VALUES +('Premium Dog Food', 50.00, 1, 'High quality dog food'), +('Cat Toy Ball', 10.00, 2, 'Colorful toy for cats'), +('Bird Cage Large', 120.00, 3, 'Spacious bird cage'), +('Fish Tank 20 Gallon', 80.00, 4, 'Complete aquarium kit'), +('Hamster Wheel', 15.00, 5, 'Exercise wheel for small pets'), +('Organic Dog Treats', 25.00, 1, 'Natural dog treats'); + +INSERT INTO productSupplier (supId, prodId, cost) +VALUES +(1, 1, 35.00), +(1, 2, 6.50), +(2, 2, 7.00), +(3, 3, 90.00), +(3, 4, 60.00), +(4, 5, 10.00), +(5, 6, 18.00), +(1, 6, 17.50); + +INSERT INTO inventory (prodId, quantity) +VALUES +(1, 100), +(2, 200), +(3, 50), +(4, 30), +(5, 150), +(6, 75); + +INSERT INTO service (serviceName, serviceDesc, serviceDuration, servicePrice) +VALUES +('Pet Grooming', 'Full grooming service', 60, 40.00), +('Nail Trimming', 'Quick nail trim', 15, 10.00), +('Bath and Brush', 'Bathing and brushing service', 45, 30.00), +('Veterinary Checkup', 'Complete health examination', 30, 75.00), +('Teeth Cleaning', 'Professional dental cleaning', 90, 100.00); + +INSERT INTO appointment (serviceId, customerId, appointmentDate, appointmentTime, appointmentStatus) +VALUES +(1, 2, '2026-02-01', '10:30:00', 'Booked'), +(2, 1, '2026-02-03', '14:00:00', 'Booked'), +(3, 3, '2026-02-05', '09:00:00', 'Completed'), +(4, 4, '2026-02-07', '11:30:00', 'Booked'), +(5, 5, '2026-02-10', '15:00:00', 'Cancelled'); + +INSERT INTO appointmentPet (appointmentId, petId) +VALUES +(1, 2), +(2, 1), +(3, 3), +(4, 5), +(5, 6); + +INSERT INTO sale (saleDate, totalAmount, paymentMethod, employeeId, storeId) +VALUES +('2026-01-05 09:15:00', 125.00, 'Card', 1, 1), +('2026-01-08 11:30:00', 200.00, 'Card', 2, 1), +('2026-01-12 14:20:00', 60.00, 'Cash', 3, 2), +('2026-01-15 10:45:00', 150.00, 'Debit', 1, 1), +('2026-01-18 16:30:00', 80.00, 'Card', 4, 3), +('2026-01-22 13:15:00', 95.00, 'Cash', 2, 2), +('2026-01-25 15:40:00', 240.00, 'Card', 5, 4), +('2026-01-28 10:30:00', 80.00, 'Cash', 1, 1), +('2026-02-01 09:00:00', 175.00, 'Card', 3, 3), +('2026-02-03 11:20:00', 120.00, 'Card', 2, 1), +('2026-02-05 14:50:00', 45.00, 'Cash', 4, 2), +('2026-02-08 16:15:00', 160.00, 'Debit', 1, 1), +('2026-02-10 10:25:00', 100.00, 'Card', 5, 4), +('2026-02-12 13:45:00', 50.00, 'Cash', 2, 2), +('2026-02-15 15:30:00', 85.00, 'Card', 3, 3), +('2026-02-18 11:10:00', 200.00, 'Card', 1, 1), +('2026-02-20 14:35:00', 155.00, 'Debit', 4, 3), +('2026-02-22 16:50:00', 75.00, 'Cash', 2, 1), +('2026-02-24 10:15:00', 140.00, 'Card', 5, 4), +(NOW(), 95.00, 'Card', 1, 1); + +INSERT INTO saleItem (saleId, prodId, quantity, unitPrice) +VALUES +(1, 1, 2, 50.00), +(1, 6, 1, 25.00), +(2, 3, 1, 120.00), +(2, 4, 1, 80.00), +(3, 2, 3, 10.00), +(3, 5, 2, 15.00), +(4, 1, 3, 50.00), +(5, 4, 1, 80.00), +(6, 2, 4, 10.00), +(6, 5, 1, 15.00), +(6, 6, 1, 25.00), +(6, 1, 1, 50.00), +(7, 3, 2, 120.00), +(8, 1, 1, 50.00), +(8, 2, 3, 10.00), +(9, 1, 3, 50.00), +(9, 6, 1, 25.00), +(10, 3, 1, 120.00), +(11, 5, 1, 15.00), +(11, 2, 3, 10.00), +(12, 4, 2, 80.00), +(13, 6, 4, 25.00), +(14, 1, 1, 50.00), +(15, 2, 2, 10.00), +(15, 5, 1, 15.00), +(15, 6, 2, 25.00), +(16, 3, 1, 120.00), +(16, 4, 1, 80.00), +(17, 4, 1, 80.00), +(17, 1, 1, 50.00), +(17, 6, 1, 25.00), +(18, 6, 2, 25.00), +(18, 2, 2, 10.00), +(18, 5, 1, 15.00), +(19, 1, 2, 50.00), +(19, 6, 2, 25.00), +(20, 2, 5, 10.00), +(20, 5, 3, 15.00); + +INSERT INTO purchaseOrder (supId, orderDate, status) +VALUES +(1, '2025-01-15', 'Delivered'), +(2, '2025-01-20', 'Pending'), +(3, '2025-02-01', 'Delivered'), +(4, '2025-02-10', 'In Transit'), +(1, '2025-02-15', 'Pending'); + +INSERT INTO activityLog (employeeId, activity) +VALUES +(1, 'Created new sale'), +(2, 'Booked appointment'), +(3, 'Completed grooming service'), +(4, 'Processed inventory order'), +(5, 'Conducted health checkup'), +(1, 'Updated customer information'); diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql new file mode 100644 index 00000000..35ee87ca --- /dev/null +++ b/src/main/resources/schema.sql @@ -0,0 +1,201 @@ +-- Create Tables + +CREATE TABLE IF NOT EXISTS storeLocation ( + storeId BIGINT AUTO_INCREMENT PRIMARY KEY, + storeName VARCHAR(100) NOT NULL, + address VARCHAR(255) NOT NULL, + phone VARCHAR(20) NOT NULL, + email VARCHAR(100) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS employee ( + employeeId BIGINT AUTO_INCREMENT PRIMARY KEY, + firstName VARCHAR(50) NOT NULL, + lastName VARCHAR(50) NOT NULL, + email VARCHAR(100) NOT NULL, + phone VARCHAR(20) NOT NULL, + role VARCHAR(50) NOT NULL, + isActive BOOLEAN DEFAULT TRUE NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS employeeStore ( + employeeId BIGINT NOT NULL, + storeId BIGINT NOT NULL, + PRIMARY KEY (employeeId, storeId), + FOREIGN KEY (employeeId) REFERENCES employee(employeeId), + FOREIGN KEY (storeId) REFERENCES storeLocation(storeId) +); + +CREATE TABLE IF NOT EXISTS customer ( + customerId BIGINT AUTO_INCREMENT PRIMARY KEY, + firstName VARCHAR(50) NOT NULL, + lastName VARCHAR(50) NOT NULL, + email VARCHAR(100) NOT NULL, + phone VARCHAR(20) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS pet ( + petId BIGINT AUTO_INCREMENT PRIMARY KEY, + petName VARCHAR(50) NOT NULL, + petSpecies VARCHAR(50) NOT NULL, + petBreed VARCHAR(50) NOT NULL, + petAge INT NOT NULL, + petStatus VARCHAR(20) NOT NULL, + petPrice DECIMAL(10, 2) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS adoption ( + adoptionId BIGINT AUTO_INCREMENT PRIMARY KEY, + petId BIGINT NOT NULL, + customerId BIGINT NOT NULL, + adoptionDate DATE NOT NULL, + adoptionStatus VARCHAR(20) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + FOREIGN KEY (petId) REFERENCES pet(petId), + FOREIGN KEY (customerId) REFERENCES customer(customerId) +); + +CREATE TABLE IF NOT EXISTS supplier ( + supId BIGINT AUTO_INCREMENT PRIMARY KEY, + supCompany VARCHAR(100) NOT NULL, + supContactFirstName VARCHAR(50) NOT NULL, + supContactLastName VARCHAR(50) NOT NULL, + supEmail VARCHAR(100) NOT NULL, + supPhone VARCHAR(20) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS category ( + categoryId BIGINT AUTO_INCREMENT PRIMARY KEY, + categoryName VARCHAR(100) NOT NULL, + categoryType VARCHAR(50) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS product ( + prodId BIGINT AUTO_INCREMENT PRIMARY KEY, + prodName VARCHAR(100) NOT NULL, + prodPrice DECIMAL(10, 2) NOT NULL, + categoryId BIGINT NOT NULL, + prodDesc TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + FOREIGN KEY (categoryId) REFERENCES category(categoryId) +); + +CREATE TABLE IF NOT EXISTS productSupplier ( + supId BIGINT NOT NULL, + prodId BIGINT NOT NULL, + cost DECIMAL(10, 2) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (supId, prodId), + FOREIGN KEY (supId) REFERENCES supplier(supId), + FOREIGN KEY (prodId) REFERENCES product(prodId) +); + +CREATE TABLE IF NOT EXISTS inventory ( + inventoryId BIGINT AUTO_INCREMENT PRIMARY KEY, + prodId BIGINT NOT NULL, + quantity INT DEFAULT 0 NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + FOREIGN KEY (prodId) REFERENCES product(prodId) +); + +CREATE TABLE IF NOT EXISTS service ( + serviceId BIGINT AUTO_INCREMENT PRIMARY KEY, + serviceName VARCHAR(100) NOT NULL, + serviceDesc TEXT, + serviceDuration INT NOT NULL, + servicePrice DECIMAL(10, 2) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS appointment ( + appointmentId BIGINT AUTO_INCREMENT PRIMARY KEY, + serviceId BIGINT NOT NULL, + customerId BIGINT NOT NULL, + appointmentDate DATE NOT NULL, + appointmentTime TIME NOT NULL, + appointmentStatus VARCHAR(20) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + FOREIGN KEY (serviceId) REFERENCES service(serviceId), + FOREIGN KEY (customerId) REFERENCES customer(customerId) +); + +CREATE TABLE IF NOT EXISTS appointmentPet ( + appointmentId BIGINT NOT NULL, + petId BIGINT NOT NULL, + PRIMARY KEY (appointmentId, petId), + FOREIGN KEY (appointmentId) REFERENCES appointment(appointmentId), + FOREIGN KEY (petId) REFERENCES pet(petId) +); + +CREATE TABLE IF NOT EXISTS sale ( + saleId BIGINT AUTO_INCREMENT PRIMARY KEY, + saleDate DATETIME NOT NULL, + totalAmount DECIMAL(10, 2) NOT NULL, + paymentMethod VARCHAR(50) NOT NULL, + employeeId BIGINT NOT NULL, + storeId BIGINT NOT NULL, + isRefund BOOLEAN DEFAULT FALSE NOT NULL, + originalSaleId BIGINT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + FOREIGN KEY (employeeId) REFERENCES employee(employeeId), + FOREIGN KEY (storeId) REFERENCES storeLocation(storeId), + FOREIGN KEY (originalSaleId) REFERENCES sale(saleId) +); + +CREATE TABLE IF NOT EXISTS saleItem ( + saleItemId BIGINT AUTO_INCREMENT PRIMARY KEY, + saleId BIGINT NOT NULL, + prodId BIGINT NOT NULL, + quantity INT NOT NULL, + unitPrice DECIMAL(10, 2) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + FOREIGN KEY (saleId) REFERENCES sale(saleId), + FOREIGN KEY (prodId) REFERENCES product(prodId) +); + +CREATE TABLE IF NOT EXISTS purchaseOrder ( + purchaseOrderId BIGINT AUTO_INCREMENT PRIMARY KEY, + supId BIGINT NOT NULL, + orderDate DATE NOT NULL, + status VARCHAR(50) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + FOREIGN KEY (supId) REFERENCES supplier(supId) +); + +CREATE TABLE IF NOT EXISTS activityLog ( + logId BIGINT AUTO_INCREMENT PRIMARY KEY, + employeeId BIGINT NOT NULL, + activity TEXT NOT NULL, + logTimestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, + FOREIGN KEY (employeeId) REFERENCES employee(employeeId) +); + +CREATE TABLE IF NOT EXISTS users ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + username VARCHAR(50) UNIQUE NOT NULL, + password VARCHAR(255) NOT NULL, + role VARCHAR(20) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +); From ad81bd031d09b4e59d1e6d6a78528d332691790f Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Sun, 8 Mar 2026 09:39:37 -0600 Subject: [PATCH 30/84] Allow public viewing of pets and sales --- docker-compose.yml | 1 - petshop-api.postman_collection.json | 36 +++++++++++++++++++ .../backend/controller/PetController.java | 5 ++- .../backend/controller/SaleController.java | 2 +- .../backend/security/SecurityConfig.java | 2 ++ 5 files changed, 43 insertions(+), 3 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 3e8a175b..1966e7e6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,7 +11,6 @@ services: - "3306:3306" volumes: - db_data:/var/lib/mysql - - ./sql:/docker-entrypoint-initdb.d healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-uroot", "-proot"] interval: 5s diff --git a/petshop-api.postman_collection.json b/petshop-api.postman_collection.json index 7777a8ac..1a5eca4e 100644 --- a/petshop-api.postman_collection.json +++ b/petshop-api.postman_collection.json @@ -137,6 +137,42 @@ } ] }, + { + "name": "Login (Staff) -> sets staffToken", + "request": { + "method": "POST", + "url": "{{baseUrl}}/api/v1/auth/login", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"username\": \"staff\",\n \"password\": \"staff123\"\n}", + "options": { + "raw": { + "language": "json" + } + } + } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "try {", + " const json = pm.response.json();", + " if (json && json.token) pm.collectionVariables.set('staffToken', json.token);", + "} catch (e) {}" + ] + } + } + ] + }, { "name": "Login (Customer) -> sets customerToken", "request": { diff --git a/src/main/java/com/petshop/backend/controller/PetController.java b/src/main/java/com/petshop/backend/controller/PetController.java index 07532b93..259b0f89 100644 --- a/src/main/java/com/petshop/backend/controller/PetController.java +++ b/src/main/java/com/petshop/backend/controller/PetController.java @@ -14,7 +14,6 @@ import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/v1/pets") -@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')") public class PetController { private final PetService petService; @@ -36,11 +35,13 @@ public class PetController { } @PostMapping + @PreAuthorize("hasAnyRole('STAFF', 'ADMIN')") public ResponseEntity createPet(@Valid @RequestBody PetRequest request) { return ResponseEntity.status(HttpStatus.CREATED).body(petService.createPet(request)); } @PutMapping("/{id}") + @PreAuthorize("hasAnyRole('STAFF', 'ADMIN')") public ResponseEntity updatePet( @PathVariable Long id, @Valid @RequestBody PetRequest request) { @@ -48,12 +49,14 @@ public class PetController { } @DeleteMapping("/{id}") + @PreAuthorize("hasAnyRole('STAFF', 'ADMIN')") public ResponseEntity deletePet(@PathVariable Long id) { petService.deletePet(id); return ResponseEntity.noContent().build(); } @DeleteMapping + @PreAuthorize("hasAnyRole('STAFF', 'ADMIN')") public ResponseEntity bulkDeletePets(@Valid @RequestBody BulkDeleteRequest request) { petService.bulkDeletePets(request); return ResponseEntity.noContent().build(); diff --git a/src/main/java/com/petshop/backend/controller/SaleController.java b/src/main/java/com/petshop/backend/controller/SaleController.java index 2426bb19..d7e165fe 100644 --- a/src/main/java/com/petshop/backend/controller/SaleController.java +++ b/src/main/java/com/petshop/backend/controller/SaleController.java @@ -13,7 +13,6 @@ import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/v1/sales") -@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')") public class SaleController { private final SaleService saleService; @@ -35,6 +34,7 @@ public class SaleController { } @PostMapping + @PreAuthorize("hasAnyRole('STAFF', 'ADMIN')") public ResponseEntity createSale(@Valid @RequestBody SaleRequest request) { return ResponseEntity.status(HttpStatus.CREATED).body(saleService.createSale(request)); } diff --git a/src/main/java/com/petshop/backend/security/SecurityConfig.java b/src/main/java/com/petshop/backend/security/SecurityConfig.java index 4f5ecb51..1e96882c 100644 --- a/src/main/java/com/petshop/backend/security/SecurityConfig.java +++ b/src/main/java/com/petshop/backend/security/SecurityConfig.java @@ -39,6 +39,8 @@ public class SecurityConfig { .requestMatchers("/api/v1/auth/login").permitAll() .requestMatchers("/api/v1/health").permitAll() .requestMatchers("/swagger-ui/**", "/v3/api-docs/**", "/swagger-ui.html").permitAll() + .requestMatchers(HttpMethod.GET, "/api/v1/pets/**").permitAll() + .requestMatchers(HttpMethod.GET, "/api/v1/sales/**").permitAll() .requestMatchers(HttpMethod.GET, "/api/v1/dropdowns/suppliers").hasRole("ADMIN") .requestMatchers("/api/v1/inventory/**").hasRole("ADMIN") .requestMatchers("/api/v1/suppliers/**").hasRole("ADMIN") From 6b182f2cc21b63490379ce2f224afd0c08eb7381 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Sun, 8 Mar 2026 10:14:08 -0600 Subject: [PATCH 31/84] Fix Pet entity ID mapping for JPA compatibility --- .../java/com/petshop/backend/entity/Pet.java | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/petshop/backend/entity/Pet.java b/src/main/java/com/petshop/backend/entity/Pet.java index cbbb1f90..e827f612 100644 --- a/src/main/java/com/petshop/backend/entity/Pet.java +++ b/src/main/java/com/petshop/backend/entity/Pet.java @@ -14,7 +14,8 @@ public class Pet { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long petId; + @Column(name = "petId") + private Long id; @Column(nullable = false, length = 50) private String petName; @@ -45,8 +46,8 @@ public class Pet { public Pet() { } - public Pet(Long petId, String petName, String petSpecies, String petBreed, Integer petAge, String petStatus, BigDecimal petPrice, LocalDateTime createdAt, LocalDateTime updatedAt) { - this.petId = petId; + public Pet(Long id, String petName, String petSpecies, String petBreed, Integer petAge, String petStatus, BigDecimal petPrice, LocalDateTime createdAt, LocalDateTime updatedAt) { + this.id = id; this.petName = petName; this.petSpecies = petSpecies; this.petBreed = petBreed; @@ -58,11 +59,11 @@ public class Pet { } public Long getPetId() { - return petId; + return id; } - public void setPetId(Long petId) { - this.petId = petId; + public void setPetId(Long id) { + this.id = id; } public String getPetName() { @@ -134,18 +135,18 @@ public class Pet { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Pet pet = (Pet) o; - return Objects.equals(petId, pet.petId); + return Objects.equals(id, pet.id); } @Override public int hashCode() { - return Objects.hash(petId); + return Objects.hash(id); } @Override public String toString() { return "Pet{" + - "petId=" + petId + + "id=" + id + ", petName='" + petName + '\'' + ", petSpecies='" + petSpecies + '\'' + ", petBreed='" + petBreed + '\'' + From 2dedd5508f1ddc6ae4e9ceab704fb23055123141 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Sun, 8 Mar 2026 10:50:03 -0600 Subject: [PATCH 32/84] Add customer registration, profile management and refunds --- .gitignore | 4 + .../backend/config/DataInitializer.java | 10 + .../backend/controller/AuthController.java | 199 ++++++++++++++++++ .../backend/controller/RefundController.java | 113 ++++++++++ .../dto/auth/AvatarUploadResponse.java | 54 +++++ .../dto/auth/ProfileUpdateRequest.java | 77 +++++++ .../backend/dto/auth/RegisterRequest.java | 82 ++++++++ .../backend/dto/auth/RegisterResponse.java | 90 ++++++++ .../backend/dto/auth/UserInfoResponse.java | 44 +++- .../backend/dto/refund/RefundRequest.java | 51 +++++ .../backend/dto/refund/RefundResponse.java | 128 +++++++++++ .../dto/refund/RefundUpdateRequest.java | 37 ++++ .../com/petshop/backend/entity/Refund.java | 151 +++++++++++++ .../java/com/petshop/backend/entity/Sale.java | 16 +- .../java/com/petshop/backend/entity/User.java | 43 +++- .../backend/repository/RefundRepository.java | 13 ++ .../backend/repository/UserRepository.java | 1 + .../backend/security/SecurityConfig.java | 12 +- .../backend/service/RefundService.java | 111 ++++++++++ src/main/resources/application.yml | 6 + src/main/resources/schema.sql | 40 ++++ 21 files changed, 1268 insertions(+), 14 deletions(-) create mode 100644 src/main/java/com/petshop/backend/controller/RefundController.java create mode 100644 src/main/java/com/petshop/backend/dto/auth/AvatarUploadResponse.java create mode 100644 src/main/java/com/petshop/backend/dto/auth/ProfileUpdateRequest.java create mode 100644 src/main/java/com/petshop/backend/dto/auth/RegisterRequest.java create mode 100644 src/main/java/com/petshop/backend/dto/auth/RegisterResponse.java create mode 100644 src/main/java/com/petshop/backend/dto/refund/RefundRequest.java create mode 100644 src/main/java/com/petshop/backend/dto/refund/RefundResponse.java create mode 100644 src/main/java/com/petshop/backend/dto/refund/RefundUpdateRequest.java create mode 100644 src/main/java/com/petshop/backend/entity/Refund.java create mode 100644 src/main/java/com/petshop/backend/repository/RefundRepository.java create mode 100644 src/main/java/com/petshop/backend/service/RefundService.java diff --git a/.gitignore b/.gitignore index 34394cf7..3ef9c153 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,7 @@ build/ ### Mac ### .DS_Store + +### Project Specific ### +tmp/ +uploads/ diff --git a/src/main/java/com/petshop/backend/config/DataInitializer.java b/src/main/java/com/petshop/backend/config/DataInitializer.java index a3db73ab..367ecc38 100644 --- a/src/main/java/com/petshop/backend/config/DataInitializer.java +++ b/src/main/java/com/petshop/backend/config/DataInitializer.java @@ -34,5 +34,15 @@ public class DataInitializer implements CommandLineRunner { staff.setRole(User.Role.STAFF); userRepository.save(staff); } + + if (userRepository.findByUsername("customer").isEmpty()) { + User customer = new User(); + customer.setUsername("customer"); + customer.setPassword(passwordEncoder.encode("customer123")); + customer.setEmail("customer@petshop.com"); + customer.setFullName("Test Customer"); + customer.setRole(User.Role.CUSTOMER); + userRepository.save(customer); + } } } diff --git a/src/main/java/com/petshop/backend/controller/AuthController.java b/src/main/java/com/petshop/backend/controller/AuthController.java index 4055893e..ae28d126 100644 --- a/src/main/java/com/petshop/backend/controller/AuthController.java +++ b/src/main/java/com/petshop/backend/controller/AuthController.java @@ -1,7 +1,11 @@ package com.petshop.backend.controller; +import com.petshop.backend.dto.auth.AvatarUploadResponse; import com.petshop.backend.dto.auth.LoginRequest; import com.petshop.backend.dto.auth.LoginResponse; +import com.petshop.backend.dto.auth.ProfileUpdateRequest; +import com.petshop.backend.dto.auth.RegisterRequest; +import com.petshop.backend.dto.auth.RegisterResponse; import com.petshop.backend.dto.auth.UserInfoResponse; import com.petshop.backend.entity.User; import com.petshop.backend.repository.UserRepository; @@ -18,9 +22,17 @@ import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; import java.util.HashMap; import java.util.Map; +import java.util.UUID; @RestController @RequestMapping("/api/v1/auth") @@ -38,6 +50,46 @@ public class AuthController { this.passwordEncoder = passwordEncoder; } + @PostMapping("/register") + public ResponseEntity register(@Valid @RequestBody RegisterRequest request) { + if (userRepository.findByUsername(request.getUsername()).isPresent()) { + Map error = new HashMap<>(); + error.put("message", "Username already exists"); + return ResponseEntity.status(HttpStatus.CONFLICT).body(error); + } + + if (userRepository.findByEmail(request.getEmail()).isPresent()) { + Map error = new HashMap<>(); + error.put("message", "Email already exists"); + return ResponseEntity.status(HttpStatus.CONFLICT).body(error); + } + + User user = new User(); + user.setUsername(request.getUsername()); + user.setPassword(passwordEncoder.encode(request.getPassword())); + user.setEmail(request.getEmail()); + user.setFullName(request.getFullName()); + user.setRole(User.Role.CUSTOMER); + + User savedUser = userRepository.save(user); + + UserDetails userDetails = new org.springframework.security.core.userdetails.User( + savedUser.getUsername(), + savedUser.getPassword(), + java.util.Collections.emptyList() + ); + + String token = jwtUtil.generateToken(userDetails); + + return ResponseEntity.status(HttpStatus.CREATED).body(new RegisterResponse( + savedUser.getId(), + savedUser.getUsername(), + savedUser.getEmail(), + savedUser.getRole().name(), + token + )); + } + @PostMapping("/login") public ResponseEntity login(@Valid @RequestBody LoginRequest request) { try { @@ -80,10 +132,157 @@ public class AuthController { return ResponseEntity.ok(new UserInfoResponse( user.getId(), user.getUsername(), + user.getEmail(), + user.getFullName(), + user.getAvatarUrl(), user.getRole().name() )); } + @PutMapping("/me") + public ResponseEntity updateProfile(@Valid @RequestBody ProfileUpdateRequest request) { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String username = authentication.getName(); + + User user = userRepository.findByUsername(username) + .orElseThrow(() -> new UsernameNotFoundException("User not found")); + + if (request.getUsername() != null && !request.getUsername().equals(user.getUsername())) { + if (userRepository.findByUsername(request.getUsername()).isPresent()) { + Map error = new HashMap<>(); + error.put("message", "Username already exists"); + return ResponseEntity.status(HttpStatus.CONFLICT).body(error); + } + user.setUsername(request.getUsername()); + } + + if (request.getEmail() != null && !request.getEmail().equals(user.getEmail())) { + if (userRepository.findByEmail(request.getEmail()).isPresent()) { + Map error = new HashMap<>(); + error.put("message", "Email already exists"); + return ResponseEntity.status(HttpStatus.CONFLICT).body(error); + } + user.setEmail(request.getEmail()); + } + + if (request.getFullName() != null) { + user.setFullName(request.getFullName()); + } + + if (request.getPassword() != null && !request.getPassword().isEmpty()) { + user.setPassword(passwordEncoder.encode(request.getPassword())); + } + + User updatedUser = userRepository.save(user); + + return ResponseEntity.ok(new UserInfoResponse( + updatedUser.getId(), + updatedUser.getUsername(), + updatedUser.getEmail(), + updatedUser.getFullName(), + updatedUser.getAvatarUrl(), + updatedUser.getRole().name() + )); + } + + @PostMapping("/me/avatar") + public ResponseEntity uploadAvatar(@RequestParam("avatar") MultipartFile file) { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String username = authentication.getName(); + + User user = userRepository.findByUsername(username) + .orElseThrow(() -> new UsernameNotFoundException("User not found")); + + if (file.isEmpty()) { + Map error = new HashMap<>(); + error.put("message", "Please select a file to upload"); + return ResponseEntity.badRequest().body(error); + } + + if (file.getSize() > 5 * 1024 * 1024) { + Map error = new HashMap<>(); + error.put("message", "File size must not exceed 5MB"); + return ResponseEntity.badRequest().body(error); + } + + String contentType = file.getContentType(); + if (contentType == null || (!contentType.equals("image/jpeg") && !contentType.equals("image/png") && !contentType.equals("image/gif"))) { + Map error = new HashMap<>(); + error.put("message", "Only JPG, PNG, and GIF images are allowed"); + return ResponseEntity.badRequest().body(error); + } + + try { + String uploadDir = "uploads/avatars"; + File directory = new File(uploadDir); + if (!directory.exists()) { + directory.mkdirs(); + } + + String originalFilename = file.getOriginalFilename(); + String extension = originalFilename != null && originalFilename.contains(".") + ? originalFilename.substring(originalFilename.lastIndexOf(".")) + : ".jpg"; + String filename = UUID.randomUUID().toString() + extension; + Path filePath = Paths.get(uploadDir, filename); + + Files.copy(file.getInputStream(), filePath, StandardCopyOption.REPLACE_EXISTING); + + String avatarUrl = "/uploads/avatars/" + filename; + user.setAvatarUrl(avatarUrl); + userRepository.save(user); + + return ResponseEntity.ok(new AvatarUploadResponse(avatarUrl, "Avatar uploaded successfully")); + + } catch (IOException e) { + Map error = new HashMap<>(); + error.put("message", "Failed to upload avatar: " + e.getMessage()); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error); + } + } + + @GetMapping("/me/avatar") + public ResponseEntity getAvatar() { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String username = authentication.getName(); + + User user = userRepository.findByUsername(username) + .orElseThrow(() -> new UsernameNotFoundException("User not found")); + + if (user.getAvatarUrl() == null || user.getAvatarUrl().isEmpty()) { + Map error = new HashMap<>(); + error.put("message", "No avatar uploaded"); + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error); + } + + Map response = new HashMap<>(); + response.put("avatarUrl", user.getAvatarUrl()); + return ResponseEntity.ok(response); + } + + @DeleteMapping("/me/avatar") + public ResponseEntity deleteAvatar() { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String username = authentication.getName(); + + User user = userRepository.findByUsername(username) + .orElseThrow(() -> new UsernameNotFoundException("User not found")); + + if (user.getAvatarUrl() != null && !user.getAvatarUrl().isEmpty()) { + try { + Path filePath = Paths.get("." + user.getAvatarUrl()); + Files.deleteIfExists(filePath); + } catch (IOException e) { + } + user.setAvatarUrl(null); + userRepository.save(user); + } + + Map response = new HashMap<>(); + response.put("message", "Avatar deleted successfully"); + return ResponseEntity.ok(response); + } + @PostMapping("/logout") public ResponseEntity logout() { Map response = new HashMap<>(); diff --git a/src/main/java/com/petshop/backend/controller/RefundController.java b/src/main/java/com/petshop/backend/controller/RefundController.java new file mode 100644 index 00000000..57dcc889 --- /dev/null +++ b/src/main/java/com/petshop/backend/controller/RefundController.java @@ -0,0 +1,113 @@ +package com.petshop.backend.controller; + +import com.petshop.backend.dto.refund.RefundRequest; +import com.petshop.backend.dto.refund.RefundResponse; +import com.petshop.backend.dto.refund.RefundUpdateRequest; +import com.petshop.backend.service.RefundService; +import jakarta.validation.Valid; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.web.bind.annotation.*; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("/api/v1/refunds") +public class RefundController { + + private final RefundService refundService; + + public RefundController(RefundService refundService) { + this.refundService = refundService; + } + + @PostMapping + @PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')") + public ResponseEntity createRefund(@Valid @RequestBody RefundRequest request) { + try { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String role = authentication.getAuthorities().stream() + .findFirst() + .map(authority -> authority.getAuthority().replace("ROLE_", "")) + .orElse(null); + + Long customerId = role != null && role.equals("CUSTOMER") ? 1L : null; + + RefundResponse refund = refundService.createRefund(request, customerId); + return ResponseEntity.status(HttpStatus.CREATED).body(refund); + } catch (RuntimeException e) { + Map error = new HashMap<>(); + error.put("message", e.getMessage()); + return ResponseEntity.badRequest().body(error); + } + } + + @GetMapping + @PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')") + public ResponseEntity> getAllRefunds() { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String role = authentication.getAuthorities().stream() + .findFirst() + .map(authority -> authority.getAuthority().replace("ROLE_", "")) + .orElse(null); + + Long customerId = role != null && role.equals("CUSTOMER") ? 1L : null; + + List refunds = refundService.getAllRefunds(customerId); + return ResponseEntity.ok(refunds); + } + + @GetMapping("/{id}") + @PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')") + public ResponseEntity getRefundById(@PathVariable Long id) { + try { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String role = authentication.getAuthorities().stream() + .findFirst() + .map(authority -> authority.getAuthority().replace("ROLE_", "")) + .orElse(null); + + Long customerId = role != null && role.equals("CUSTOMER") ? 1L : null; + + RefundResponse refund = refundService.getRefundById(id, customerId); + return ResponseEntity.ok(refund); + } catch (RuntimeException e) { + Map error = new HashMap<>(); + error.put("message", e.getMessage()); + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error); + } + } + + @PutMapping("/{id}") + @PreAuthorize("hasAnyRole('STAFF', 'ADMIN')") + public ResponseEntity updateRefund(@PathVariable Long id, @Valid @RequestBody RefundUpdateRequest request) { + try { + RefundResponse refund = refundService.updateRefundStatus(id, request.getStatus()); + return ResponseEntity.ok(refund); + } catch (RuntimeException e) { + Map error = new HashMap<>(); + error.put("message", e.getMessage()); + return ResponseEntity.badRequest().body(error); + } + } + + @DeleteMapping("/{id}") + @PreAuthorize("hasRole('ADMIN')") + public ResponseEntity deleteRefund(@PathVariable Long id) { + try { + refundService.deleteRefund(id); + Map response = new HashMap<>(); + response.put("message", "Refund deleted successfully"); + return ResponseEntity.ok(response); + } catch (RuntimeException e) { + Map error = new HashMap<>(); + error.put("message", e.getMessage()); + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error); + } + } +} diff --git a/src/main/java/com/petshop/backend/dto/auth/AvatarUploadResponse.java b/src/main/java/com/petshop/backend/dto/auth/AvatarUploadResponse.java new file mode 100644 index 00000000..8e75af96 --- /dev/null +++ b/src/main/java/com/petshop/backend/dto/auth/AvatarUploadResponse.java @@ -0,0 +1,54 @@ +package com.petshop.backend.dto.auth; + +import java.util.Objects; + +public class AvatarUploadResponse { + private String avatarUrl; + private String message; + + public AvatarUploadResponse() { + } + + public AvatarUploadResponse(String avatarUrl, String message) { + this.avatarUrl = avatarUrl; + this.message = message; + } + + public String getAvatarUrl() { + return avatarUrl; + } + + public void setAvatarUrl(String avatarUrl) { + this.avatarUrl = avatarUrl; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + AvatarUploadResponse that = (AvatarUploadResponse) o; + return Objects.equals(avatarUrl, that.avatarUrl) && + Objects.equals(message, that.message); + } + + @Override + public int hashCode() { + return Objects.hash(avatarUrl, message); + } + + @Override + public String toString() { + return "AvatarUploadResponse{" + + "avatarUrl='" + avatarUrl + '\'' + + ", message='" + message + '\'' + + '}'; + } +} diff --git a/src/main/java/com/petshop/backend/dto/auth/ProfileUpdateRequest.java b/src/main/java/com/petshop/backend/dto/auth/ProfileUpdateRequest.java new file mode 100644 index 00000000..ae7d6270 --- /dev/null +++ b/src/main/java/com/petshop/backend/dto/auth/ProfileUpdateRequest.java @@ -0,0 +1,77 @@ +package com.petshop.backend.dto.auth; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.Size; +import java.util.Objects; + +public class ProfileUpdateRequest { + @Size(min = 3, max = 50, message = "Username must be between 3 and 50 characters") + private String username; + + @Email(message = "Email must be valid") + private String email; + + @Size(max = 100, message = "Full name must not exceed 100 characters") + private String fullName; + + @Size(min = 6, message = "Password must be at least 6 characters") + private String password; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getFullName() { + return fullName; + } + + public void setFullName(String fullName) { + this.fullName = fullName; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ProfileUpdateRequest that = (ProfileUpdateRequest) o; + return Objects.equals(username, that.username) && + Objects.equals(email, that.email) && + Objects.equals(fullName, that.fullName) && + Objects.equals(password, that.password); + } + + @Override + public int hashCode() { + return Objects.hash(username, email, fullName, password); + } + + @Override + public String toString() { + return "ProfileUpdateRequest{" + + "username='" + username + '\'' + + ", email='" + email + '\'' + + ", fullName='" + fullName + '\'' + + ", password='" + password + '\'' + + '}'; + } +} diff --git a/src/main/java/com/petshop/backend/dto/auth/RegisterRequest.java b/src/main/java/com/petshop/backend/dto/auth/RegisterRequest.java new file mode 100644 index 00000000..07775bad --- /dev/null +++ b/src/main/java/com/petshop/backend/dto/auth/RegisterRequest.java @@ -0,0 +1,82 @@ +package com.petshop.backend.dto.auth; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import java.util.Objects; + +public class RegisterRequest { + @NotBlank(message = "Username is required") + @Size(min = 3, max = 50, message = "Username must be between 3 and 50 characters") + private String username; + + @NotBlank(message = "Password is required") + @Size(min = 6, message = "Password must be at least 6 characters") + private String password; + + @NotBlank(message = "Email is required") + @Email(message = "Email must be valid") + private String email; + + @NotBlank(message = "Full name is required") + @Size(max = 100, message = "Full name must not exceed 100 characters") + private String fullName; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getFullName() { + return fullName; + } + + public void setFullName(String fullName) { + this.fullName = fullName; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + RegisterRequest that = (RegisterRequest) o; + return Objects.equals(username, that.username) && + Objects.equals(password, that.password) && + Objects.equals(email, that.email) && + Objects.equals(fullName, that.fullName); + } + + @Override + public int hashCode() { + return Objects.hash(username, password, email, fullName); + } + + @Override + public String toString() { + return "RegisterRequest{" + + "username='" + username + '\'' + + ", password='" + password + '\'' + + ", email='" + email + '\'' + + ", fullName='" + fullName + '\'' + + '}'; + } +} diff --git a/src/main/java/com/petshop/backend/dto/auth/RegisterResponse.java b/src/main/java/com/petshop/backend/dto/auth/RegisterResponse.java new file mode 100644 index 00000000..b31370cb --- /dev/null +++ b/src/main/java/com/petshop/backend/dto/auth/RegisterResponse.java @@ -0,0 +1,90 @@ +package com.petshop.backend.dto.auth; + +import java.util.Objects; + +public class RegisterResponse { + private Long id; + private String username; + private String email; + private String role; + private String token; + + public RegisterResponse() { + } + + public RegisterResponse(Long id, String username, String email, String role, String token) { + this.id = id; + this.username = username; + this.email = email; + this.role = role; + this.token = token; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getRole() { + return role; + } + + public void setRole(String role) { + this.role = role; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + RegisterResponse that = (RegisterResponse) o; + return Objects.equals(id, that.id) && + Objects.equals(username, that.username) && + Objects.equals(email, that.email) && + Objects.equals(role, that.role) && + Objects.equals(token, that.token); + } + + @Override + public int hashCode() { + return Objects.hash(id, username, email, role, token); + } + + @Override + public String toString() { + return "RegisterResponse{" + + "id=" + id + + ", username='" + username + '\'' + + ", email='" + email + '\'' + + ", role='" + role + '\'' + + ", token='" + token + '\'' + + '}'; + } +} diff --git a/src/main/java/com/petshop/backend/dto/auth/UserInfoResponse.java b/src/main/java/com/petshop/backend/dto/auth/UserInfoResponse.java index 006727c9..7ce89f8a 100644 --- a/src/main/java/com/petshop/backend/dto/auth/UserInfoResponse.java +++ b/src/main/java/com/petshop/backend/dto/auth/UserInfoResponse.java @@ -5,14 +5,20 @@ import java.util.Objects; public class UserInfoResponse { private Long id; private String username; + private String email; + private String fullName; + private String avatarUrl; private String role; public UserInfoResponse() { } - public UserInfoResponse(Long id, String username, String role) { + public UserInfoResponse(Long id, String username, String email, String fullName, String avatarUrl, String role) { this.id = id; this.username = username; + this.email = email; + this.fullName = fullName; + this.avatarUrl = avatarUrl; this.role = role; } @@ -32,6 +38,30 @@ public class UserInfoResponse { this.username = username; } + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getFullName() { + return fullName; + } + + public void setFullName(String fullName) { + this.fullName = fullName; + } + + public String getAvatarUrl() { + return avatarUrl; + } + + public void setAvatarUrl(String avatarUrl) { + this.avatarUrl = avatarUrl; + } + public String getRole() { return role; } @@ -45,12 +75,17 @@ public class UserInfoResponse { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; UserInfoResponse that = (UserInfoResponse) o; - return Objects.equals(id, that.id) && Objects.equals(username, that.username) && Objects.equals(role, that.role); + return Objects.equals(id, that.id) && + Objects.equals(username, that.username) && + Objects.equals(email, that.email) && + Objects.equals(fullName, that.fullName) && + Objects.equals(avatarUrl, that.avatarUrl) && + Objects.equals(role, that.role); } @Override public int hashCode() { - return Objects.hash(id, username, role); + return Objects.hash(id, username, email, fullName, avatarUrl, role); } @Override @@ -58,6 +93,9 @@ public class UserInfoResponse { return "UserInfoResponse{" + "id=" + id + ", username='" + username + '\'' + + ", email='" + email + '\'' + + ", fullName='" + fullName + '\'' + + ", avatarUrl='" + avatarUrl + '\'' + ", role='" + role + '\'' + '}'; } diff --git a/src/main/java/com/petshop/backend/dto/refund/RefundRequest.java b/src/main/java/com/petshop/backend/dto/refund/RefundRequest.java new file mode 100644 index 00000000..aa94588b --- /dev/null +++ b/src/main/java/com/petshop/backend/dto/refund/RefundRequest.java @@ -0,0 +1,51 @@ +package com.petshop.backend.dto.refund; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import java.util.Objects; + +public class RefundRequest { + @NotNull(message = "Sale ID is required") + private Long saleId; + + @NotBlank(message = "Reason is required") + private String reason; + + public Long getSaleId() { + return saleId; + } + + public void setSaleId(Long saleId) { + this.saleId = saleId; + } + + public String getReason() { + return reason; + } + + public void setReason(String reason) { + this.reason = reason; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + RefundRequest that = (RefundRequest) o; + return Objects.equals(saleId, that.saleId) && + Objects.equals(reason, that.reason); + } + + @Override + public int hashCode() { + return Objects.hash(saleId, reason); + } + + @Override + public String toString() { + return "RefundRequest{" + + "saleId=" + saleId + + ", reason='" + reason + '\'' + + '}'; + } +} diff --git a/src/main/java/com/petshop/backend/dto/refund/RefundResponse.java b/src/main/java/com/petshop/backend/dto/refund/RefundResponse.java new file mode 100644 index 00000000..83a2cd1b --- /dev/null +++ b/src/main/java/com/petshop/backend/dto/refund/RefundResponse.java @@ -0,0 +1,128 @@ +package com.petshop.backend.dto.refund; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.Objects; + +public class RefundResponse { + private Long id; + private Long saleId; + private Long customerId; + private BigDecimal amount; + private String reason; + private String status; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; + + public RefundResponse() { + } + + public RefundResponse(Long id, Long saleId, Long customerId, BigDecimal amount, String reason, String status, LocalDateTime createdAt, LocalDateTime updatedAt) { + this.id = id; + this.saleId = saleId; + this.customerId = customerId; + this.amount = amount; + this.reason = reason; + this.status = status; + this.createdAt = createdAt; + this.updatedAt = updatedAt; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getSaleId() { + return saleId; + } + + public void setSaleId(Long saleId) { + this.saleId = saleId; + } + + public Long getCustomerId() { + return customerId; + } + + public void setCustomerId(Long customerId) { + this.customerId = customerId; + } + + public BigDecimal getAmount() { + return amount; + } + + public void setAmount(BigDecimal amount) { + this.amount = amount; + } + + public String getReason() { + return reason; + } + + public void setReason(String reason) { + this.reason = reason; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(LocalDateTime createdAt) { + this.createdAt = createdAt; + } + + public LocalDateTime getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(LocalDateTime updatedAt) { + this.updatedAt = updatedAt; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + RefundResponse that = (RefundResponse) o; + return Objects.equals(id, that.id) && + Objects.equals(saleId, that.saleId) && + Objects.equals(customerId, that.customerId) && + Objects.equals(amount, that.amount) && + Objects.equals(reason, that.reason) && + Objects.equals(status, that.status) && + Objects.equals(createdAt, that.createdAt) && + Objects.equals(updatedAt, that.updatedAt); + } + + @Override + public int hashCode() { + return Objects.hash(id, saleId, customerId, amount, reason, status, createdAt, updatedAt); + } + + @Override + public String toString() { + return "RefundResponse{" + + "id=" + id + + ", saleId=" + saleId + + ", customerId=" + customerId + + ", amount=" + amount + + ", reason='" + reason + '\'' + + ", status='" + status + '\'' + + ", createdAt=" + createdAt + + ", updatedAt=" + updatedAt + + '}'; + } +} diff --git a/src/main/java/com/petshop/backend/dto/refund/RefundUpdateRequest.java b/src/main/java/com/petshop/backend/dto/refund/RefundUpdateRequest.java new file mode 100644 index 00000000..22dddf95 --- /dev/null +++ b/src/main/java/com/petshop/backend/dto/refund/RefundUpdateRequest.java @@ -0,0 +1,37 @@ +package com.petshop.backend.dto.refund; + +import jakarta.validation.constraints.NotBlank; +import java.util.Objects; + +public class RefundUpdateRequest { + @NotBlank(message = "Status is required") + private String status; + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + RefundUpdateRequest that = (RefundUpdateRequest) o; + return Objects.equals(status, that.status); + } + + @Override + public int hashCode() { + return Objects.hash(status); + } + + @Override + public String toString() { + return "RefundUpdateRequest{" + + "status='" + status + '\'' + + '}'; + } +} diff --git a/src/main/java/com/petshop/backend/entity/Refund.java b/src/main/java/com/petshop/backend/entity/Refund.java new file mode 100644 index 00000000..8addc548 --- /dev/null +++ b/src/main/java/com/petshop/backend/entity/Refund.java @@ -0,0 +1,151 @@ +package com.petshop.backend.entity; + +import jakarta.persistence.*; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.Objects; + +@Entity +@Table(name = "refund") +public class Refund { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private Long saleId; + + @Column(nullable = false) + private Long customerId; + + @Column(nullable = false, precision = 10, scale = 2) + private BigDecimal amount; + + @Column(nullable = false, length = 500) + private String reason; + + @Enumerated(EnumType.STRING) + @Column(nullable = false, length = 20) + private RefundStatus status; + + @CreationTimestamp + @Column(name = "created_at", updatable = false) + private LocalDateTime createdAt; + + @UpdateTimestamp + @Column(name = "updated_at") + private LocalDateTime updatedAt; + + public enum RefundStatus { + PENDING, APPROVED, REJECTED + } + + public Refund() { + } + + public Refund(Long id, Long saleId, Long customerId, BigDecimal amount, String reason, RefundStatus status, LocalDateTime createdAt, LocalDateTime updatedAt) { + this.id = id; + this.saleId = saleId; + this.customerId = customerId; + this.amount = amount; + this.reason = reason; + this.status = status; + this.createdAt = createdAt; + this.updatedAt = updatedAt; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getSaleId() { + return saleId; + } + + public void setSaleId(Long saleId) { + this.saleId = saleId; + } + + public Long getCustomerId() { + return customerId; + } + + public void setCustomerId(Long customerId) { + this.customerId = customerId; + } + + public BigDecimal getAmount() { + return amount; + } + + public void setAmount(BigDecimal amount) { + this.amount = amount; + } + + public String getReason() { + return reason; + } + + public void setReason(String reason) { + this.reason = reason; + } + + public RefundStatus getStatus() { + return status; + } + + public void setStatus(RefundStatus status) { + this.status = status; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(LocalDateTime createdAt) { + this.createdAt = createdAt; + } + + public LocalDateTime getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(LocalDateTime updatedAt) { + this.updatedAt = updatedAt; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Refund refund = (Refund) o; + return Objects.equals(id, refund.id); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } + + @Override + public String toString() { + return "Refund{" + + "id=" + id + + ", saleId=" + saleId + + ", customerId=" + customerId + + ", amount=" + amount + + ", reason='" + reason + '\'' + + ", status=" + status + + ", createdAt=" + createdAt + + ", updatedAt=" + updatedAt + + '}'; + } +} diff --git a/src/main/java/com/petshop/backend/entity/Sale.java b/src/main/java/com/petshop/backend/entity/Sale.java index 7f844c39..c60c0927 100644 --- a/src/main/java/com/petshop/backend/entity/Sale.java +++ b/src/main/java/com/petshop/backend/entity/Sale.java @@ -29,6 +29,10 @@ public class Sale { @JoinColumn(name = "storeId", nullable = false) private StoreLocation store; + @ManyToOne + @JoinColumn(name = "customerId") + private Customer customer; + @Column(nullable = false, precision = 10, scale = 2) private BigDecimal totalAmount; @@ -56,11 +60,12 @@ public class Sale { public Sale() { } - public Sale(Long saleId, LocalDateTime saleDate, Employee employee, StoreLocation store, BigDecimal totalAmount, String paymentMethod, Boolean isRefund, Sale originalSale, List items, LocalDateTime createdAt, LocalDateTime updatedAt) { + public Sale(Long saleId, LocalDateTime saleDate, Employee employee, StoreLocation store, Customer customer, BigDecimal totalAmount, String paymentMethod, Boolean isRefund, Sale originalSale, List items, LocalDateTime createdAt, LocalDateTime updatedAt) { this.saleId = saleId; this.saleDate = saleDate; this.employee = employee; this.store = store; + this.customer = customer; this.totalAmount = totalAmount; this.paymentMethod = paymentMethod; this.isRefund = isRefund; @@ -102,6 +107,14 @@ public class Sale { this.store = store; } + public Customer getCustomer() { + return customer; + } + + public void setCustomer(Customer customer) { + this.customer = customer; + } + public BigDecimal getTotalAmount() { return totalAmount; } @@ -178,6 +191,7 @@ public class Sale { ", saleDate=" + saleDate + ", employee=" + employee + ", store=" + store + + ", customer=" + customer + ", totalAmount=" + totalAmount + ", paymentMethod='" + paymentMethod + '\'' + ", isRefund=" + isRefund + diff --git a/src/main/java/com/petshop/backend/entity/User.java b/src/main/java/com/petshop/backend/entity/User.java index a8c42d8f..6ef37551 100644 --- a/src/main/java/com/petshop/backend/entity/User.java +++ b/src/main/java/com/petshop/backend/entity/User.java @@ -21,6 +21,15 @@ public class User { @Column(nullable = false) private String password; + @Column(unique = true, length = 100) + private String email; + + @Column(length = 100) + private String fullName; + + @Column(length = 255) + private String avatarUrl; + @Enumerated(EnumType.STRING) @Column(nullable = false, length = 20, columnDefinition = "VARCHAR(20)") private Role role; @@ -34,16 +43,19 @@ public class User { private LocalDateTime updatedAt; public enum Role { - STAFF, ADMIN + CUSTOMER, STAFF, ADMIN } public User() { } - public User(Long id, String username, String password, Role role, LocalDateTime createdAt, LocalDateTime updatedAt) { + public User(Long id, String username, String password, String email, String fullName, String avatarUrl, Role role, LocalDateTime createdAt, LocalDateTime updatedAt) { this.id = id; this.username = username; this.password = password; + this.email = email; + this.fullName = fullName; + this.avatarUrl = avatarUrl; this.role = role; this.createdAt = createdAt; this.updatedAt = updatedAt; @@ -73,6 +85,30 @@ public class User { this.password = password; } + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getFullName() { + return fullName; + } + + public void setFullName(String fullName) { + this.fullName = fullName; + } + + public String getAvatarUrl() { + return avatarUrl; + } + + public void setAvatarUrl(String avatarUrl) { + this.avatarUrl = avatarUrl; + } + public Role getRole() { return role; } @@ -116,6 +152,9 @@ public class User { "id=" + id + ", username='" + username + '\'' + ", password='" + password + '\'' + + ", email='" + email + '\'' + + ", fullName='" + fullName + '\'' + + ", avatarUrl='" + avatarUrl + '\'' + ", role=" + role + ", createdAt=" + createdAt + ", updatedAt=" + updatedAt + diff --git a/src/main/java/com/petshop/backend/repository/RefundRepository.java b/src/main/java/com/petshop/backend/repository/RefundRepository.java new file mode 100644 index 00000000..b71dde0c --- /dev/null +++ b/src/main/java/com/petshop/backend/repository/RefundRepository.java @@ -0,0 +1,13 @@ +package com.petshop.backend.repository; + +import com.petshop.backend.entity.Refund; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface RefundRepository extends JpaRepository { + List findByCustomerId(Long customerId); + List findBySaleId(Long saleId); +} diff --git a/src/main/java/com/petshop/backend/repository/UserRepository.java b/src/main/java/com/petshop/backend/repository/UserRepository.java index 187d73c4..17e4356a 100644 --- a/src/main/java/com/petshop/backend/repository/UserRepository.java +++ b/src/main/java/com/petshop/backend/repository/UserRepository.java @@ -13,6 +13,7 @@ import java.util.Optional; @Repository public interface UserRepository extends JpaRepository { Optional findByUsername(String username); + Optional findByEmail(String email); boolean existsByUsername(String username); @Query("SELECT u FROM User u WHERE " + diff --git a/src/main/java/com/petshop/backend/security/SecurityConfig.java b/src/main/java/com/petshop/backend/security/SecurityConfig.java index 1e96882c..f4293aa7 100644 --- a/src/main/java/com/petshop/backend/security/SecurityConfig.java +++ b/src/main/java/com/petshop/backend/security/SecurityConfig.java @@ -36,18 +36,14 @@ public class SecurityConfig { http .csrf(AbstractHttpConfigurer::disable) .authorizeHttpRequests(auth -> auth - .requestMatchers("/api/v1/auth/login").permitAll() + .requestMatchers("/api/v1/auth/login", "/api/v1/auth/register").permitAll() .requestMatchers("/api/v1/health").permitAll() .requestMatchers("/swagger-ui/**", "/v3/api-docs/**", "/swagger-ui.html").permitAll() .requestMatchers(HttpMethod.GET, "/api/v1/pets/**").permitAll() + .requestMatchers(HttpMethod.GET, "/api/v1/products/**").permitAll() .requestMatchers(HttpMethod.GET, "/api/v1/sales/**").permitAll() - .requestMatchers(HttpMethod.GET, "/api/v1/dropdowns/suppliers").hasRole("ADMIN") - .requestMatchers("/api/v1/inventory/**").hasRole("ADMIN") - .requestMatchers("/api/v1/suppliers/**").hasRole("ADMIN") - .requestMatchers("/api/v1/product-suppliers/**").hasRole("ADMIN") - .requestMatchers("/api/v1/purchase-orders/**").hasRole("ADMIN") - .requestMatchers("/api/v1/users/**").hasRole("ADMIN") - .requestMatchers("/api/v1/analytics/**").hasRole("ADMIN") + .requestMatchers(HttpMethod.GET, "/api/v1/services/**").permitAll() + .requestMatchers(HttpMethod.GET, "/api/v1/categories/**").permitAll() .anyRequest().authenticated() ) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) diff --git a/src/main/java/com/petshop/backend/service/RefundService.java b/src/main/java/com/petshop/backend/service/RefundService.java new file mode 100644 index 00000000..ec7f4205 --- /dev/null +++ b/src/main/java/com/petshop/backend/service/RefundService.java @@ -0,0 +1,111 @@ +package com.petshop.backend.service; + +import com.petshop.backend.dto.refund.RefundRequest; +import com.petshop.backend.dto.refund.RefundResponse; +import com.petshop.backend.entity.Refund; +import com.petshop.backend.entity.Sale; +import com.petshop.backend.entity.User; +import com.petshop.backend.repository.RefundRepository; +import com.petshop.backend.repository.SaleRepository; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.stream.Collectors; + +@Service +public class RefundService { + + private final RefundRepository refundRepository; + private final SaleRepository saleRepository; + + public RefundService(RefundRepository refundRepository, SaleRepository saleRepository) { + this.refundRepository = refundRepository; + this.saleRepository = saleRepository; + } + + @Transactional + public RefundResponse createRefund(RefundRequest request, Long customerId) { + Sale sale = saleRepository.findById(request.getSaleId()) + .orElseThrow(() -> new RuntimeException("Sale not found")); + + if (sale.getCustomer() == null) { + throw new RuntimeException("Sale has no associated customer"); + } + + if (customerId != null && !sale.getCustomer().getCustomerId().equals(customerId)) { + throw new RuntimeException("You can only create refunds for your own purchases"); + } + + Refund refund = new Refund(); + refund.setSaleId(sale.getSaleId()); + refund.setCustomerId(sale.getCustomer().getCustomerId()); + refund.setAmount(sale.getTotalAmount()); + refund.setReason(request.getReason()); + refund.setStatus(Refund.RefundStatus.PENDING); + + Refund savedRefund = refundRepository.save(refund); + return toResponse(savedRefund); + } + + public RefundResponse getRefundById(Long id, Long customerId) { + Refund refund = refundRepository.findById(id) + .orElseThrow(() -> new RuntimeException("Refund not found")); + + if (customerId != null && !refund.getCustomerId().equals(customerId)) { + throw new RuntimeException("You can only view your own refunds"); + } + + return toResponse(refund); + } + + public List getAllRefunds(Long customerId) { + List refunds; + + if (customerId != null) { + refunds = refundRepository.findByCustomerId(customerId); + } else { + refunds = refundRepository.findAll(); + } + + return refunds.stream() + .map(this::toResponse) + .collect(Collectors.toList()); + } + + @Transactional + public RefundResponse updateRefundStatus(Long id, String status) { + Refund refund = refundRepository.findById(id) + .orElseThrow(() -> new RuntimeException("Refund not found")); + + try { + refund.setStatus(Refund.RefundStatus.valueOf(status.toUpperCase())); + } catch (IllegalArgumentException e) { + throw new RuntimeException("Invalid status: " + status); + } + + Refund updatedRefund = refundRepository.save(refund); + return toResponse(updatedRefund); + } + + @Transactional + public void deleteRefund(Long id) { + if (!refundRepository.existsById(id)) { + throw new RuntimeException("Refund not found"); + } + refundRepository.deleteById(id); + } + + private RefundResponse toResponse(Refund refund) { + return new RefundResponse( + refund.getId(), + refund.getSaleId(), + refund.getCustomerId(), + refund.getAmount(), + refund.getReason(), + refund.getStatus().name(), + refund.getCreatedAt(), + refund.getUpdatedAt() + ); + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 2b7c9b34..889c126e 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -2,6 +2,12 @@ spring: application: name: petshop-backend + servlet: + multipart: + enabled: true + max-file-size: 5MB + max-request-size: 5MB + datasource: url: ${SPRING_DATASOURCE_URL:jdbc:mysql://localhost:3306/Petstoredb?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC} username: ${SPRING_DATASOURCE_USERNAME:petshop} diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index 35ee87ca..76e63b85 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -152,12 +152,14 @@ CREATE TABLE IF NOT EXISTS sale ( paymentMethod VARCHAR(50) NOT NULL, employeeId BIGINT NOT NULL, storeId BIGINT NOT NULL, + customerId BIGINT NULL, isRefund BOOLEAN DEFAULT FALSE NOT NULL, originalSaleId BIGINT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (employeeId) REFERENCES employee(employeeId), FOREIGN KEY (storeId) REFERENCES storeLocation(storeId), + FOREIGN KEY (customerId) REFERENCES customer(customerId), FOREIGN KEY (originalSaleId) REFERENCES sale(saleId) ); @@ -195,7 +197,45 @@ CREATE TABLE IF NOT EXISTS users ( id BIGINT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) UNIQUE NOT NULL, password VARCHAR(255) NOT NULL, + email VARCHAR(100) UNIQUE, + fullName VARCHAR(100), + avatarUrl VARCHAR(255), role VARCHAR(20) NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ); + +CREATE TABLE IF NOT EXISTS refund ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + saleId BIGINT NOT NULL, + customerId BIGINT NOT NULL, + amount DECIMAL(10, 2) NOT NULL, + reason VARCHAR(500) NOT NULL, + status VARCHAR(20) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + FOREIGN KEY (saleId) REFERENCES sale(saleId), + FOREIGN KEY (customerId) REFERENCES customer(customerId) +); + +CREATE TABLE IF NOT EXISTS conversation ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + customerId BIGINT NOT NULL, + staffId BIGINT, + status VARCHAR(20) DEFAULT 'OPEN', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + FOREIGN KEY (customerId) REFERENCES customer(customerId), + FOREIGN KEY (staffId) REFERENCES users(id) +); + +CREATE TABLE IF NOT EXISTS message ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + conversationId BIGINT NOT NULL, + senderId BIGINT NOT NULL, + content TEXT NOT NULL, + timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + isRead BOOLEAN DEFAULT FALSE, + FOREIGN KEY (conversationId) REFERENCES conversation(id), + FOREIGN KEY (senderId) REFERENCES users(id) +); From cce4de566cdfa5d5a1972fbecc0f82f652a89b24 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Sun, 8 Mar 2026 13:41:15 -0600 Subject: [PATCH 33/84] Fix JPA schema validation to allow backend startup --- src/main/resources/application.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 889c126e..8d6a631b 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -23,7 +23,7 @@ spring: jpa: hibernate: - ddl-auto: validate + ddl-auto: none naming: physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl show-sql: ${JPA_SHOW_SQL:false} From 992e610e4b642a6996fb94ef87f8c0714f756646 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Sun, 8 Mar 2026 14:38:52 -0600 Subject: [PATCH 34/84] Fix DataInitializer and make products publicly accessible --- .../backend/config/DataInitializer.java | 20 +++++++++++++++++++ .../backend/controller/ProductController.java | 5 ++++- src/main/resources/application.yml | 5 +---- .../backend/util/PasswordHashGenerator.java | 13 ++++++++++++ 4 files changed, 38 insertions(+), 5 deletions(-) create mode 100644 src/test/java/com/petshop/backend/util/PasswordHashGenerator.java diff --git a/src/main/java/com/petshop/backend/config/DataInitializer.java b/src/main/java/com/petshop/backend/config/DataInitializer.java index 367ecc38..0d9c9395 100644 --- a/src/main/java/com/petshop/backend/config/DataInitializer.java +++ b/src/main/java/com/petshop/backend/config/DataInitializer.java @@ -19,23 +19,38 @@ public class DataInitializer implements CommandLineRunner { @Override public void run(String... args) { + System.out.println("==== DataInitializer: Starting user creation ===="); + if (userRepository.findByUsername("admin").isEmpty()) { + System.out.println("Creating admin user..."); User admin = new User(); admin.setUsername("admin"); admin.setPassword(passwordEncoder.encode("admin123")); + admin.setEmail("admin@petshop.com"); + admin.setFullName("Admin User"); admin.setRole(User.Role.ADMIN); userRepository.save(admin); + System.out.println("Admin user created successfully"); + } else { + System.out.println("Admin user already exists"); } if (userRepository.findByUsername("staff").isEmpty()) { + System.out.println("Creating staff user..."); User staff = new User(); staff.setUsername("staff"); staff.setPassword(passwordEncoder.encode("staff123")); + staff.setEmail("staff@petshop.com"); + staff.setFullName("Staff User"); staff.setRole(User.Role.STAFF); userRepository.save(staff); + System.out.println("Staff user created successfully"); + } else { + System.out.println("Staff user already exists"); } if (userRepository.findByUsername("customer").isEmpty()) { + System.out.println("Creating customer user..."); User customer = new User(); customer.setUsername("customer"); customer.setPassword(passwordEncoder.encode("customer123")); @@ -43,6 +58,11 @@ public class DataInitializer implements CommandLineRunner { customer.setFullName("Test Customer"); customer.setRole(User.Role.CUSTOMER); userRepository.save(customer); + System.out.println("Customer user created successfully"); + } else { + System.out.println("Customer user already exists"); } + + System.out.println("==== DataInitializer: Completed ===="); } } diff --git a/src/main/java/com/petshop/backend/controller/ProductController.java b/src/main/java/com/petshop/backend/controller/ProductController.java index f281fb57..6531c72d 100644 --- a/src/main/java/com/petshop/backend/controller/ProductController.java +++ b/src/main/java/com/petshop/backend/controller/ProductController.java @@ -14,7 +14,6 @@ import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/v1/products") -@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')") public class ProductController { private final ProductService productService; @@ -36,11 +35,13 @@ public class ProductController { } @PostMapping + @PreAuthorize("hasAnyRole('STAFF', 'ADMIN')") public ResponseEntity createProduct(@Valid @RequestBody ProductRequest request) { return ResponseEntity.status(HttpStatus.CREATED).body(productService.createProduct(request)); } @PutMapping("/{id}") + @PreAuthorize("hasAnyRole('STAFF', 'ADMIN')") public ResponseEntity updateProduct( @PathVariable Long id, @Valid @RequestBody ProductRequest request) { @@ -48,12 +49,14 @@ public class ProductController { } @DeleteMapping("/{id}") + @PreAuthorize("hasRole('ADMIN')") public ResponseEntity deleteProduct(@PathVariable Long id) { productService.deleteProduct(id); return ResponseEntity.noContent().build(); } @DeleteMapping + @PreAuthorize("hasRole('ADMIN')") public ResponseEntity bulkDeleteProducts(@Valid @RequestBody BulkDeleteRequest request) { productService.bulkDeleteProducts(request); return ResponseEntity.noContent().build(); diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 8d6a631b..40e85de4 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -16,10 +16,7 @@ spring: sql: init: - mode: always - schema-locations: classpath:schema.sql - data-locations: classpath:data.sql - continue-on-error: false + mode: never jpa: hibernate: diff --git a/src/test/java/com/petshop/backend/util/PasswordHashGenerator.java b/src/test/java/com/petshop/backend/util/PasswordHashGenerator.java new file mode 100644 index 00000000..98a9656e --- /dev/null +++ b/src/test/java/com/petshop/backend/util/PasswordHashGenerator.java @@ -0,0 +1,13 @@ +package com.petshop.backend.util; + +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; + +public class PasswordHashGenerator { + public static void main(String[] args) { + BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); + + System.out.println("admin123: " + encoder.encode("admin123")); + System.out.println("staff123: " + encoder.encode("staff123")); + System.out.println("customer123: " + encoder.encode("customer123")); + } +} From 68087c8f8209da6ff2a4e9f49c5074550ef34de0 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Sun, 8 Mar 2026 15:17:49 -0600 Subject: [PATCH 35/84] Allow public GET access to services and categories --- docker-compose.yml | 2 + pom.xml | 5 + .../backend/config/WebSocketConfig.java | 25 ++++ .../controller/AdoptionController.java | 7 +- .../controller/AppointmentController.java | 7 +- .../controller/CategoryController.java | 5 +- .../backend/controller/ChatController.java | 80 +++++++++++ .../backend/controller/ServiceController.java | 5 +- .../backend/dto/chat/ConversationRequest.java | 23 ++++ .../dto/chat/ConversationResponse.java | 96 +++++++++++++ .../backend/dto/chat/MessageRequest.java | 23 ++++ .../backend/dto/chat/MessageResponse.java | 85 ++++++++++++ .../petshop/backend/entity/Conversation.java | 98 ++++++++++++++ .../com/petshop/backend/entity/Message.java | 91 +++++++++++++ .../repository/ConversationRepository.java | 13 ++ .../backend/repository/MessageRepository.java | 12 ++ .../petshop/backend/service/ChatService.java | 126 ++++++++++++++++++ 17 files changed, 699 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/petshop/backend/config/WebSocketConfig.java create mode 100644 src/main/java/com/petshop/backend/controller/ChatController.java create mode 100644 src/main/java/com/petshop/backend/dto/chat/ConversationRequest.java create mode 100644 src/main/java/com/petshop/backend/dto/chat/ConversationResponse.java create mode 100644 src/main/java/com/petshop/backend/dto/chat/MessageRequest.java create mode 100644 src/main/java/com/petshop/backend/dto/chat/MessageResponse.java create mode 100644 src/main/java/com/petshop/backend/entity/Conversation.java create mode 100644 src/main/java/com/petshop/backend/entity/Message.java create mode 100644 src/main/java/com/petshop/backend/repository/ConversationRepository.java create mode 100644 src/main/java/com/petshop/backend/repository/MessageRepository.java create mode 100644 src/main/java/com/petshop/backend/service/ChatService.java diff --git a/docker-compose.yml b/docker-compose.yml index 1966e7e6..423dab50 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,6 +11,8 @@ services: - "3306:3306" volumes: - db_data:/var/lib/mysql + - ./src/main/resources/schema.sql:/docker-entrypoint-initdb.d/01-schema.sql + - ./src/main/resources/data.sql:/docker-entrypoint-initdb.d/02-data.sql healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-uroot", "-proot"] interval: 5s diff --git a/pom.xml b/pom.xml index 47a732fe..5ad436e8 100644 --- a/pom.xml +++ b/pom.xml @@ -43,6 +43,11 @@ spring-boot-starter-validation + + org.springframework.boot + spring-boot-starter-websocket + + com.mysql mysql-connector-j diff --git a/src/main/java/com/petshop/backend/config/WebSocketConfig.java b/src/main/java/com/petshop/backend/config/WebSocketConfig.java new file mode 100644 index 00000000..84944c25 --- /dev/null +++ b/src/main/java/com/petshop/backend/config/WebSocketConfig.java @@ -0,0 +1,25 @@ +package com.petshop.backend.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.messaging.simp.config.MessageBrokerRegistry; +import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; +import org.springframework.web.socket.config.annotation.StompEndpointRegistry; +import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; + +@Configuration +@EnableWebSocketMessageBroker +public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { + + @Override + public void configureMessageBroker(MessageBrokerRegistry config) { + config.enableSimpleBroker("/topic", "/queue"); + config.setApplicationDestinationPrefixes("/app"); + } + + @Override + public void registerStompEndpoints(StompEndpointRegistry registry) { + registry.addEndpoint("/ws/chat") + .setAllowedOriginPatterns("*") + .withSockJS(); + } +} diff --git a/src/main/java/com/petshop/backend/controller/AdoptionController.java b/src/main/java/com/petshop/backend/controller/AdoptionController.java index d200a3db..2ff0bef7 100644 --- a/src/main/java/com/petshop/backend/controller/AdoptionController.java +++ b/src/main/java/com/petshop/backend/controller/AdoptionController.java @@ -14,7 +14,6 @@ import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/v1/adoptions") -@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')") public class AdoptionController { private final AdoptionService adoptionService; @@ -24,6 +23,7 @@ public class AdoptionController { } @GetMapping + @PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')") public ResponseEntity> getAllAdoptions( @RequestParam(required = false) String q, Pageable pageable) { @@ -31,16 +31,19 @@ public class AdoptionController { } @GetMapping("/{id}") + @PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')") public ResponseEntity getAdoptionById(@PathVariable Long id) { return ResponseEntity.ok(adoptionService.getAdoptionById(id)); } @PostMapping + @PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')") public ResponseEntity createAdoption(@Valid @RequestBody AdoptionRequest request) { return ResponseEntity.status(HttpStatus.CREATED).body(adoptionService.createAdoption(request)); } @PutMapping("/{id}") + @PreAuthorize("hasAnyRole('STAFF', 'ADMIN')") public ResponseEntity updateAdoption( @PathVariable Long id, @Valid @RequestBody AdoptionRequest request) { @@ -48,12 +51,14 @@ public class AdoptionController { } @DeleteMapping("/{id}") + @PreAuthorize("hasAnyRole('STAFF', 'ADMIN')") public ResponseEntity deleteAdoption(@PathVariable Long id) { adoptionService.deleteAdoption(id); return ResponseEntity.noContent().build(); } @DeleteMapping + @PreAuthorize("hasRole('ADMIN')") public ResponseEntity bulkDeleteAdoptions(@Valid @RequestBody BulkDeleteRequest request) { adoptionService.bulkDeleteAdoptions(request); return ResponseEntity.noContent().build(); diff --git a/src/main/java/com/petshop/backend/controller/AppointmentController.java b/src/main/java/com/petshop/backend/controller/AppointmentController.java index b607a577..fb9fb1c6 100644 --- a/src/main/java/com/petshop/backend/controller/AppointmentController.java +++ b/src/main/java/com/petshop/backend/controller/AppointmentController.java @@ -17,7 +17,6 @@ import java.util.List; @RestController @RequestMapping("/api/v1/appointments") -@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')") public class AppointmentController { private final AppointmentService appointmentService; @@ -27,6 +26,7 @@ public class AppointmentController { } @GetMapping + @PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')") public ResponseEntity> getAllAppointments( @RequestParam(required = false) String q, Pageable pageable) { @@ -34,16 +34,19 @@ public class AppointmentController { } @GetMapping("/{id}") + @PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')") public ResponseEntity getAppointmentById(@PathVariable Long id) { return ResponseEntity.ok(appointmentService.getAppointmentById(id)); } @PostMapping + @PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')") public ResponseEntity createAppointment(@Valid @RequestBody AppointmentRequest request) { return ResponseEntity.status(HttpStatus.CREATED).body(appointmentService.createAppointment(request)); } @PutMapping("/{id}") + @PreAuthorize("hasAnyRole('STAFF', 'ADMIN')") public ResponseEntity updateAppointment( @PathVariable Long id, @Valid @RequestBody AppointmentRequest request) { @@ -51,12 +54,14 @@ public class AppointmentController { } @DeleteMapping("/{id}") + @PreAuthorize("hasAnyRole('STAFF', 'ADMIN')") public ResponseEntity deleteAppointment(@PathVariable Long id) { appointmentService.deleteAppointment(id); return ResponseEntity.noContent().build(); } @DeleteMapping + @PreAuthorize("hasRole('ADMIN')") public ResponseEntity bulkDeleteAppointments(@Valid @RequestBody BulkDeleteRequest request) { appointmentService.bulkDeleteAppointments(request); return ResponseEntity.noContent().build(); diff --git a/src/main/java/com/petshop/backend/controller/CategoryController.java b/src/main/java/com/petshop/backend/controller/CategoryController.java index 5e1b80c4..fb938dd9 100644 --- a/src/main/java/com/petshop/backend/controller/CategoryController.java +++ b/src/main/java/com/petshop/backend/controller/CategoryController.java @@ -14,7 +14,6 @@ import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/v1/categories") -@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')") public class CategoryController { private final CategoryService categoryService; @@ -36,11 +35,13 @@ public class CategoryController { } @PostMapping + @PreAuthorize("hasAnyRole('STAFF', 'ADMIN')") public ResponseEntity createCategory(@Valid @RequestBody CategoryRequest request) { return ResponseEntity.status(HttpStatus.CREATED).body(categoryService.createCategory(request)); } @PutMapping("/{id}") + @PreAuthorize("hasAnyRole('STAFF', 'ADMIN')") public ResponseEntity updateCategory( @PathVariable Long id, @Valid @RequestBody CategoryRequest request) { @@ -48,12 +49,14 @@ public class CategoryController { } @DeleteMapping("/{id}") + @PreAuthorize("hasAnyRole('STAFF', 'ADMIN')") public ResponseEntity deleteCategory(@PathVariable Long id) { categoryService.deleteCategory(id); return ResponseEntity.noContent().build(); } @DeleteMapping + @PreAuthorize("hasAnyRole('STAFF', 'ADMIN')") public ResponseEntity bulkDeleteCategories(@Valid @RequestBody BulkDeleteRequest request) { categoryService.bulkDeleteCategories(request); return ResponseEntity.noContent().build(); diff --git a/src/main/java/com/petshop/backend/controller/ChatController.java b/src/main/java/com/petshop/backend/controller/ChatController.java new file mode 100644 index 00000000..181f1ab9 --- /dev/null +++ b/src/main/java/com/petshop/backend/controller/ChatController.java @@ -0,0 +1,80 @@ +package com.petshop.backend.controller; + +import com.petshop.backend.dto.chat.ConversationRequest; +import com.petshop.backend.dto.chat.ConversationResponse; +import com.petshop.backend.dto.chat.MessageRequest; +import com.petshop.backend.dto.chat.MessageResponse; +import com.petshop.backend.entity.User; +import com.petshop.backend.repository.UserRepository; +import com.petshop.backend.service.ChatService; +import jakarta.validation.Valid; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/api/v1/chat") +public class ChatController { + + private final ChatService chatService; + private final UserRepository userRepository; + + public ChatController(ChatService chatService, UserRepository userRepository) { + this.chatService = chatService; + this.userRepository = userRepository; + } + + private User getCurrentUser() { + UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); + return userRepository.findByUsername(userDetails.getUsername()) + .orElseThrow(() -> new UsernameNotFoundException("User not found")); + } + + @PostMapping("/conversations") + @PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')") + public ResponseEntity createConversation(@Valid @RequestBody ConversationRequest request) { + User user = getCurrentUser(); + ConversationResponse response = chatService.createConversation(user.getId(), request); + return ResponseEntity.status(HttpStatus.CREATED).body(response); + } + + @GetMapping("/conversations") + @PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')") + public ResponseEntity> getConversations() { + User user = getCurrentUser(); + List conversations = chatService.getConversations(user.getId(), user.getRole()); + return ResponseEntity.ok(conversations); + } + + @GetMapping("/conversations/{id}") + @PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')") + public ResponseEntity getConversation(@PathVariable Long id) { + User user = getCurrentUser(); + ConversationResponse conversation = chatService.getConversation(id, user.getId(), user.getRole()); + return ResponseEntity.ok(conversation); + } + + @PostMapping("/conversations/{id}/messages") + @PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')") + public ResponseEntity sendMessage( + @PathVariable Long id, + @Valid @RequestBody MessageRequest request) { + User user = getCurrentUser(); + MessageResponse message = chatService.sendMessage(id, user.getId(), request); + return ResponseEntity.status(HttpStatus.CREATED).body(message); + } + + @GetMapping("/conversations/{id}/messages") + @PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')") + public ResponseEntity> getMessages(@PathVariable Long id) { + User user = getCurrentUser(); + List messages = chatService.getMessages(id, user.getId(), user.getRole()); + return ResponseEntity.ok(messages); + } +} diff --git a/src/main/java/com/petshop/backend/controller/ServiceController.java b/src/main/java/com/petshop/backend/controller/ServiceController.java index a7160a62..4f6503dc 100644 --- a/src/main/java/com/petshop/backend/controller/ServiceController.java +++ b/src/main/java/com/petshop/backend/controller/ServiceController.java @@ -14,7 +14,6 @@ import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/v1/services") -@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')") public class ServiceController { private final ServiceService serviceService; @@ -36,11 +35,13 @@ public class ServiceController { } @PostMapping + @PreAuthorize("hasAnyRole('STAFF', 'ADMIN')") public ResponseEntity createService(@Valid @RequestBody ServiceRequest request) { return ResponseEntity.status(HttpStatus.CREATED).body(serviceService.createService(request)); } @PutMapping("/{id}") + @PreAuthorize("hasAnyRole('STAFF', 'ADMIN')") public ResponseEntity updateService( @PathVariable Long id, @Valid @RequestBody ServiceRequest request) { @@ -48,12 +49,14 @@ public class ServiceController { } @DeleteMapping("/{id}") + @PreAuthorize("hasAnyRole('STAFF', 'ADMIN')") public ResponseEntity deleteService(@PathVariable Long id) { serviceService.deleteService(id); return ResponseEntity.noContent().build(); } @DeleteMapping + @PreAuthorize("hasAnyRole('STAFF', 'ADMIN')") public ResponseEntity bulkDeleteServices(@Valid @RequestBody BulkDeleteRequest request) { serviceService.bulkDeleteServices(request); return ResponseEntity.noContent().build(); diff --git a/src/main/java/com/petshop/backend/dto/chat/ConversationRequest.java b/src/main/java/com/petshop/backend/dto/chat/ConversationRequest.java new file mode 100644 index 00000000..a6ef1db1 --- /dev/null +++ b/src/main/java/com/petshop/backend/dto/chat/ConversationRequest.java @@ -0,0 +1,23 @@ +package com.petshop.backend.dto.chat; + +import jakarta.validation.constraints.NotBlank; + +public class ConversationRequest { + @NotBlank(message = "Initial message is required") + private String message; + + public ConversationRequest() { + } + + public ConversationRequest(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } +} diff --git a/src/main/java/com/petshop/backend/dto/chat/ConversationResponse.java b/src/main/java/com/petshop/backend/dto/chat/ConversationResponse.java new file mode 100644 index 00000000..d9dbb1f8 --- /dev/null +++ b/src/main/java/com/petshop/backend/dto/chat/ConversationResponse.java @@ -0,0 +1,96 @@ +package com.petshop.backend.dto.chat; + +import com.petshop.backend.entity.Conversation; + +import java.time.LocalDateTime; + +public class ConversationResponse { + private Long id; + private Long customerId; + private Long staffId; + private String status; + private String lastMessage; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; + + public ConversationResponse() { + } + + public ConversationResponse(Long id, Long customerId, Long staffId, String status, String lastMessage, LocalDateTime createdAt, LocalDateTime updatedAt) { + this.id = id; + this.customerId = customerId; + this.staffId = staffId; + this.status = status; + this.lastMessage = lastMessage; + this.createdAt = createdAt; + this.updatedAt = updatedAt; + } + + public static ConversationResponse fromEntity(Conversation conversation, String lastMessage) { + ConversationResponse response = new ConversationResponse(); + response.setId(conversation.getId()); + response.setCustomerId(conversation.getCustomerId()); + response.setStaffId(conversation.getStaffId()); + response.setStatus(conversation.getStatus().name()); + response.setLastMessage(lastMessage); + response.setCreatedAt(conversation.getCreatedAt()); + response.setUpdatedAt(conversation.getUpdatedAt()); + return response; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getCustomerId() { + return customerId; + } + + public void setCustomerId(Long customerId) { + this.customerId = customerId; + } + + public Long getStaffId() { + return staffId; + } + + public void setStaffId(Long staffId) { + this.staffId = staffId; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public String getLastMessage() { + return lastMessage; + } + + public void setLastMessage(String lastMessage) { + this.lastMessage = lastMessage; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(LocalDateTime createdAt) { + this.createdAt = createdAt; + } + + public LocalDateTime getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(LocalDateTime updatedAt) { + this.updatedAt = updatedAt; + } +} diff --git a/src/main/java/com/petshop/backend/dto/chat/MessageRequest.java b/src/main/java/com/petshop/backend/dto/chat/MessageRequest.java new file mode 100644 index 00000000..cb03d310 --- /dev/null +++ b/src/main/java/com/petshop/backend/dto/chat/MessageRequest.java @@ -0,0 +1,23 @@ +package com.petshop.backend.dto.chat; + +import jakarta.validation.constraints.NotBlank; + +public class MessageRequest { + @NotBlank(message = "Message content is required") + private String content; + + public MessageRequest() { + } + + public MessageRequest(String content) { + this.content = content; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } +} diff --git a/src/main/java/com/petshop/backend/dto/chat/MessageResponse.java b/src/main/java/com/petshop/backend/dto/chat/MessageResponse.java new file mode 100644 index 00000000..25cffae5 --- /dev/null +++ b/src/main/java/com/petshop/backend/dto/chat/MessageResponse.java @@ -0,0 +1,85 @@ +package com.petshop.backend.dto.chat; + +import com.petshop.backend.entity.Message; + +import java.time.LocalDateTime; + +public class MessageResponse { + private Long id; + private Long conversationId; + private Long senderId; + private String content; + private LocalDateTime timestamp; + private Boolean isRead; + + public MessageResponse() { + } + + public MessageResponse(Long id, Long conversationId, Long senderId, String content, LocalDateTime timestamp, Boolean isRead) { + this.id = id; + this.conversationId = conversationId; + this.senderId = senderId; + this.content = content; + this.timestamp = timestamp; + this.isRead = isRead; + } + + public static MessageResponse fromEntity(Message message) { + MessageResponse response = new MessageResponse(); + response.setId(message.getId()); + response.setConversationId(message.getConversationId()); + response.setSenderId(message.getSenderId()); + response.setContent(message.getContent()); + response.setTimestamp(message.getTimestamp()); + response.setIsRead(message.getIsRead()); + return response; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getConversationId() { + return conversationId; + } + + public void setConversationId(Long conversationId) { + this.conversationId = conversationId; + } + + public Long getSenderId() { + return senderId; + } + + public void setSenderId(Long senderId) { + this.senderId = senderId; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public LocalDateTime getTimestamp() { + return timestamp; + } + + public void setTimestamp(LocalDateTime timestamp) { + this.timestamp = timestamp; + } + + public Boolean getIsRead() { + return isRead; + } + + public void setIsRead(Boolean isRead) { + this.isRead = isRead; + } +} diff --git a/src/main/java/com/petshop/backend/entity/Conversation.java b/src/main/java/com/petshop/backend/entity/Conversation.java new file mode 100644 index 00000000..1d8008e1 --- /dev/null +++ b/src/main/java/com/petshop/backend/entity/Conversation.java @@ -0,0 +1,98 @@ +package com.petshop.backend.entity; + +import jakarta.persistence.*; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; + +import java.time.LocalDateTime; + +@Entity +@Table(name = "conversation") +public class Conversation { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private Long customerId; + + @Column + private Long staffId; + + @Enumerated(EnumType.STRING) + @Column(length = 20, nullable = false) + private ConversationStatus status = ConversationStatus.OPEN; + + @CreationTimestamp + @Column(name = "created_at", nullable = false, updatable = false) + private LocalDateTime createdAt; + + @UpdateTimestamp + @Column(name = "updated_at", nullable = false) + private LocalDateTime updatedAt; + + public enum ConversationStatus { + OPEN, CLOSED + } + + public Conversation() { + } + + public Conversation(Long id, Long customerId, Long staffId, ConversationStatus status, LocalDateTime createdAt, LocalDateTime updatedAt) { + this.id = id; + this.customerId = customerId; + this.staffId = staffId; + this.status = status; + this.createdAt = createdAt; + this.updatedAt = updatedAt; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getCustomerId() { + return customerId; + } + + public void setCustomerId(Long customerId) { + this.customerId = customerId; + } + + public Long getStaffId() { + return staffId; + } + + public void setStaffId(Long staffId) { + this.staffId = staffId; + } + + public ConversationStatus getStatus() { + return status; + } + + public void setStatus(ConversationStatus status) { + this.status = status; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(LocalDateTime createdAt) { + this.createdAt = createdAt; + } + + public LocalDateTime getUpdatedAt() { + return updatedAt; + } + + public void setUpdatedAt(LocalDateTime updatedAt) { + this.updatedAt = updatedAt; + } +} diff --git a/src/main/java/com/petshop/backend/entity/Message.java b/src/main/java/com/petshop/backend/entity/Message.java new file mode 100644 index 00000000..33777bf5 --- /dev/null +++ b/src/main/java/com/petshop/backend/entity/Message.java @@ -0,0 +1,91 @@ +package com.petshop.backend.entity; + +import jakarta.persistence.*; +import org.hibernate.annotations.CreationTimestamp; + +import java.time.LocalDateTime; + +@Entity +@Table(name = "message") +public class Message { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private Long conversationId; + + @Column(nullable = false) + private Long senderId; + + @Column(nullable = false, columnDefinition = "TEXT") + private String content; + + @CreationTimestamp + @Column(nullable = false, updatable = false) + private LocalDateTime timestamp; + + @Column(nullable = false) + private Boolean isRead = false; + + public Message() { + } + + public Message(Long id, Long conversationId, Long senderId, String content, LocalDateTime timestamp, Boolean isRead) { + this.id = id; + this.conversationId = conversationId; + this.senderId = senderId; + this.content = content; + this.timestamp = timestamp; + this.isRead = isRead; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getConversationId() { + return conversationId; + } + + public void setConversationId(Long conversationId) { + this.conversationId = conversationId; + } + + public Long getSenderId() { + return senderId; + } + + public void setSenderId(Long senderId) { + this.senderId = senderId; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public LocalDateTime getTimestamp() { + return timestamp; + } + + public void setTimestamp(LocalDateTime timestamp) { + this.timestamp = timestamp; + } + + public Boolean getIsRead() { + return isRead; + } + + public void setIsRead(Boolean isRead) { + this.isRead = isRead; + } +} diff --git a/src/main/java/com/petshop/backend/repository/ConversationRepository.java b/src/main/java/com/petshop/backend/repository/ConversationRepository.java new file mode 100644 index 00000000..142853d6 --- /dev/null +++ b/src/main/java/com/petshop/backend/repository/ConversationRepository.java @@ -0,0 +1,13 @@ +package com.petshop.backend.repository; + +import com.petshop.backend.entity.Conversation; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface ConversationRepository extends JpaRepository { + List findByCustomerId(Long customerId); + List findByStaffId(Long staffId); +} diff --git a/src/main/java/com/petshop/backend/repository/MessageRepository.java b/src/main/java/com/petshop/backend/repository/MessageRepository.java new file mode 100644 index 00000000..6c87be6e --- /dev/null +++ b/src/main/java/com/petshop/backend/repository/MessageRepository.java @@ -0,0 +1,12 @@ +package com.petshop.backend.repository; + +import com.petshop.backend.entity.Message; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface MessageRepository extends JpaRepository { + List findByConversationIdOrderByTimestampAsc(Long conversationId); +} diff --git a/src/main/java/com/petshop/backend/service/ChatService.java b/src/main/java/com/petshop/backend/service/ChatService.java new file mode 100644 index 00000000..e7d7ec00 --- /dev/null +++ b/src/main/java/com/petshop/backend/service/ChatService.java @@ -0,0 +1,126 @@ +package com.petshop.backend.service; + +import com.petshop.backend.dto.chat.ConversationRequest; +import com.petshop.backend.dto.chat.ConversationResponse; +import com.petshop.backend.dto.chat.MessageRequest; +import com.petshop.backend.dto.chat.MessageResponse; +import com.petshop.backend.entity.Conversation; +import com.petshop.backend.entity.Message; +import com.petshop.backend.entity.User; +import com.petshop.backend.exception.ResourceNotFoundException; +import com.petshop.backend.repository.ConversationRepository; +import com.petshop.backend.repository.MessageRepository; +import com.petshop.backend.repository.UserRepository; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.stream.Collectors; + +@Service +public class ChatService { + + private final ConversationRepository conversationRepository; + private final MessageRepository messageRepository; + private final UserRepository userRepository; + + public ChatService(ConversationRepository conversationRepository, + MessageRepository messageRepository, + UserRepository userRepository) { + this.conversationRepository = conversationRepository; + this.messageRepository = messageRepository; + this.userRepository = userRepository; + } + + @Transactional + public ConversationResponse createConversation(Long userId, ConversationRequest request) { + User user = userRepository.findById(userId) + .orElseThrow(() -> new ResourceNotFoundException("User not found")); + + Conversation conversation = new Conversation(); + conversation.setCustomerId(userId); + conversation.setStatus(Conversation.ConversationStatus.OPEN); + conversation = conversationRepository.save(conversation); + + Message message = new Message(); + message.setConversationId(conversation.getId()); + message.setSenderId(userId); + message.setContent(request.getMessage()); + message.setIsRead(false); + messageRepository.save(message); + + return ConversationResponse.fromEntity(conversation, request.getMessage()); + } + + public List getConversations(Long userId, User.Role role) { + List conversations; + + if (role == User.Role.CUSTOMER) { + conversations = conversationRepository.findByCustomerId(userId); + } else if (role == User.Role.STAFF) { + conversations = conversationRepository.findByStaffId(userId); + if (conversations.isEmpty()) { + conversations = conversationRepository.findAll(); + } + } else { + conversations = conversationRepository.findAll(); + } + + return conversations.stream() + .map(conv -> { + List messages = messageRepository.findByConversationIdOrderByTimestampAsc(conv.getId()); + String lastMessage = messages.isEmpty() ? "" : messages.get(messages.size() - 1).getContent(); + return ConversationResponse.fromEntity(conv, lastMessage); + }) + .collect(Collectors.toList()); + } + + public ConversationResponse getConversation(Long conversationId, Long userId, User.Role role) { + Conversation conversation = conversationRepository.findById(conversationId) + .orElseThrow(() -> new ResourceNotFoundException("Conversation not found")); + + if (role == User.Role.CUSTOMER && !conversation.getCustomerId().equals(userId)) { + throw new AccessDeniedException("You can only view your own conversations"); + } + + List messages = messageRepository.findByConversationIdOrderByTimestampAsc(conversationId); + String lastMessage = messages.isEmpty() ? "" : messages.get(messages.size() - 1).getContent(); + + return ConversationResponse.fromEntity(conversation, lastMessage); + } + + @Transactional + public MessageResponse sendMessage(Long conversationId, Long userId, MessageRequest request) { + Conversation conversation = conversationRepository.findById(conversationId) + .orElseThrow(() -> new ResourceNotFoundException("Conversation not found")); + + Message message = new Message(); + message.setConversationId(conversationId); + message.setSenderId(userId); + message.setContent(request.getContent()); + message.setIsRead(false); + message = messageRepository.save(message); + + if (conversation.getStaffId() == null && !userId.equals(conversation.getCustomerId())) { + conversation.setStaffId(userId); + conversationRepository.save(conversation); + } + + return MessageResponse.fromEntity(message); + } + + public List getMessages(Long conversationId, Long userId, User.Role role) { + Conversation conversation = conversationRepository.findById(conversationId) + .orElseThrow(() -> new ResourceNotFoundException("Conversation not found")); + + if (role == User.Role.CUSTOMER && !conversation.getCustomerId().equals(userId)) { + throw new AccessDeniedException("You can only view messages from your own conversations"); + } + + List messages = messageRepository.findByConversationIdOrderByTimestampAsc(conversationId); + return messages.stream() + .map(MessageResponse::fromEntity) + .collect(Collectors.toList()); + } +} From d7d294130f42d13ea6696de99710b4407d63f510 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Sun, 8 Mar 2026 15:23:16 -0600 Subject: [PATCH 36/84] Complete Postman collection with all endpoints --- petshop-api.postman_collection.json | 3536 ++++++++++++--------------- 1 file changed, 1550 insertions(+), 1986 deletions(-) diff --git a/petshop-api.postman_collection.json b/petshop-api.postman_collection.json index 1a5eca4e..50d4fa3a 100644 --- a/petshop-api.postman_collection.json +++ b/petshop-api.postman_collection.json @@ -1,8 +1,8 @@ { "info": { - "name": "PetShop Unified API v1 (RBAC + Dropdowns + Chat)", - "_postman_id": "4d2d4e10-4338-4b85-a8ef-aa9db2ed75be", - "description": "Unified /api/v1 endpoints for Desktop, Android, and Website.\n\nVariables:\n- baseUrl (example http://localhost:8080)\n- staffToken (set by staff/admin login)\n- customerToken (set by customer login)\n\nNotes:\n- Login test scripts assume response contains {\"token\":\"...\"}.\n- Bulk delete uses DELETE with JSON body {ids:[...]}.", + "name": "PetShop API Complete Collection", + "_postman_id": "petshop-api-complete-v1", + "description": "Complete API collection with all 95+ verified endpoints", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" }, "variable": [ @@ -10,74 +10,266 @@ "key": "baseUrl", "value": "http://localhost:8080" }, - { - "key": "staffToken", - "value": "" - }, { "key": "customerToken", "value": "" }, { - "key": "petId", - "value": "1" + "key": "staffToken", + "value": "" }, { - "key": "adoptionId", - "value": "1" - }, - { - "key": "appointmentId", - "value": "1" - }, - { - "key": "serviceId", - "value": "1" - }, - { - "key": "prodId", - "value": "1" - }, - { - "key": "categoryId", - "value": "1" - }, - { - "key": "supId", - "value": "1" - }, - { - "key": "inventoryId", - "value": "1" - }, - { - "key": "saleId", - "value": "1" - }, - { - "key": "purchaseOrderId", - "value": "1" - }, - { - "key": "userId", - "value": "1" - }, - { - "key": "employeeId", - "value": "1" - }, - { - "key": "roomId", - "value": "1" - }, - { - "key": "storeId", - "value": "1" + "key": "adminToken", + "value": "" } ], "item": [ { - "name": "Auth", + "name": "1 Public Endpoints", + "item": [ + { + "name": "Health Check", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/health", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ] + } + }, + { + "name": "Get All Pets", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/pets", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ] + } + }, + { + "name": "Get Pet by ID", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/pets/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ] + } + }, + { + "name": "Get All Products", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/products", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ] + } + }, + { + "name": "Get Product by ID", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/products/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ] + } + }, + { + "name": "Get All Sales", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/sales", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ] + } + }, + { + "name": "Get Sale by ID", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/sales/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ] + } + }, + { + "name": "Get All Services", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/services", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ] + } + }, + { + "name": "Get Service by ID", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/services/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ] + } + }, + { + "name": "Get All Categories", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/categories", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ] + } + }, + { + "name": "Get Category by ID", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/categories/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ] + } + }, + { + "name": "Check Appointment Availability", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/appointments/availability?storeId=1&serviceId=1&date=2026-03-15", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ] + } + }, + { + "name": "Dropdowns", + "item": [ + { + "name": "Get Pets Dropdown", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/dropdowns/pets", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ] + } + }, + { + "name": "Get Customers Dropdown", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/dropdowns/customers", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ] + } + }, + { + "name": "Get Services Dropdown", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/dropdowns/services", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ] + } + }, + { + "name": "Get Products Dropdown", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/dropdowns/products", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ] + } + }, + { + "name": "Get Categories Dropdown", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/dropdowns/categories", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ] + } + }, + { + "name": "Get Stores Dropdown", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/dropdowns/stores", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ] + } + } + ] + } + ] + }, + { + "name": "2 Authentication", "item": [ { "name": "Register Customer", @@ -92,17 +284,12 @@ ], "body": { "mode": "raw", - "raw": "{\n \"firstName\": \"Chris\",\n \"lastName\": \"Ng\",\n \"email\": \"chris.ng@example.com\",\n \"phone\": \"555-0120\",\n \"password\": \"ChangeMe123\"\n}", - "options": { - "raw": { - "language": "json" - } - } + "raw": "{\n \"username\": \"newcustomer\",\n \"password\": \"password123\",\n \"email\": \"new@example.com\",\n \"fullName\": \"New Customer\"\n}" } } }, { - "name": "Login (Staff/Admin) -> sets staffToken", + "name": "Login as Customer", "request": { "method": "POST", "url": "{{baseUrl}}/api/v1/auth/login", @@ -114,31 +301,29 @@ ], "body": { "mode": "raw", - "raw": "{\n \"username\": \"admin\",\n \"password\": \"admin123\"\n}", - "options": { - "raw": { - "language": "json" - } - } + "raw": "{\n \"username\": \"customer\",\n \"password\": \"customer123\"\n}" } }, "event": [ { "listen": "test", "script": { - "type": "text/javascript", "exec": [ - "try {", - " const json = pm.response.json();", - " if (json && json.token) pm.collectionVariables.set('staffToken', json.token);", - "} catch (e) {}" + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});", + "", + "var jsonData = pm.response.json();", + "if (jsonData.token) {", + " pm.collectionVariables.set('customerToken', jsonData.token);", + "}" ] } } ] }, { - "name": "Login (Staff) -> sets staffToken", + "name": "Login as Staff", "request": { "method": "POST", "url": "{{baseUrl}}/api/v1/auth/login", @@ -150,31 +335,29 @@ ], "body": { "mode": "raw", - "raw": "{\n \"username\": \"staff\",\n \"password\": \"staff123\"\n}", - "options": { - "raw": { - "language": "json" - } - } + "raw": "{\n \"username\": \"staff\",\n \"password\": \"staff123\"\n}" } }, "event": [ { "listen": "test", "script": { - "type": "text/javascript", "exec": [ - "try {", - " const json = pm.response.json();", - " if (json && json.token) pm.collectionVariables.set('staffToken', json.token);", - "} catch (e) {}" + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});", + "", + "var jsonData = pm.response.json();", + "if (jsonData.token) {", + " pm.collectionVariables.set('staffToken', jsonData.token);", + "}" ] } } ] }, { - "name": "Login (Customer) -> sets customerToken", + "name": "Login as Admin", "request": { "method": "POST", "url": "{{baseUrl}}/api/v1/auth/login", @@ -184,599 +367,235 @@ "value": "application/json" } ], - "description": "If login uses username instead of email, adjust request body.", "body": { "mode": "raw", - "raw": "{\n \"email\": \"chris.ng@example.com\",\n \"password\": \"ChangeMe123\"\n}", - "options": { - "raw": { - "language": "json" - } - } + "raw": "{\n \"username\": \"admin\",\n \"password\": \"admin123\"\n}" } }, "event": [ { "listen": "test", "script": { - "type": "text/javascript", "exec": [ - "try {", - " const json = pm.response.json();", - " if (json && json.token) pm.collectionVariables.set('customerToken', json.token);", - "} catch (e) {}" + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});", + "", + "var jsonData = pm.response.json();", + "if (jsonData.token) {", + " pm.collectionVariables.set('adminToken', jsonData.token);", + "}" ] } } ] }, { - "name": "Logout (Staff/Admin)", - "request": { - "method": "POST", - "url": "{{baseUrl}}/api/v1/auth/logout", - "header": [], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - } - } - }, - { - "name": "Logout (Customer)", - "request": { - "method": "POST", - "url": "{{baseUrl}}/api/v1/auth/logout", - "header": [], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{customerToken}}", - "type": "string" - } - ] - } - } - }, - { - "name": "Auth Me (Staff/Admin)", + "name": "Get My Profile", "request": { "method": "GET", "url": "{{baseUrl}}/api/v1/auth/me", - "header": [], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - } + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{customerToken}}", + "type": "text" + } + ] } }, { - "name": "Auth Me (Customer)", + "name": "Update My Profile", "request": { - "method": "GET", + "method": "PUT", "url": "{{baseUrl}}/api/v1/auth/me", - "header": [], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{customerToken}}", - "type": "string" - } - ] - } - } - }, - { - "name": "Health Check", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/health", - "header": [] - } - } - ] - }, - { - "name": "My Account (/me)", - "item": [ - { - "name": "Get My Profile (Staff/Admin)", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/me", - "header": [], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - } - } - }, - { - "name": "Update My Profile (Staff/Admin)", - "request": { - "method": "PUT", - "url": "{{baseUrl}}/api/v1/me", "header": [ { "key": "Content-Type", "value": "application/json" - } - ], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - }, - "body": { - "mode": "raw", - "raw": "{\n \"phone\": \"555-0999\"\n}", - "options": { - "raw": { - "language": "json" - } - } - } - } - }, - { - "name": "Get My Profile (Customer)", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/me", - "header": [], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{customerToken}}", - "type": "string" - } - ] - } - } - }, - { - "name": "Update My Profile (Customer)", - "request": { - "method": "PUT", - "url": "{{baseUrl}}/api/v1/me", - "header": [ + }, { - "key": "Content-Type", - "value": "application/json" + "key": "Authorization", + "value": "Bearer {{customerToken}}", + "type": "text" } ], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{customerToken}}", - "type": "string" - } - ] - }, "body": { "mode": "raw", - "raw": "{\n \"phone\": \"555-0999\"\n}", - "options": { - "raw": { - "language": "json" - } - } - } - } - }, - { - "name": "Upload My Avatar (Staff/Admin) [multipart]", - "request": { - "method": "PUT", - "url": "{{baseUrl}}/api/v1/me/avatar", - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - }, - "header": [], - "body": { - "mode": "formdata", - "formdata": [ - { - "key": "file", - "type": "file", - "src": "" - } - ] - }, - "description": "Uploads avatar image for the logged-in staff/admin. Public access via GET /api/v1/staff/{employeeId}/avatar." - } - }, - { - "name": "Upload My Avatar (Customer) [multipart]", - "request": { - "method": "PUT", - "url": "{{baseUrl}}/api/v1/me/avatar", - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{customerToken}}", - "type": "string" - } - ] - }, - "header": [], - "body": { - "mode": "formdata", - "formdata": [ - { - "key": "file", - "type": "file", - "src": "" - } - ] - }, - "description": "Optional if customer avatars are supported." - } - } - ] - }, - { - "name": "Dropdowns (lightweight id+name)", - "item": [ - { - "name": "Dropdown - Pets (staff)", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/dropdowns/pets", - "header": [], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - } - } - }, - { - "name": "Dropdown - Customers (staff)", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/dropdowns/customers", - "header": [], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - } - } - }, - { - "name": "Dropdown - Services (staff)", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/dropdowns/services", - "header": [], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - } - } - }, - { - "name": "Dropdown - Products (staff)", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/dropdowns/products", - "header": [], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - } - } - }, - { - "name": "Dropdown - Suppliers (admin)", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/dropdowns/suppliers", - "header": [], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - } - } - }, - { - "name": "Dropdown - Categories (staff)", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/dropdowns/categories", - "header": [], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - } - } - }, - { - "name": "Dropdown - Stores (staff)", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/dropdowns/stores", - "header": [], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - } - } - }, - { - "name": "Dropdown - Services (customer)", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/dropdowns/services", - "header": [], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{customerToken}}", - "type": "string" - } - ] - } - } - }, - { - "name": "Dropdown - Stores (customer)", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/dropdowns/stores", - "header": [], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{customerToken}}", - "type": "string" - } - ] + "raw": "{\n \"fullName\": \"Updated Name\",\n \"email\": \"updated@example.com\"\n}" } } } ] }, { - "name": "Resources (Staff)", + "name": "3 Customer Features", "item": [ { - "name": "Pets", + "name": "Chat", "item": [ { - "name": "List/Search", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/pets?q=&page=0&size=50&sort=id,desc", - "header": [], - "description": "Returns UI-ready rows (joined fields included).", - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - } - } - }, - { - "name": "Create", + "name": "Create Conversation", "request": { "method": "POST", - "url": "{{baseUrl}}/api/v1/pets", + "url": "{{baseUrl}}/api/v1/chat/conversations", "header": [ { "key": "Content-Type", "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{customerToken}}", + "type": "text" } ], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - }, "body": { "mode": "raw", - "raw": "{\n \"petName\": \"Buddy\",\n \"petSpecies\": \"Dog\",\n \"petBreed\": \"Labrador\",\n \"petAge\": 3,\n \"petStatus\": \"Available\",\n \"petPrice\": 499.99\n}", - "options": { - "raw": { - "language": "json" - } - } + "raw": "{\n \"message\": \"I need help\"\n}" } } }, { - "name": "Get One", + "name": "List Conversations", "request": { "method": "GET", - "url": "{{baseUrl}}/api/v1/pets/{{petId}}", - "header": [], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - } - } - }, - { - "name": "Update (PUT)", - "request": { - "method": "PUT", - "url": "{{baseUrl}}/api/v1/pets/{{petId}}", + "url": "{{baseUrl}}/api/v1/chat/conversations", "header": [ { "key": "Content-Type", "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{customerToken}}", + "type": "text" } - ], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - }, - "body": { - "mode": "raw", - "raw": "{\n \"petName\": \"Buddy\",\n \"petSpecies\": \"Dog\",\n \"petBreed\": \"Labrador\",\n \"petAge\": 4,\n \"petStatus\": \"Available\",\n \"petPrice\": 450.0\n}", - "options": { - "raw": { - "language": "json" - } - } - } + ] } }, { - "name": "Delete One", + "name": "Get Conversation", "request": { - "method": "DELETE", - "url": "{{baseUrl}}/api/v1/pets/{{petId}}", - "header": [], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - } - } - }, - { - "name": "Bulk Delete (DELETE body)", - "request": { - "method": "DELETE", - "url": "{{baseUrl}}/api/v1/pets", + "method": "GET", + "url": "{{baseUrl}}/api/v1/chat/conversations/1", "header": [ { "key": "Content-Type", "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{customerToken}}", + "type": "text" + } + ] + } + }, + { + "name": "Send Message", + "request": { + "method": "POST", + "url": "{{baseUrl}}/api/v1/chat/conversations/1/messages", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{customerToken}}", + "type": "text" } ], - "description": "Bulk delete with JSON body.", - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - }, "body": { "mode": "raw", - "raw": "{\n \"ids\": [\n 1,\n 2,\n 3\n ]\n}", - "options": { - "raw": { - "language": "json" - } + "raw": "{\n \"content\": \"Hello\"\n}" + } + } + }, + { + "name": "Get Messages", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/chat/conversations/1/messages", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{customerToken}}", + "type": "text" } + ] + } + } + ] + }, + { + "name": "Appointments", + "item": [ + { + "name": "List Appointments", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/appointments", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{customerToken}}", + "type": "text" + } + ] + } + }, + { + "name": "Get Appointment", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/appointments/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{customerToken}}", + "type": "text" + } + ] + } + }, + { + "name": "Create Appointment", + "request": { + "method": "POST", + "url": "{{baseUrl}}/api/v1/appointments", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{customerToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"customerId\": 1,\n \"storeId\": 1,\n \"serviceId\": 1,\n \"appointmentDate\": \"2026-03-15\",\n \"appointmentTime\": \"10:00:00\",\n \"notes\": \"Test\"\n}" } } } @@ -786,26 +605,43 @@ "name": "Adoptions", "item": [ { - "name": "List/Search", + "name": "List Adoptions", "request": { "method": "GET", - "url": "{{baseUrl}}/api/v1/adoptions?q=&page=0&size=50&sort=id,desc", - "header": [], - "description": "Returns UI-ready rows (joined fields included).", - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - } + "url": "{{baseUrl}}/api/v1/adoptions", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{customerToken}}", + "type": "text" + } + ] } }, { - "name": "Create", + "name": "Get Adoption", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/adoptions/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{customerToken}}", + "type": "text" + } + ] + } + }, + { + "name": "Create Adoption", "request": { "method": "POST", "url": "{{baseUrl}}/api/v1/adoptions", @@ -813,459 +649,173 @@ { "key": "Content-Type", "value": "application/json" - } - ], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - }, - "body": { - "mode": "raw", - "raw": "{\n \"petId\": \"{{petId}}\",\n \"customerId\": 1,\n \"adoptionDate\": \"2026-03-04\",\n \"adoptionStatus\": \"Pending\"\n}", - "options": { - "raw": { - "language": "json" - } - } - } - } - }, - { - "name": "Get One", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/adoptions/{{adoptionId}}", - "header": [], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - } - } - }, - { - "name": "Update (PUT)", - "request": { - "method": "PUT", - "url": "{{baseUrl}}/api/v1/adoptions/{{adoptionId}}", - "header": [ + }, { - "key": "Content-Type", - "value": "application/json" + "key": "Authorization", + "value": "Bearer {{customerToken}}", + "type": "text" } ], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - }, "body": { "mode": "raw", - "raw": "{\n \"petId\": \"{{petId}}\",\n \"customerId\": 1,\n \"adoptionDate\": \"2026-03-04\",\n \"adoptionStatus\": \"Approved\"\n}", - "options": { - "raw": { - "language": "json" - } - } - } - } - }, - { - "name": "Delete One", - "request": { - "method": "DELETE", - "url": "{{baseUrl}}/api/v1/adoptions/{{adoptionId}}", - "header": [], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - } - } - }, - { - "name": "Bulk Delete (DELETE body)", - "request": { - "method": "DELETE", - "url": "{{baseUrl}}/api/v1/adoptions", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "description": "Bulk delete with JSON body.", - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - }, - "body": { - "mode": "raw", - "raw": "{\n \"ids\": [\n 1,\n 2,\n 3\n ]\n}", - "options": { - "raw": { - "language": "json" - } - } + "raw": "{\n \"petId\": 1,\n \"customerId\": 1,\n \"adoptionDate\": \"2026-03-10\"\n}" } } } ] }, { - "name": "Appointments", + "name": "Refunds", "item": [ { - "name": "List/Search", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/appointments?q=&page=0&size=50&sort=id,desc", - "header": [], - "description": "Returns UI-ready rows (joined fields included).", - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - } - } - }, - { - "name": "Create", + "name": "Create Refund", "request": { "method": "POST", - "url": "{{baseUrl}}/api/v1/appointments", + "url": "{{baseUrl}}/api/v1/refunds", "header": [ { "key": "Content-Type", "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{customerToken}}", + "type": "text" } ], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - }, "body": { "mode": "raw", - "raw": "{\n \"serviceId\": \"{{serviceId}}\",\n \"customerId\": 1,\n \"appointmentDate\": \"2026-03-04\",\n \"appointmentTime\": \"14:30:00\",\n \"appointmentStatus\": \"Booked\",\n \"petIds\": [\n \"{{petId}}\"\n ]\n}", - "options": { - "raw": { - "language": "json" - } - } + "raw": "{\n \"saleId\": 1,\n \"reason\": \"Defective\",\n \"amount\": 50.0\n}" } } }, { - "name": "Get One", + "name": "List Refunds", "request": { "method": "GET", - "url": "{{baseUrl}}/api/v1/appointments/{{appointmentId}}", - "header": [], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - } - } - }, - { - "name": "Update (PUT)", - "request": { - "method": "PUT", - "url": "{{baseUrl}}/api/v1/appointments/{{appointmentId}}", + "url": "{{baseUrl}}/api/v1/refunds", "header": [ { "key": "Content-Type", "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{customerToken}}", + "type": "text" } - ], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - }, - "body": { - "mode": "raw", - "raw": "{\n \"serviceId\": \"{{serviceId}}\",\n \"customerId\": 1,\n \"appointmentDate\": \"2026-03-05\",\n \"appointmentTime\": \"15:00:00\",\n \"appointmentStatus\": \"Booked\",\n \"petIds\": [\n \"{{petId}}\"\n ]\n}", - "options": { - "raw": { - "language": "json" - } - } - } + ] } }, { - "name": "Delete One", + "name": "Get Refund", "request": { - "method": "DELETE", - "url": "{{baseUrl}}/api/v1/appointments/{{appointmentId}}", - "header": [], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - } - } - }, - { - "name": "Bulk Delete (DELETE body)", - "request": { - "method": "DELETE", - "url": "{{baseUrl}}/api/v1/appointments", + "method": "GET", + "url": "{{baseUrl}}/api/v1/refunds/1", "header": [ { "key": "Content-Type", "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{customerToken}}", + "type": "text" } - ], - "description": "Bulk delete with JSON body.", - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - }, - "body": { - "mode": "raw", - "raw": "{\n \"ids\": [\n 1,\n 2,\n 3\n ]\n}", - "options": { - "raw": { - "language": "json" - } - } - } + ] } } ] - }, + } + ] + }, + { + "name": "4 Staff Operations", + "item": [ { - "name": "Appointments - Availability", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/appointments/availability?storeId={{storeId}}&serviceId={{serviceId}}&date=2026-03-04", - "header": [], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - } - } - }, - { - "name": "Services", + "name": "Pets", "item": [ { - "name": "List/Search", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/services?q=&page=0&size=50&sort=id,desc", - "header": [], - "description": "Returns UI-ready rows (joined fields included).", - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - } - } - }, - { - "name": "Create", + "name": "Create Pet", "request": { "method": "POST", - "url": "{{baseUrl}}/api/v1/services", + "url": "{{baseUrl}}/api/v1/pets", "header": [ { "key": "Content-Type", "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" } ], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - }, "body": { "mode": "raw", - "raw": "{\n \"serviceName\": \"Grooming\",\n \"serviceDesc\": \"Full grooming package\",\n \"serviceDuration\": 60,\n \"servicePrice\": 49.99\n}", - "options": { - "raw": { - "language": "json" - } - } + "raw": "{\n \"petName\": \"Buddy\",\n \"petSpecies\": \"Dog\",\n \"petBreed\": \"Labrador\",\n \"petAge\": 2,\n \"petStatus\": \"Available\",\n \"petPrice\": 500.0\n}" } } }, { - "name": "Get One", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/services/{{serviceId}}", - "header": [], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - } - } - }, - { - "name": "Update (PUT)", + "name": "Update Pet", "request": { "method": "PUT", - "url": "{{baseUrl}}/api/v1/services/{{serviceId}}", + "url": "{{baseUrl}}/api/v1/pets/1", "header": [ { "key": "Content-Type", "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" } ], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - }, "body": { "mode": "raw", - "raw": "{\n \"serviceName\": \"Grooming\",\n \"serviceDesc\": \"Updated description\",\n \"serviceDuration\": 60,\n \"servicePrice\": 54.99\n}", - "options": { - "raw": { - "language": "json" - } - } + "raw": "{\n \"petName\": \"Buddy\",\n \"petSpecies\": \"Dog\",\n \"petBreed\": \"Labrador\",\n \"petAge\": 3,\n \"petStatus\": \"Available\",\n \"petPrice\": 500.0\n}" } } }, { - "name": "Delete One", + "name": "Delete Pet", "request": { "method": "DELETE", - "url": "{{baseUrl}}/api/v1/services/{{serviceId}}", - "header": [], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - } - } - }, - { - "name": "Bulk Delete (DELETE body)", - "request": { - "method": "DELETE", - "url": "{{baseUrl}}/api/v1/services", + "url": "{{baseUrl}}/api/v1/pets/1", "header": [ { "key": "Content-Type", "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" + } + ] + } + }, + { + "name": "Bulk Delete Pets", + "request": { + "method": "DELETE", + "url": "{{baseUrl}}/api/v1/pets", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" } ], - "description": "Bulk delete with JSON body.", - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - }, "body": { "mode": "raw", - "raw": "{\n \"ids\": [\n 1,\n 2,\n 3\n ]\n}", - "options": { - "raw": { - "language": "json" - } - } + "raw": "{\n \"ids\": [\n 1,\n 2\n ]\n}" } } } @@ -1275,26 +825,7 @@ "name": "Products", "item": [ { - "name": "List/Search", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/products?q=&page=0&size=50&sort=id,desc", - "header": [], - "description": "Returns UI-ready rows (joined fields included).", - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - } - } - }, - { - "name": "Create", + "name": "Create Product", "request": { "method": "POST", "url": "{{baseUrl}}/api/v1/products", @@ -1302,127 +833,154 @@ { "key": "Content-Type", "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" } ], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - }, "body": { "mode": "raw", - "raw": "{\n \"prodName\": \"Dog Food\",\n \"prodPrice\": 19.99,\n \"prodDesc\": \"Large bag\",\n \"categoryId\": \"{{categoryId}}\"\n}", - "options": { - "raw": { - "language": "json" - } - } + "raw": "{\n \"prodName\": \"Dog Food\",\n \"categoryId\": 1,\n \"prodDesc\": \"Premium\",\n \"prodPrice\": 50.0\n}" } } }, { - "name": "Get One", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/products/{{prodId}}", - "header": [], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - } - } - }, - { - "name": "Update (PUT)", + "name": "Update Product", "request": { "method": "PUT", - "url": "{{baseUrl}}/api/v1/products/{{prodId}}", + "url": "{{baseUrl}}/api/v1/products/1", "header": [ { "key": "Content-Type", "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" } ], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - }, "body": { "mode": "raw", - "raw": "{\n \"prodName\": \"Dog Food\",\n \"prodPrice\": 21.99,\n \"prodDesc\": \"Large bag\",\n \"categoryId\": \"{{categoryId}}\"\n}", - "options": { - "raw": { - "language": "json" - } - } + "raw": "{\n \"prodName\": \"Dog Food\",\n \"categoryId\": 1,\n \"prodDesc\": \"Premium\",\n \"prodPrice\": 55.0\n}" } } - }, + } + ] + }, + { + "name": "Sales", + "item": [ { - "name": "Delete One", + "name": "Create Sale", "request": { - "method": "DELETE", - "url": "{{baseUrl}}/api/v1/products/{{prodId}}", - "header": [], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - } - } - }, - { - "name": "Bulk Delete (DELETE body)", - "request": { - "method": "DELETE", - "url": "{{baseUrl}}/api/v1/products", + "method": "POST", + "url": "{{baseUrl}}/api/v1/sales", "header": [ { "key": "Content-Type", "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" } ], - "description": "Bulk delete with JSON body.", - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - }, "body": { "mode": "raw", - "raw": "{\n \"ids\": [\n 1,\n 2,\n 3\n ]\n}", - "options": { - "raw": { - "language": "json" - } + "raw": "{\n \"employeeId\": 1,\n \"storeId\": 1,\n \"totalAmount\": 100.0,\n \"paymentMethod\": \"Card\",\n \"isRefund\": false\n}" + } + } + } + ] + }, + { + "name": "Services", + "item": [ + { + "name": "Create Service", + "request": { + "method": "POST", + "url": "{{baseUrl}}/api/v1/services", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" } + ], + "body": { + "mode": "raw", + "raw": "{\n \"serviceName\": \"Grooming\",\n \"serviceDesc\": \"Full grooming\",\n \"servicePrice\": 75.0\n}" + } + } + }, + { + "name": "Update Service", + "request": { + "method": "PUT", + "url": "{{baseUrl}}/api/v1/services/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"serviceName\": \"Grooming\",\n \"serviceDesc\": \"Full grooming\",\n \"servicePrice\": 80.0\n}" + } + } + }, + { + "name": "Delete Service", + "request": { + "method": "DELETE", + "url": "{{baseUrl}}/api/v1/services/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" + } + ] + } + }, + { + "name": "Bulk Delete Services", + "request": { + "method": "DELETE", + "url": "{{baseUrl}}/api/v1/services", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"ids\": [\n 1\n ]\n}" } } } @@ -1432,26 +990,7 @@ "name": "Categories", "item": [ { - "name": "List/Search", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/categories?q=&page=0&size=50&sort=id,desc", - "header": [], - "description": "Returns UI-ready rows (joined fields included).", - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - } - } - }, - { - "name": "Create", + "name": "Create Category", "request": { "method": "POST", "url": "{{baseUrl}}/api/v1/categories", @@ -1459,99 +998,61 @@ { "key": "Content-Type", "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" } ], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - }, "body": { "mode": "raw", - "raw": "{\n \"categoryName\": \"Food\",\n \"categoryType\": \"PRODUCT\"\n}", - "options": { - "raw": { - "language": "json" - } - } + "raw": "{\n \"categoryName\": \"Dog Supplies\"\n}" } } }, { - "name": "Get One", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/categories/{{categoryId}}", - "header": [], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - } - } - }, - { - "name": "Update (PUT)", + "name": "Update Category", "request": { "method": "PUT", - "url": "{{baseUrl}}/api/v1/categories/{{categoryId}}", + "url": "{{baseUrl}}/api/v1/categories/1", "header": [ { "key": "Content-Type", "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" } ], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - }, "body": { "mode": "raw", - "raw": "{\n \"categoryName\": \"Food & Treats\",\n \"categoryType\": \"PRODUCT\"\n}", - "options": { - "raw": { - "language": "json" - } - } + "raw": "{\n \"categoryName\": \"Dog Supplies\"\n}" } } }, { - "name": "Delete One", + "name": "Delete Category", "request": { "method": "DELETE", - "url": "{{baseUrl}}/api/v1/categories/{{categoryId}}", - "header": [], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - } + "url": "{{baseUrl}}/api/v1/categories/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" + } + ] } }, { - "name": "Bulk Delete (DELETE body)", + "name": "Bulk Delete Categories", "request": { "method": "DELETE", "url": "{{baseUrl}}/api/v1/categories", @@ -1559,27 +1060,258 @@ { "key": "Content-Type", "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" } ], - "description": "Bulk delete with JSON body.", - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - }, "body": { "mode": "raw", - "raw": "{\n \"ids\": [\n 1,\n 2,\n 3\n ]\n}", - "options": { - "raw": { - "language": "json" - } + "raw": "{\n \"ids\": [\n 1\n ]\n}" + } + } + } + ] + }, + { + "name": "Customers", + "item": [ + { + "name": "List Customers", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/customers", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" } + ] + } + }, + { + "name": "Get Customer", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/customers/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" + } + ] + } + }, + { + "name": "Create Customer", + "request": { + "method": "POST", + "url": "{{baseUrl}}/api/v1/customers", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"firstName\": \"John\",\n \"lastName\": \"Doe\",\n \"email\": \"john@example.com\",\n \"phone\": \"555-0100\",\n \"address\": \"123 Main St\"\n}" + } + } + }, + { + "name": "Update Customer", + "request": { + "method": "PUT", + "url": "{{baseUrl}}/api/v1/customers/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"firstName\": \"John\",\n \"lastName\": \"Doe\",\n \"email\": \"john@example.com\",\n \"phone\": \"555-0100\",\n \"address\": \"123 Main St\"\n}" + } + } + }, + { + "name": "Delete Customer", + "request": { + "method": "DELETE", + "url": "{{baseUrl}}/api/v1/customers/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" + } + ] + } + }, + { + "name": "Bulk Delete Customers", + "request": { + "method": "POST", + "url": "{{baseUrl}}/api/v1/customers/bulk-delete", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"ids\": [\n 1\n ]\n}" + } + } + } + ] + }, + { + "name": "Appointments Management", + "item": [ + { + "name": "Update Appointment", + "request": { + "method": "PUT", + "url": "{{baseUrl}}/api/v1/appointments/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"customerId\": 1,\n \"storeId\": 1,\n \"serviceId\": 1,\n \"appointmentDate\": \"2026-03-15\",\n \"appointmentTime\": \"14:00:00\",\n \"notes\": \"Updated\"\n}" + } + } + }, + { + "name": "Delete Appointment", + "request": { + "method": "DELETE", + "url": "{{baseUrl}}/api/v1/appointments/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" + } + ] + } + } + ] + }, + { + "name": "Adoptions Management", + "item": [ + { + "name": "Update Adoption", + "request": { + "method": "PUT", + "url": "{{baseUrl}}/api/v1/adoptions/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"petId\": 1,\n \"customerId\": 1,\n \"adoptionDate\": \"2026-03-11\"\n}" + } + } + }, + { + "name": "Delete Adoption", + "request": { + "method": "DELETE", + "url": "{{baseUrl}}/api/v1/adoptions/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" + } + ] + } + } + ] + }, + { + "name": "Refunds Management", + "item": [ + { + "name": "Update Refund", + "request": { + "method": "PUT", + "url": "{{baseUrl}}/api/v1/refunds/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"status\": \"APPROVED\"\n}" } } } @@ -1588,32 +1320,317 @@ ] }, { - "name": "Admin-only Resources", + "name": "5 Admin Operations", "item": [ { - "name": "Inventory", + "name": "Analytics Dashboard", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/analytics/dashboard", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ] + } + }, + { + "name": "Users", "item": [ { - "name": "List/Search", + "name": "List Users", "request": { "method": "GET", - "url": "{{baseUrl}}/api/v1/inventory?q=&page=0&size=50&sort=id,desc", - "header": [], - "description": "Returns UI-ready rows (joined fields included).", - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] + "url": "{{baseUrl}}/api/v1/users", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ] + } + }, + { + "name": "Get User", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/users/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ] + } + }, + { + "name": "Create User", + "request": { + "method": "POST", + "url": "{{baseUrl}}/api/v1/users", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"username\": \"newuser\",\n \"password\": \"password123\",\n \"role\": \"STAFF\"\n}" } } }, { - "name": "Create", + "name": "Update User", + "request": { + "method": "PUT", + "url": "{{baseUrl}}/api/v1/users/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"username\": \"user1\",\n \"role\": \"STAFF\"\n}" + } + } + }, + { + "name": "Delete User", + "request": { + "method": "DELETE", + "url": "{{baseUrl}}/api/v1/users/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ] + } + }, + { + "name": "Bulk Delete Users", + "request": { + "method": "DELETE", + "url": "{{baseUrl}}/api/v1/users", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"ids\": [\n 1\n ]\n}" + } + } + } + ] + }, + { + "name": "Stores", + "item": [ + { + "name": "List Stores", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/stores", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ] + } + }, + { + "name": "Get Store", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/stores/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ] + } + }, + { + "name": "Create Store", + "request": { + "method": "POST", + "url": "{{baseUrl}}/api/v1/stores", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"storeName\": \"New Branch\",\n \"storeAddress\": \"456 Oak St\",\n \"storePhone\": \"555-0200\"\n}" + } + } + }, + { + "name": "Update Store", + "request": { + "method": "PUT", + "url": "{{baseUrl}}/api/v1/stores/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"storeName\": \"Updated Branch\",\n \"storeAddress\": \"456 Oak St\",\n \"storePhone\": \"555-0200\"\n}" + } + } + }, + { + "name": "Delete Store", + "request": { + "method": "DELETE", + "url": "{{baseUrl}}/api/v1/stores/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ] + } + }, + { + "name": "Bulk Delete Stores", + "request": { + "method": "DELETE", + "url": "{{baseUrl}}/api/v1/stores", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"ids\": [\n 1\n ]\n}" + } + } + } + ] + }, + { + "name": "Inventory", + "item": [ + { + "name": "List Inventory", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/inventory", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ] + } + }, + { + "name": "Get Inventory Item", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/inventory/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ] + } + }, + { + "name": "Create Inventory", "request": { "method": "POST", "url": "{{baseUrl}}/api/v1/inventory", @@ -1621,99 +1638,61 @@ { "key": "Content-Type", "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" } ], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - }, "body": { "mode": "raw", - "raw": "{\n \"prodId\": \"{{prodId}}\",\n \"quantity\": 25\n}", - "options": { - "raw": { - "language": "json" - } - } + "raw": "{\n \"productId\": 1,\n \"storeId\": 1,\n \"quantity\": 100\n}" } } }, { - "name": "Get One", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/inventory/{{inventoryId}}", - "header": [], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - } - } - }, - { - "name": "Update (PUT)", + "name": "Update Inventory", "request": { "method": "PUT", - "url": "{{baseUrl}}/api/v1/inventory/{{inventoryId}}", + "url": "{{baseUrl}}/api/v1/inventory/1", "header": [ { "key": "Content-Type", "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" } ], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - }, "body": { "mode": "raw", - "raw": "{\n \"prodId\": \"{{prodId}}\",\n \"quantity\": 30\n}", - "options": { - "raw": { - "language": "json" - } - } + "raw": "{\n \"quantity\": 150\n}" } } }, { - "name": "Delete One", + "name": "Delete Inventory", "request": { "method": "DELETE", - "url": "{{baseUrl}}/api/v1/inventory/{{inventoryId}}", - "header": [], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - } + "url": "{{baseUrl}}/api/v1/inventory/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ] } }, { - "name": "Bulk Delete (DELETE body)", + "name": "Bulk Delete Inventory", "request": { "method": "DELETE", "url": "{{baseUrl}}/api/v1/inventory", @@ -1721,27 +1700,16 @@ { "key": "Content-Type", "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" } ], - "description": "Bulk delete with JSON body.", - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - }, "body": { "mode": "raw", - "raw": "{\n \"ids\": [\n 1,\n 2,\n 3\n ]\n}", - "options": { - "raw": { - "language": "json" - } - } + "raw": "{\n \"ids\": [\n 1\n ]\n}" } } } @@ -1751,26 +1719,43 @@ "name": "Suppliers", "item": [ { - "name": "List/Search", + "name": "List Suppliers", "request": { "method": "GET", - "url": "{{baseUrl}}/api/v1/suppliers?q=&page=0&size=50&sort=id,desc", - "header": [], - "description": "Returns UI-ready rows (joined fields included).", - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - } + "url": "{{baseUrl}}/api/v1/suppliers", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ] } }, { - "name": "Create", + "name": "Get Supplier", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/suppliers/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ] + } + }, + { + "name": "Create Supplier", "request": { "method": "POST", "url": "{{baseUrl}}/api/v1/suppliers", @@ -1778,99 +1763,61 @@ { "key": "Content-Type", "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" } ], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - }, "body": { "mode": "raw", - "raw": "{\n \"supName\": \"Acme Supplies\",\n \"supPhone\": \"555-0100\",\n \"supEmail\": \"sales@acme.example\",\n \"supAddress\": \"123 Main St\"\n}", - "options": { - "raw": { - "language": "json" - } - } + "raw": "{\n \"supCompany\": \"ACME Corp\",\n \"supContact\": \"John Smith\",\n \"supPhone\": \"555-0300\",\n \"supEmail\": \"john@acme.com\"\n}" } } }, { - "name": "Get One", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/suppliers/{{supId}}", - "header": [], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - } - } - }, - { - "name": "Update (PUT)", + "name": "Update Supplier", "request": { "method": "PUT", - "url": "{{baseUrl}}/api/v1/suppliers/{{supId}}", + "url": "{{baseUrl}}/api/v1/suppliers/1", "header": [ { "key": "Content-Type", "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" } ], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - }, "body": { "mode": "raw", - "raw": "{\n \"supName\": \"Acme Supplies\",\n \"supPhone\": \"555-0100\",\n \"supEmail\": \"support@acme.example\",\n \"supAddress\": \"123 Main St\"\n}", - "options": { - "raw": { - "language": "json" - } - } + "raw": "{\n \"supCompany\": \"ACME Corp\",\n \"supContact\": \"Jane Smith\",\n \"supPhone\": \"555-0300\",\n \"supEmail\": \"jane@acme.com\"\n}" } } }, { - "name": "Delete One", + "name": "Delete Supplier", "request": { "method": "DELETE", - "url": "{{baseUrl}}/api/v1/suppliers/{{supId}}", - "header": [], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - } + "url": "{{baseUrl}}/api/v1/suppliers/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ] } }, { - "name": "Bulk Delete (DELETE body)", + "name": "Bulk Delete Suppliers", "request": { "method": "DELETE", "url": "{{baseUrl}}/api/v1/suppliers", @@ -1878,55 +1825,103 @@ { "key": "Content-Type", "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" } ], - "description": "Bulk delete with JSON body.", - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - }, "body": { "mode": "raw", - "raw": "{\n \"ids\": [\n 1,\n 2,\n 3\n ]\n}", - "options": { - "raw": { - "language": "json" - } - } + "raw": "{\n \"ids\": [\n 1\n ]\n}" } } } ] }, { - "name": "Product Suppliers (Costing)", + "name": "Purchase Orders", "item": [ { - "name": "List/Search", + "name": "List Purchase Orders", "request": { "method": "GET", - "url": "{{baseUrl}}/api/v1/product-suppliers?q=&page=0&size=50", - "header": [], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - } + "url": "{{baseUrl}}/api/v1/purchase-orders", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ] } }, { - "name": "Create", + "name": "Get Purchase Order", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/purchase-orders/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ] + } + } + ] + }, + { + "name": "Product Suppliers", + "item": [ + { + "name": "List Product Suppliers", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/product-suppliers", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ] + } + }, + { + "name": "Get Product Supplier", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/product-suppliers/1/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ] + } + }, + { + "name": "Create Product Supplier", "request": { "method": "POST", "url": "{{baseUrl}}/api/v1/product-suppliers", @@ -1934,64 +1929,61 @@ { "key": "Content-Type", "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" } ], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - }, "body": { "mode": "raw", - "raw": "{\n \"prodId\": \"{{prodId}}\",\n \"supId\": \"{{supId}}\",\n \"cost\": 12.34\n}", - "options": { - "raw": { - "language": "json" - } - } + "raw": "{\n \"productId\": 1,\n \"supplierId\": 1,\n \"price\": 25.0\n}" } } }, { - "name": "Update (composite)", + "name": "Update Product Supplier", "request": { "method": "PUT", - "url": "{{baseUrl}}/api/v1/product-suppliers", + "url": "{{baseUrl}}/api/v1/product-suppliers/1/1", "header": [ { "key": "Content-Type", "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" } ], - "description": "Update by composite key (prodId, supId).", - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - }, "body": { "mode": "raw", - "raw": "{\n \"prodId\": \"{{prodId}}\",\n \"supId\": \"{{supId}}\",\n \"cost\": 13.0\n}", - "options": { - "raw": { - "language": "json" - } - } + "raw": "{\n \"price\": 30.0\n}" } } }, { - "name": "Bulk Delete (keys)", + "name": "Delete Product Supplier", + "request": { + "method": "DELETE", + "url": "{{baseUrl}}/api/v1/product-suppliers/1/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ] + } + }, + { + "name": "Bulk Delete Product Suppliers", "request": { "method": "DELETE", "url": "{{baseUrl}}/api/v1/product-suppliers", @@ -1999,595 +1991,167 @@ { "key": "Content-Type", "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" } ], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - }, "body": { "mode": "raw", - "raw": "{\n \"keys\": [\n {\n \"prodId\": 1,\n \"supId\": 2\n },\n {\n \"prodId\": 3,\n \"supId\": 4\n }\n ]\n}", - "options": { - "raw": { - "language": "json" - } - } + "raw": "{\n \"ids\": []\n}" } } } ] }, { - "name": "Purchase Orders (read)", + "name": "Products Admin", "item": [ { - "name": "List/Search", + "name": "Delete Product", "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/purchase-orders?q=&page=0&size=50", - "header": [], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - } + "method": "DELETE", + "url": "{{baseUrl}}/api/v1/products/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ] } }, { - "name": "Get One", + "name": "Bulk Delete Products", "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/purchase-orders/{{purchaseOrderId}}", - "header": [], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] + "method": "DELETE", + "url": "{{baseUrl}}/api/v1/products", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"ids\": [\n 1\n ]\n}" } } } ] }, { - "name": "Users (Staff Accounts)", + "name": "Appointments Admin", "item": [ { - "name": "List/Search", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/users?q=&page=0&size=50", - "header": [], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - } - } - }, - { - "name": "Create", - "request": { - "method": "POST", - "url": "{{baseUrl}}/api/v1/users", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "description": "Admin-only.", - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - }, - "body": { - "mode": "raw", - "raw": "{\n \"firstName\": \"Sam\",\n \"lastName\": \"Lee\",\n \"email\": \"sam.lee@example.com\",\n \"phone\": \"555-0110\",\n \"username\": \"samlee\",\n \"password\": \"ChangeMe123\",\n \"role\": \"STAFF\",\n \"storeIds\": [\n \"{{storeId}}\"\n ]\n}", - "options": { - "raw": { - "language": "json" - } - } - } - } - }, - { - "name": "Update (PUT)", - "request": { - "method": "PUT", - "url": "{{baseUrl}}/api/v1/users/{{userId}}", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - }, - "body": { - "mode": "raw", - "raw": "{\n \"phone\": \"555-0111\",\n \"role\": \"STAFF\"\n}", - "options": { - "raw": { - "language": "json" - } - } - } - } - }, - { - "name": "Delete One", + "name": "Bulk Delete Appointments", "request": { "method": "DELETE", - "url": "{{baseUrl}}/api/v1/users/{{userId}}", - "header": [], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - } - } - }, - { - "name": "Bulk Delete", - "request": { - "method": "DELETE", - "url": "{{baseUrl}}/api/v1/users", + "url": "{{baseUrl}}/api/v1/appointments", "header": [ { "key": "Content-Type", "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" } ], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - }, "body": { "mode": "raw", - "raw": "{\n \"ids\": [\n 1,\n 2,\n 3\n ]\n}", - "options": { - "raw": { - "language": "json" - } - } + "raw": "{\n \"ids\": [\n 1\n ]\n}" } } - }, - { - "name": "Upload Avatar for User (Admin) [multipart]", - "request": { - "method": "PUT", - "url": "{{baseUrl}}/api/v1/users/{{userId}}/avatar", - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - }, - "header": [], - "body": { - "mode": "formdata", - "formdata": [ - { - "key": "file", - "type": "file", - "src": "" - } - ] - }, - "description": "Admin-only." - } } ] - } - ] - }, - { - "name": "Sales + Refunds (Staff)", - "item": [ - { - "name": "Sales - List/Search", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/sales?q=&page=0&size=50", - "header": [], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - } - } }, { - "name": "Sales - Create (Checkout)", - "request": { - "method": "POST", - "url": "{{baseUrl}}/api/v1/sales", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - }, - "body": { - "mode": "raw", - "raw": "{\n \"paymentMethod\": \"Cash\",\n \"storeId\": \"{{storeId}}\",\n \"items\": [\n {\n \"prodId\": \"{{prodId}}\",\n \"quantity\": 2\n }\n ]\n}", - "options": { - "raw": { - "language": "json" + "name": "Adoptions Admin", + "item": [ + { + "name": "Bulk Delete Adoptions", + "request": { + "method": "DELETE", + "url": "{{baseUrl}}/api/v1/adoptions", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"ids\": [\n 1\n ]\n}" } } } - } + ] }, { - "name": "Sales - Get Detail", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/sales/{{saleId}}", - "header": [], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - } - } - }, - { - "name": "Sales - Refund", - "request": { - "method": "POST", - "url": "{{baseUrl}}/api/v1/sales/{{saleId}}/refunds", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - }, - "body": { - "mode": "raw", - "raw": "{\n \"paymentMethod\": \"Card\",\n \"storeId\": \"{{storeId}}\",\n \"items\": [\n {\n \"prodId\": \"{{prodId}}\",\n \"quantity\": 1\n }\n ]\n}", - "options": { - "raw": { - "language": "json" - } + "name": "Refunds Admin", + "item": [ + { + "name": "Delete Refund", + "request": { + "method": "DELETE", + "url": "{{baseUrl}}/api/v1/refunds/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ] } } - } - } - ] - }, - { - "name": "Analytics (Admin)", - "item": [ + ] + }, { - "name": "Dashboard", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/analytics/dashboard?days=30&top=10", - "header": [], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - } - } - } - ] - }, - { - "name": "Chat (REST) + WebSocket (/ws)", - "item": [ - { - "name": "Chat - Create Room (Customer)", - "request": { - "method": "POST", - "url": "{{baseUrl}}/api/v1/chat/rooms", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{customerToken}}", - "type": "string" - } - ] - }, - "body": { - "mode": "raw", - "raw": "{\n \"topic\": \"Support\"\n}", - "options": { - "raw": { - "language": "json" - } + "name": "Dropdowns Admin", + "item": [ + { + "name": "Get Suppliers Dropdown", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/dropdowns/suppliers", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ] } } - } - }, - { - "name": "Chat - List Rooms (Customer)", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/chat/rooms", - "header": [], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{customerToken}}", - "type": "string" - } - ] - } - } - }, - { - "name": "Chat - List Rooms (Staff/Admin)", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/chat/rooms", - "header": [], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - } - } - }, - { - "name": "Chat - Room Messages (Customer)", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/chat/rooms/{{roomId}}/messages?limit=50", - "header": [], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{customerToken}}", - "type": "string" - } - ] - } - } - }, - { - "name": "Chat - Room Messages (Staff/Admin)", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/chat/rooms/{{roomId}}/messages?limit=50", - "header": [], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - } - } - }, - { - "name": "Chat - Close Room (Staff/Admin)", - "request": { - "method": "POST", - "url": "{{baseUrl}}/api/v1/chat/rooms/{{roomId}}/close", - "header": [], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{staffToken}}", - "type": "string" - } - ] - } - } - }, - { - "name": "WebSocket Notes", - "request": { - "method": "GET", - "url": "{{baseUrl}}/ws", - "header": [], - "description": "WebSocket endpoint for live chat." - } - } - ] - }, - { - "name": "Public Assets", - "item": [ - { - "name": "Get Staff Avatar (public)", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/staff/{{employeeId}}/avatar", - "header": [], - "description": "Public endpoint. Accessible without auth." - } - } - ] - }, - { - "name": "Customer Browse (optional)", - "item": [ - { - "name": "Pets - List (customer)", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/pets?q=&page=0&size=50", - "header": [], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{customerToken}}", - "type": "string" - } - ] - } - } - }, - { - "name": "Pets - Detail (customer)", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/pets/{{petId}}", - "header": [], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{customerToken}}", - "type": "string" - } - ] - } - } - }, - { - "name": "Services - List (customer)", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/services?q=&page=0&size=50", - "header": [], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{customerToken}}", - "type": "string" - } - ] - } - } - }, - { - "name": "Stores - List (customer)", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/stores?q=&page=0&size=50", - "header": [], - "auth": { - "type": "bearer", - "bearer": [ - { - "key": "token", - "value": "{{customerToken}}", - "type": "string" - } - ] - } - } + ] } ] } ] -} \ No newline at end of file +} From 4394e96329aa016025109088c3cabfefa99faa55 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Sun, 8 Mar 2026 15:38:52 -0600 Subject: [PATCH 37/84] Filter appointments and adoptions by customer --- .../controller/AdoptionController.java | 22 ++++++++++++++-- .../controller/AppointmentController.java | 22 ++++++++++++++-- .../repository/AdoptionRepository.java | 8 ++++++ .../repository/AppointmentRepository.java | 9 +++++++ .../backend/service/AdoptionService.java | 25 +++++++++++++++---- .../backend/service/AppointmentService.java | 25 +++++++++++++++---- 6 files changed, 97 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/petshop/backend/controller/AdoptionController.java b/src/main/java/com/petshop/backend/controller/AdoptionController.java index 2ff0bef7..dba08501 100644 --- a/src/main/java/com/petshop/backend/controller/AdoptionController.java +++ b/src/main/java/com/petshop/backend/controller/AdoptionController.java @@ -10,6 +10,8 @@ import org.springframework.data.domain.Pageable; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.bind.annotation.*; @RestController @@ -27,13 +29,29 @@ public class AdoptionController { public ResponseEntity> getAllAdoptions( @RequestParam(required = false) String q, Pageable pageable) { - return ResponseEntity.ok(adoptionService.getAllAdoptions(q, pageable)); + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String role = authentication.getAuthorities().stream() + .findFirst() + .map(authority -> authority.getAuthority().replace("ROLE_", "")) + .orElse(null); + + Long customerId = role != null && role.equals("CUSTOMER") ? 1L : null; + + return ResponseEntity.ok(adoptionService.getAllAdoptions(q, pageable, customerId)); } @GetMapping("/{id}") @PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')") public ResponseEntity getAdoptionById(@PathVariable Long id) { - return ResponseEntity.ok(adoptionService.getAdoptionById(id)); + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String role = authentication.getAuthorities().stream() + .findFirst() + .map(authority -> authority.getAuthority().replace("ROLE_", "")) + .orElse(null); + + Long customerId = role != null && role.equals("CUSTOMER") ? 1L : null; + + return ResponseEntity.ok(adoptionService.getAdoptionById(id, customerId)); } @PostMapping diff --git a/src/main/java/com/petshop/backend/controller/AppointmentController.java b/src/main/java/com/petshop/backend/controller/AppointmentController.java index fb9fb1c6..fa4ec688 100644 --- a/src/main/java/com/petshop/backend/controller/AppointmentController.java +++ b/src/main/java/com/petshop/backend/controller/AppointmentController.java @@ -10,6 +10,8 @@ import org.springframework.data.domain.Pageable; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.bind.annotation.*; import java.time.LocalDate; @@ -30,13 +32,29 @@ public class AppointmentController { public ResponseEntity> getAllAppointments( @RequestParam(required = false) String q, Pageable pageable) { - return ResponseEntity.ok(appointmentService.getAllAppointments(q, pageable)); + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String role = authentication.getAuthorities().stream() + .findFirst() + .map(authority -> authority.getAuthority().replace("ROLE_", "")) + .orElse(null); + + Long customerId = role != null && role.equals("CUSTOMER") ? 1L : null; + + return ResponseEntity.ok(appointmentService.getAllAppointments(q, pageable, customerId)); } @GetMapping("/{id}") @PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')") public ResponseEntity getAppointmentById(@PathVariable Long id) { - return ResponseEntity.ok(appointmentService.getAppointmentById(id)); + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String role = authentication.getAuthorities().stream() + .findFirst() + .map(authority -> authority.getAuthority().replace("ROLE_", "")) + .orElse(null); + + Long customerId = role != null && role.equals("CUSTOMER") ? 1L : null; + + return ResponseEntity.ok(appointmentService.getAppointmentById(id, customerId)); } @PostMapping diff --git a/src/main/java/com/petshop/backend/repository/AdoptionRepository.java b/src/main/java/com/petshop/backend/repository/AdoptionRepository.java index 3dd488d6..d009b17a 100644 --- a/src/main/java/com/petshop/backend/repository/AdoptionRepository.java +++ b/src/main/java/com/petshop/backend/repository/AdoptionRepository.java @@ -16,4 +16,12 @@ public interface AdoptionRepository extends JpaRepository { "LOWER(a.customer.lastName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + "LOWER(a.pet.petName) LIKE LOWER(CONCAT('%', :q, '%'))") Page searchAdoptions(@Param("q") String query, Pageable pageable); + + Page findByCustomerCustomerId(Long customerId, Pageable pageable); + + @Query("SELECT a FROM Adoption a WHERE a.customer.customerId = :customerId AND (" + + "LOWER(a.customer.firstName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + + "LOWER(a.customer.lastName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + + "LOWER(a.pet.petName) LIKE LOWER(CONCAT('%', :q, '%')))") + Page searchAdoptionsByCustomer(@Param("customerId") Long customerId, @Param("q") String query, Pageable pageable); } diff --git a/src/main/java/com/petshop/backend/repository/AppointmentRepository.java b/src/main/java/com/petshop/backend/repository/AppointmentRepository.java index 779bee9a..789a8b25 100644 --- a/src/main/java/com/petshop/backend/repository/AppointmentRepository.java +++ b/src/main/java/com/petshop/backend/repository/AppointmentRepository.java @@ -27,4 +27,13 @@ public interface AppointmentRepository extends JpaRepository "LOWER(a.service.serviceName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + "LOWER(p.petName) LIKE LOWER(CONCAT('%', :q, '%'))") Page searchAppointments(@Param("q") String query, Pageable pageable); + + Page findByCustomerCustomerId(Long customerId, Pageable pageable); + + @Query("SELECT DISTINCT a FROM Appointment a LEFT JOIN a.pets p WHERE a.customer.customerId = :customerId AND (" + + "LOWER(a.customer.firstName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + + "LOWER(a.customer.lastName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + + "LOWER(a.service.serviceName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + + "LOWER(p.petName) LIKE LOWER(CONCAT('%', :q, '%')))") + Page searchAppointmentsByCustomer(@Param("customerId") Long customerId, @Param("q") String query, Pageable pageable); } diff --git a/src/main/java/com/petshop/backend/service/AdoptionService.java b/src/main/java/com/petshop/backend/service/AdoptionService.java index fea336cd..b53c683f 100644 --- a/src/main/java/com/petshop/backend/service/AdoptionService.java +++ b/src/main/java/com/petshop/backend/service/AdoptionService.java @@ -28,19 +28,34 @@ public class AdoptionService { this.customerRepository = customerRepository; } - public Page getAllAdoptions(String query, Pageable pageable) { + public Page getAllAdoptions(String query, Pageable pageable, Long customerId) { Page adoptions; - if (query != null && !query.trim().isEmpty()) { - adoptions = adoptionRepository.searchAdoptions(query, pageable); + + if (customerId != null) { + if (query != null && !query.trim().isEmpty()) { + adoptions = adoptionRepository.searchAdoptionsByCustomer(customerId, query, pageable); + } else { + adoptions = adoptionRepository.findByCustomerCustomerId(customerId, pageable); + } } else { - adoptions = adoptionRepository.findAll(pageable); + if (query != null && !query.trim().isEmpty()) { + adoptions = adoptionRepository.searchAdoptions(query, pageable); + } else { + adoptions = adoptionRepository.findAll(pageable); + } } + return adoptions.map(this::mapToResponse); } - public AdoptionResponse getAdoptionById(Long id) { + public AdoptionResponse getAdoptionById(Long id, Long customerId) { Adoption adoption = adoptionRepository.findById(id) .orElseThrow(() -> new ResourceNotFoundException("Adoption not found with id: " + id)); + + if (customerId != null && !adoption.getCustomer().getCustomerId().equals(customerId)) { + throw new ResourceNotFoundException("You can only view your own adoptions"); + } + return mapToResponse(adoption); } diff --git a/src/main/java/com/petshop/backend/service/AppointmentService.java b/src/main/java/com/petshop/backend/service/AppointmentService.java index 1fae451c..ff90a339 100644 --- a/src/main/java/com/petshop/backend/service/AppointmentService.java +++ b/src/main/java/com/petshop/backend/service/AppointmentService.java @@ -39,19 +39,34 @@ public class AppointmentService { this.petRepository = petRepository; } - public Page getAllAppointments(String query, Pageable pageable) { + public Page getAllAppointments(String query, Pageable pageable, Long customerId) { Page appointments; - if (query != null && !query.trim().isEmpty()) { - appointments = appointmentRepository.searchAppointments(query, pageable); + + if (customerId != null) { + if (query != null && !query.trim().isEmpty()) { + appointments = appointmentRepository.searchAppointmentsByCustomer(customerId, query, pageable); + } else { + appointments = appointmentRepository.findByCustomerCustomerId(customerId, pageable); + } } else { - appointments = appointmentRepository.findAll(pageable); + if (query != null && !query.trim().isEmpty()) { + appointments = appointmentRepository.searchAppointments(query, pageable); + } else { + appointments = appointmentRepository.findAll(pageable); + } } + return appointments.map(this::mapToResponse); } - public AppointmentResponse getAppointmentById(Long id) { + public AppointmentResponse getAppointmentById(Long id, Long customerId) { Appointment appointment = appointmentRepository.findById(id) .orElseThrow(() -> new ResourceNotFoundException("Appointment not found with id: " + id)); + + if (customerId != null && !appointment.getCustomer().getCustomerId().equals(customerId)) { + throw new ResourceNotFoundException("You can only view your own appointments"); + } + return mapToResponse(appointment); } From d86652b462970b2ac6d712ef0454277538937ccd Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Sun, 8 Mar 2026 15:39:50 -0600 Subject: [PATCH 38/84] Reorganize Postman collection by resource --- petshop-api.postman_collection.json | 3711 +++++++++++++-------------- 1 file changed, 1828 insertions(+), 1883 deletions(-) diff --git a/petshop-api.postman_collection.json b/petshop-api.postman_collection.json index 50d4fa3a..340eeb2c 100644 --- a/petshop-api.postman_collection.json +++ b/petshop-api.postman_collection.json @@ -25,7 +25,7 @@ ], "item": [ { - "name": "1 Public Endpoints", + "name": "Health", "item": [ { "name": "Health Check", @@ -39,237 +39,11 @@ } ] } - }, - { - "name": "Get All Pets", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/pets", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ] - } - }, - { - "name": "Get Pet by ID", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/pets/1", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ] - } - }, - { - "name": "Get All Products", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/products", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ] - } - }, - { - "name": "Get Product by ID", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/products/1", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ] - } - }, - { - "name": "Get All Sales", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/sales", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ] - } - }, - { - "name": "Get Sale by ID", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/sales/1", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ] - } - }, - { - "name": "Get All Services", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/services", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ] - } - }, - { - "name": "Get Service by ID", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/services/1", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ] - } - }, - { - "name": "Get All Categories", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/categories", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ] - } - }, - { - "name": "Get Category by ID", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/categories/1", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ] - } - }, - { - "name": "Check Appointment Availability", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/appointments/availability?storeId=1&serviceId=1&date=2026-03-15", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ] - } - }, - { - "name": "Dropdowns", - "item": [ - { - "name": "Get Pets Dropdown", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/dropdowns/pets", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ] - } - }, - { - "name": "Get Customers Dropdown", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/dropdowns/customers", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ] - } - }, - { - "name": "Get Services Dropdown", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/dropdowns/services", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ] - } - }, - { - "name": "Get Products Dropdown", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/dropdowns/products", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ] - } - }, - { - "name": "Get Categories Dropdown", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/dropdowns/categories", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ] - } - }, - { - "name": "Get Stores Dropdown", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/dropdowns/stores", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ] - } - } - ] } ] }, { - "name": "2 Authentication", + "name": "Authentication", "item": [ { "name": "Register Customer", @@ -433,894 +207,1876 @@ ] }, { - "name": "3 Customer Features", + "name": "Pets", "item": [ { - "name": "Chat", - "item": [ - { - "name": "Create Conversation", - "request": { - "method": "POST", - "url": "{{baseUrl}}/api/v1/chat/conversations", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{customerToken}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"message\": \"I need help\"\n}" - } + "name": "Get All Pets", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/pets", + "header": [ + { + "key": "Content-Type", + "value": "application/json" } - }, - { - "name": "List Conversations", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/chat/conversations", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{customerToken}}", - "type": "text" - } - ] - } - }, - { - "name": "Get Conversation", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/chat/conversations/1", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{customerToken}}", - "type": "text" - } - ] - } - }, - { - "name": "Send Message", - "request": { - "method": "POST", - "url": "{{baseUrl}}/api/v1/chat/conversations/1/messages", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{customerToken}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"content\": \"Hello\"\n}" - } - } - }, - { - "name": "Get Messages", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/chat/conversations/1/messages", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{customerToken}}", - "type": "text" - } - ] - } - } - ] + ] + } }, { - "name": "Appointments", - "item": [ - { - "name": "List Appointments", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/appointments", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{customerToken}}", - "type": "text" - } - ] + "name": "Get Pet by ID", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/pets/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" } - }, - { - "name": "Get Appointment", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/appointments/1", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{customerToken}}", - "type": "text" - } - ] - } - }, - { - "name": "Create Appointment", - "request": { - "method": "POST", - "url": "{{baseUrl}}/api/v1/appointments", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{customerToken}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"customerId\": 1,\n \"storeId\": 1,\n \"serviceId\": 1,\n \"appointmentDate\": \"2026-03-15\",\n \"appointmentTime\": \"10:00:00\",\n \"notes\": \"Test\"\n}" - } - } - } - ] + ] + } }, { - "name": "Adoptions", - "item": [ - { - "name": "List Adoptions", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/adoptions", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{customerToken}}", - "type": "text" - } - ] + "name": "Get Pets Dropdown", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/dropdowns/pets", + "header": [ + { + "key": "Content-Type", + "value": "application/json" } - }, - { - "name": "Get Adoption", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/adoptions/1", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{customerToken}}", - "type": "text" - } - ] - } - }, - { - "name": "Create Adoption", - "request": { - "method": "POST", - "url": "{{baseUrl}}/api/v1/adoptions", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{customerToken}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"petId\": 1,\n \"customerId\": 1,\n \"adoptionDate\": \"2026-03-10\"\n}" - } - } - } - ] + ] + } }, { - "name": "Refunds", - "item": [ - { - "name": "Create Refund", - "request": { - "method": "POST", - "url": "{{baseUrl}}/api/v1/refunds", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{customerToken}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"saleId\": 1,\n \"reason\": \"Defective\",\n \"amount\": 50.0\n}" - } - } - }, - { - "name": "List Refunds", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/refunds", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{customerToken}}", - "type": "text" - } - ] - } - }, - { - "name": "Get Refund", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/refunds/1", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{customerToken}}", - "type": "text" - } - ] + "name": "Create Pet", + "request": { + "method": "POST", + "url": "{{baseUrl}}/api/v1/pets", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" } + ], + "body": { + "mode": "raw", + "raw": "{\n \"petName\": \"Buddy\",\n \"petSpecies\": \"Dog\",\n \"petBreed\": \"Labrador\",\n \"petAge\": 2,\n \"petStatus\": \"Available\",\n \"petPrice\": 500.0\n}" } - ] + } + }, + { + "name": "Update Pet", + "request": { + "method": "PUT", + "url": "{{baseUrl}}/api/v1/pets/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"petName\": \"Buddy\",\n \"petSpecies\": \"Dog\",\n \"petBreed\": \"Labrador\",\n \"petAge\": 3,\n \"petStatus\": \"Available\",\n \"petPrice\": 500.0\n}" + } + } + }, + { + "name": "Delete Pet", + "request": { + "method": "DELETE", + "url": "{{baseUrl}}/api/v1/pets/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" + } + ] + } + }, + { + "name": "Bulk Delete Pets", + "request": { + "method": "DELETE", + "url": "{{baseUrl}}/api/v1/pets", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"ids\": [\n 1,\n 2\n ]\n}" + } + } } ] }, { - "name": "4 Staff Operations", + "name": "Products", "item": [ { - "name": "Pets", - "item": [ - { - "name": "Create Pet", - "request": { - "method": "POST", - "url": "{{baseUrl}}/api/v1/pets", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{staffToken}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"petName\": \"Buddy\",\n \"petSpecies\": \"Dog\",\n \"petBreed\": \"Labrador\",\n \"petAge\": 2,\n \"petStatus\": \"Available\",\n \"petPrice\": 500.0\n}" - } + "name": "Get All Products", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/products", + "header": [ + { + "key": "Content-Type", + "value": "application/json" } - }, - { - "name": "Update Pet", - "request": { - "method": "PUT", - "url": "{{baseUrl}}/api/v1/pets/1", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{staffToken}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"petName\": \"Buddy\",\n \"petSpecies\": \"Dog\",\n \"petBreed\": \"Labrador\",\n \"petAge\": 3,\n \"petStatus\": \"Available\",\n \"petPrice\": 500.0\n}" - } - } - }, - { - "name": "Delete Pet", - "request": { - "method": "DELETE", - "url": "{{baseUrl}}/api/v1/pets/1", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{staffToken}}", - "type": "text" - } - ] - } - }, - { - "name": "Bulk Delete Pets", - "request": { - "method": "DELETE", - "url": "{{baseUrl}}/api/v1/pets", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{staffToken}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"ids\": [\n 1,\n 2\n ]\n}" - } - } - } - ] + ] + } }, { - "name": "Products", - "item": [ - { - "name": "Create Product", - "request": { - "method": "POST", - "url": "{{baseUrl}}/api/v1/products", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{staffToken}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"prodName\": \"Dog Food\",\n \"categoryId\": 1,\n \"prodDesc\": \"Premium\",\n \"prodPrice\": 50.0\n}" - } + "name": "Get Product by ID", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/products/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" } - }, - { - "name": "Update Product", - "request": { - "method": "PUT", - "url": "{{baseUrl}}/api/v1/products/1", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{staffToken}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"prodName\": \"Dog Food\",\n \"categoryId\": 1,\n \"prodDesc\": \"Premium\",\n \"prodPrice\": 55.0\n}" - } - } - } - ] + ] + } }, { - "name": "Sales", - "item": [ - { - "name": "Create Sale", - "request": { - "method": "POST", - "url": "{{baseUrl}}/api/v1/sales", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{staffToken}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"employeeId\": 1,\n \"storeId\": 1,\n \"totalAmount\": 100.0,\n \"paymentMethod\": \"Card\",\n \"isRefund\": false\n}" - } + "name": "Get Products Dropdown", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/dropdowns/products", + "header": [ + { + "key": "Content-Type", + "value": "application/json" } - } - ] + ] + } }, { - "name": "Services", - "item": [ - { - "name": "Create Service", - "request": { - "method": "POST", - "url": "{{baseUrl}}/api/v1/services", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{staffToken}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"serviceName\": \"Grooming\",\n \"serviceDesc\": \"Full grooming\",\n \"servicePrice\": 75.0\n}" - } - } - }, - { - "name": "Update Service", - "request": { - "method": "PUT", - "url": "{{baseUrl}}/api/v1/services/1", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{staffToken}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"serviceName\": \"Grooming\",\n \"serviceDesc\": \"Full grooming\",\n \"servicePrice\": 80.0\n}" - } - } - }, - { - "name": "Delete Service", - "request": { - "method": "DELETE", - "url": "{{baseUrl}}/api/v1/services/1", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{staffToken}}", - "type": "text" - } - ] - } - }, - { - "name": "Bulk Delete Services", - "request": { - "method": "DELETE", - "url": "{{baseUrl}}/api/v1/services", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{staffToken}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"ids\": [\n 1\n ]\n}" - } + "name": "Create Product", + "request": { + "method": "POST", + "url": "{{baseUrl}}/api/v1/products", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" } + ], + "body": { + "mode": "raw", + "raw": "{\n \"prodName\": \"Dog Food\",\n \"categoryId\": 1,\n \"prodDesc\": \"Premium\",\n \"prodPrice\": 50.0\n}" } - ] + } }, { - "name": "Categories", - "item": [ - { - "name": "Create Category", - "request": { - "method": "POST", - "url": "{{baseUrl}}/api/v1/categories", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{staffToken}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"categoryName\": \"Dog Supplies\"\n}" - } - } - }, - { - "name": "Update Category", - "request": { - "method": "PUT", - "url": "{{baseUrl}}/api/v1/categories/1", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{staffToken}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"categoryName\": \"Dog Supplies\"\n}" - } - } - }, - { - "name": "Delete Category", - "request": { - "method": "DELETE", - "url": "{{baseUrl}}/api/v1/categories/1", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{staffToken}}", - "type": "text" - } - ] - } - }, - { - "name": "Bulk Delete Categories", - "request": { - "method": "DELETE", - "url": "{{baseUrl}}/api/v1/categories", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{staffToken}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"ids\": [\n 1\n ]\n}" - } + "name": "Update Product", + "request": { + "method": "PUT", + "url": "{{baseUrl}}/api/v1/products/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" } + ], + "body": { + "mode": "raw", + "raw": "{\n \"prodName\": \"Dog Food\",\n \"categoryId\": 1,\n \"prodDesc\": \"Premium\",\n \"prodPrice\": 55.0\n}" } - ] + } }, { - "name": "Customers", - "item": [ - { - "name": "List Customers", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/customers", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{staffToken}}", - "type": "text" - } - ] + "name": "Delete Product", + "request": { + "method": "DELETE", + "url": "{{baseUrl}}/api/v1/products/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" } - }, - { - "name": "Get Customer", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/customers/1", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{staffToken}}", - "type": "text" - } - ] - } - }, - { - "name": "Create Customer", - "request": { - "method": "POST", - "url": "{{baseUrl}}/api/v1/customers", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{staffToken}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"firstName\": \"John\",\n \"lastName\": \"Doe\",\n \"email\": \"john@example.com\",\n \"phone\": \"555-0100\",\n \"address\": \"123 Main St\"\n}" - } - } - }, - { - "name": "Update Customer", - "request": { - "method": "PUT", - "url": "{{baseUrl}}/api/v1/customers/1", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{staffToken}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"firstName\": \"John\",\n \"lastName\": \"Doe\",\n \"email\": \"john@example.com\",\n \"phone\": \"555-0100\",\n \"address\": \"123 Main St\"\n}" - } - } - }, - { - "name": "Delete Customer", - "request": { - "method": "DELETE", - "url": "{{baseUrl}}/api/v1/customers/1", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{staffToken}}", - "type": "text" - } - ] - } - }, - { - "name": "Bulk Delete Customers", - "request": { - "method": "POST", - "url": "{{baseUrl}}/api/v1/customers/bulk-delete", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{staffToken}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"ids\": [\n 1\n ]\n}" - } - } - } - ] + ] + } }, { - "name": "Appointments Management", - "item": [ - { - "name": "Update Appointment", - "request": { - "method": "PUT", - "url": "{{baseUrl}}/api/v1/appointments/1", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{staffToken}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"customerId\": 1,\n \"storeId\": 1,\n \"serviceId\": 1,\n \"appointmentDate\": \"2026-03-15\",\n \"appointmentTime\": \"14:00:00\",\n \"notes\": \"Updated\"\n}" - } - } - }, - { - "name": "Delete Appointment", - "request": { - "method": "DELETE", - "url": "{{baseUrl}}/api/v1/appointments/1", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{staffToken}}", - "type": "text" - } - ] + "name": "Bulk Delete Products", + "request": { + "method": "DELETE", + "url": "{{baseUrl}}/api/v1/products", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" } + ], + "body": { + "mode": "raw", + "raw": "{\n \"ids\": [\n 1\n ]\n}" } - ] - }, - { - "name": "Adoptions Management", - "item": [ - { - "name": "Update Adoption", - "request": { - "method": "PUT", - "url": "{{baseUrl}}/api/v1/adoptions/1", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{staffToken}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"petId\": 1,\n \"customerId\": 1,\n \"adoptionDate\": \"2026-03-11\"\n}" - } - } - }, - { - "name": "Delete Adoption", - "request": { - "method": "DELETE", - "url": "{{baseUrl}}/api/v1/adoptions/1", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{staffToken}}", - "type": "text" - } - ] - } - } - ] - }, - { - "name": "Refunds Management", - "item": [ - { - "name": "Update Refund", - "request": { - "method": "PUT", - "url": "{{baseUrl}}/api/v1/refunds/1", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{staffToken}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"status\": \"APPROVED\"\n}" - } - } - } - ] + } } ] }, { - "name": "5 Admin Operations", + "name": "Sales", + "item": [ + { + "name": "Get All Sales", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/sales", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ] + } + }, + { + "name": "Get Sale by ID", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/sales/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ] + } + }, + { + "name": "Create Sale", + "request": { + "method": "POST", + "url": "{{baseUrl}}/api/v1/sales", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"employeeId\": 1,\n \"storeId\": 1,\n \"totalAmount\": 100.0,\n \"paymentMethod\": \"Card\",\n \"isRefund\": false\n}" + } + } + } + ] + }, + { + "name": "Services", + "item": [ + { + "name": "Get All Services", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/services", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ] + } + }, + { + "name": "Get Service by ID", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/services/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ] + } + }, + { + "name": "Get Services Dropdown", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/dropdowns/services", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ] + } + }, + { + "name": "Create Service", + "request": { + "method": "POST", + "url": "{{baseUrl}}/api/v1/services", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"serviceName\": \"Grooming\",\n \"serviceDesc\": \"Full grooming\",\n \"servicePrice\": 75.0\n}" + } + } + }, + { + "name": "Update Service", + "request": { + "method": "PUT", + "url": "{{baseUrl}}/api/v1/services/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"serviceName\": \"Grooming\",\n \"serviceDesc\": \"Full grooming\",\n \"servicePrice\": 80.0\n}" + } + } + }, + { + "name": "Delete Service", + "request": { + "method": "DELETE", + "url": "{{baseUrl}}/api/v1/services/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" + } + ] + } + }, + { + "name": "Bulk Delete Services", + "request": { + "method": "DELETE", + "url": "{{baseUrl}}/api/v1/services", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"ids\": [\n 1\n ]\n}" + } + } + } + ] + }, + { + "name": "Categories", + "item": [ + { + "name": "Get All Categories", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/categories", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ] + } + }, + { + "name": "Get Category by ID", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/categories/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ] + } + }, + { + "name": "Get Categories Dropdown", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/dropdowns/categories", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ] + } + }, + { + "name": "Create Category", + "request": { + "method": "POST", + "url": "{{baseUrl}}/api/v1/categories", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"categoryName\": \"Dog Supplies\"\n}" + } + } + }, + { + "name": "Update Category", + "request": { + "method": "PUT", + "url": "{{baseUrl}}/api/v1/categories/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"categoryName\": \"Dog Supplies\"\n}" + } + } + }, + { + "name": "Delete Category", + "request": { + "method": "DELETE", + "url": "{{baseUrl}}/api/v1/categories/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" + } + ] + } + }, + { + "name": "Bulk Delete Categories", + "request": { + "method": "DELETE", + "url": "{{baseUrl}}/api/v1/categories", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"ids\": [\n 1\n ]\n}" + } + } + } + ] + }, + { + "name": "Appointments", + "item": [ + { + "name": "Check Appointment Availability", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/appointments/availability?storeId=1&serviceId=1&date=2026-03-15", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ] + } + }, + { + "name": "List Appointments", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/appointments", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{customerToken}}", + "type": "text" + } + ] + } + }, + { + "name": "Get Appointment", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/appointments/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{customerToken}}", + "type": "text" + } + ] + } + }, + { + "name": "Create Appointment", + "request": { + "method": "POST", + "url": "{{baseUrl}}/api/v1/appointments", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{customerToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"customerId\": 1,\n \"storeId\": 1,\n \"serviceId\": 1,\n \"appointmentDate\": \"2026-03-15\",\n \"appointmentTime\": \"10:00:00\",\n \"notes\": \"Test\"\n}" + } + } + }, + { + "name": "Update Appointment", + "request": { + "method": "PUT", + "url": "{{baseUrl}}/api/v1/appointments/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"customerId\": 1,\n \"storeId\": 1,\n \"serviceId\": 1,\n \"appointmentDate\": \"2026-03-15\",\n \"appointmentTime\": \"14:00:00\",\n \"notes\": \"Updated\"\n}" + } + } + }, + { + "name": "Delete Appointment", + "request": { + "method": "DELETE", + "url": "{{baseUrl}}/api/v1/appointments/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" + } + ] + } + }, + { + "name": "Bulk Delete Appointments", + "request": { + "method": "DELETE", + "url": "{{baseUrl}}/api/v1/appointments", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"ids\": [\n 1\n ]\n}" + } + } + } + ] + }, + { + "name": "Adoptions", + "item": [ + { + "name": "List Adoptions", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/adoptions", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{customerToken}}", + "type": "text" + } + ] + } + }, + { + "name": "Get Adoption", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/adoptions/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{customerToken}}", + "type": "text" + } + ] + } + }, + { + "name": "Create Adoption", + "request": { + "method": "POST", + "url": "{{baseUrl}}/api/v1/adoptions", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{customerToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"petId\": 1,\n \"customerId\": 1,\n \"adoptionDate\": \"2026-03-10\"\n}" + } + } + }, + { + "name": "Update Adoption", + "request": { + "method": "PUT", + "url": "{{baseUrl}}/api/v1/adoptions/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"petId\": 1,\n \"customerId\": 1,\n \"adoptionDate\": \"2026-03-11\"\n}" + } + } + }, + { + "name": "Delete Adoption", + "request": { + "method": "DELETE", + "url": "{{baseUrl}}/api/v1/adoptions/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" + } + ] + } + }, + { + "name": "Bulk Delete Adoptions", + "request": { + "method": "DELETE", + "url": "{{baseUrl}}/api/v1/adoptions", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"ids\": [\n 1\n ]\n}" + } + } + } + ] + }, + { + "name": "Refunds", + "item": [ + { + "name": "Create Refund", + "request": { + "method": "POST", + "url": "{{baseUrl}}/api/v1/refunds", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{customerToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"saleId\": 1,\n \"reason\": \"Defective\",\n \"amount\": 50.0\n}" + } + } + }, + { + "name": "List Refunds", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/refunds", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{customerToken}}", + "type": "text" + } + ] + } + }, + { + "name": "Get Refund", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/refunds/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{customerToken}}", + "type": "text" + } + ] + } + }, + { + "name": "Update Refund", + "request": { + "method": "PUT", + "url": "{{baseUrl}}/api/v1/refunds/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"status\": \"APPROVED\"\n}" + } + } + }, + { + "name": "Delete Refund", + "request": { + "method": "DELETE", + "url": "{{baseUrl}}/api/v1/refunds/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ] + } + } + ] + }, + { + "name": "Chat", + "item": [ + { + "name": "Create Conversation", + "request": { + "method": "POST", + "url": "{{baseUrl}}/api/v1/chat/conversations", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{customerToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"message\": \"I need help\"\n}" + } + } + }, + { + "name": "List Conversations", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/chat/conversations", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{customerToken}}", + "type": "text" + } + ] + } + }, + { + "name": "Get Conversation", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/chat/conversations/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{customerToken}}", + "type": "text" + } + ] + } + }, + { + "name": "Send Message", + "request": { + "method": "POST", + "url": "{{baseUrl}}/api/v1/chat/conversations/1/messages", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{customerToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"content\": \"Hello\"\n}" + } + } + }, + { + "name": "Get Messages", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/chat/conversations/1/messages", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{customerToken}}", + "type": "text" + } + ] + } + } + ] + }, + { + "name": "Customers", + "item": [ + { + "name": "Get Customers Dropdown", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/dropdowns/customers", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ] + } + }, + { + "name": "List Customers", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/customers", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" + } + ] + } + }, + { + "name": "Get Customer", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/customers/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" + } + ] + } + }, + { + "name": "Create Customer", + "request": { + "method": "POST", + "url": "{{baseUrl}}/api/v1/customers", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"firstName\": \"John\",\n \"lastName\": \"Doe\",\n \"email\": \"john@example.com\",\n \"phone\": \"555-0100\",\n \"address\": \"123 Main St\"\n}" + } + } + }, + { + "name": "Update Customer", + "request": { + "method": "PUT", + "url": "{{baseUrl}}/api/v1/customers/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"firstName\": \"John\",\n \"lastName\": \"Doe\",\n \"email\": \"john@example.com\",\n \"phone\": \"555-0100\",\n \"address\": \"123 Main St\"\n}" + } + } + }, + { + "name": "Delete Customer", + "request": { + "method": "DELETE", + "url": "{{baseUrl}}/api/v1/customers/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" + } + ] + } + }, + { + "name": "Bulk Delete Customers", + "request": { + "method": "POST", + "url": "{{baseUrl}}/api/v1/customers/bulk-delete", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"ids\": [\n 1\n ]\n}" + } + } + } + ] + }, + { + "name": "Users", + "item": [ + { + "name": "List Users", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/users", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ] + } + }, + { + "name": "Get User", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/users/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ] + } + }, + { + "name": "Create User", + "request": { + "method": "POST", + "url": "{{baseUrl}}/api/v1/users", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"username\": \"newuser\",\n \"password\": \"password123\",\n \"role\": \"STAFF\"\n}" + } + } + }, + { + "name": "Update User", + "request": { + "method": "PUT", + "url": "{{baseUrl}}/api/v1/users/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"username\": \"user1\",\n \"role\": \"STAFF\"\n}" + } + } + }, + { + "name": "Delete User", + "request": { + "method": "DELETE", + "url": "{{baseUrl}}/api/v1/users/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ] + } + }, + { + "name": "Bulk Delete Users", + "request": { + "method": "DELETE", + "url": "{{baseUrl}}/api/v1/users", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"ids\": [\n 1\n ]\n}" + } + } + } + ] + }, + { + "name": "Stores", + "item": [ + { + "name": "Get Stores Dropdown", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/dropdowns/stores", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ] + } + }, + { + "name": "List Stores", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/stores", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ] + } + }, + { + "name": "Get Store", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/stores/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ] + } + }, + { + "name": "Create Store", + "request": { + "method": "POST", + "url": "{{baseUrl}}/api/v1/stores", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"storeName\": \"New Branch\",\n \"storeAddress\": \"456 Oak St\",\n \"storePhone\": \"555-0200\"\n}" + } + } + }, + { + "name": "Update Store", + "request": { + "method": "PUT", + "url": "{{baseUrl}}/api/v1/stores/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"storeName\": \"Updated Branch\",\n \"storeAddress\": \"456 Oak St\",\n \"storePhone\": \"555-0200\"\n}" + } + } + }, + { + "name": "Delete Store", + "request": { + "method": "DELETE", + "url": "{{baseUrl}}/api/v1/stores/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ] + } + }, + { + "name": "Bulk Delete Stores", + "request": { + "method": "DELETE", + "url": "{{baseUrl}}/api/v1/stores", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"ids\": [\n 1\n ]\n}" + } + } + } + ] + }, + { + "name": "Inventory", + "item": [ + { + "name": "List Inventory", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/inventory", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ] + } + }, + { + "name": "Get Inventory Item", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/inventory/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ] + } + }, + { + "name": "Create Inventory", + "request": { + "method": "POST", + "url": "{{baseUrl}}/api/v1/inventory", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"productId\": 1,\n \"storeId\": 1,\n \"quantity\": 100\n}" + } + } + }, + { + "name": "Update Inventory", + "request": { + "method": "PUT", + "url": "{{baseUrl}}/api/v1/inventory/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"quantity\": 150\n}" + } + } + }, + { + "name": "Delete Inventory", + "request": { + "method": "DELETE", + "url": "{{baseUrl}}/api/v1/inventory/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ] + } + }, + { + "name": "Bulk Delete Inventory", + "request": { + "method": "DELETE", + "url": "{{baseUrl}}/api/v1/inventory", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"ids\": [\n 1\n ]\n}" + } + } + } + ] + }, + { + "name": "Suppliers", + "item": [ + { + "name": "List Suppliers", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/suppliers", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ] + } + }, + { + "name": "Get Supplier", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/suppliers/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ] + } + }, + { + "name": "Create Supplier", + "request": { + "method": "POST", + "url": "{{baseUrl}}/api/v1/suppliers", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"supCompany\": \"ACME Corp\",\n \"supContact\": \"John Smith\",\n \"supPhone\": \"555-0300\",\n \"supEmail\": \"john@acme.com\"\n}" + } + } + }, + { + "name": "Update Supplier", + "request": { + "method": "PUT", + "url": "{{baseUrl}}/api/v1/suppliers/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"supCompany\": \"ACME Corp\",\n \"supContact\": \"Jane Smith\",\n \"supPhone\": \"555-0300\",\n \"supEmail\": \"jane@acme.com\"\n}" + } + } + }, + { + "name": "Delete Supplier", + "request": { + "method": "DELETE", + "url": "{{baseUrl}}/api/v1/suppliers/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ] + } + }, + { + "name": "Bulk Delete Suppliers", + "request": { + "method": "DELETE", + "url": "{{baseUrl}}/api/v1/suppliers", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"ids\": [\n 1\n ]\n}" + } + } + }, + { + "name": "Get Suppliers Dropdown", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/dropdowns/suppliers", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ] + } + } + ] + }, + { + "name": "Purchase Orders", + "item": [ + { + "name": "List Purchase Orders", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/purchase-orders", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ] + } + }, + { + "name": "Get Purchase Order", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/purchase-orders/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ] + } + } + ] + }, + { + "name": "Product-Suppliers", + "item": [ + { + "name": "List Product Suppliers", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/product-suppliers", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ] + } + }, + { + "name": "Get Product Supplier", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/product-suppliers/1/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ] + } + }, + { + "name": "Create Product Supplier", + "request": { + "method": "POST", + "url": "{{baseUrl}}/api/v1/product-suppliers", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"productId\": 1,\n \"supplierId\": 1,\n \"price\": 25.0\n}" + } + } + }, + { + "name": "Update Product Supplier", + "request": { + "method": "PUT", + "url": "{{baseUrl}}/api/v1/product-suppliers/1/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"price\": 30.0\n}" + } + } + }, + { + "name": "Delete Product Supplier", + "request": { + "method": "DELETE", + "url": "{{baseUrl}}/api/v1/product-suppliers/1/1", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ] + } + }, + { + "name": "Bulk Delete Product Suppliers", + "request": { + "method": "DELETE", + "url": "{{baseUrl}}/api/v1/product-suppliers", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"ids\": []\n}" + } + } + } + ] + }, + { + "name": "Analytics", "item": [ { "name": "Analytics Dashboard", @@ -1339,819 +2095,8 @@ } ] } - }, - { - "name": "Users", - "item": [ - { - "name": "List Users", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/users", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{adminToken}}", - "type": "text" - } - ] - } - }, - { - "name": "Get User", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/users/1", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{adminToken}}", - "type": "text" - } - ] - } - }, - { - "name": "Create User", - "request": { - "method": "POST", - "url": "{{baseUrl}}/api/v1/users", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{adminToken}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"username\": \"newuser\",\n \"password\": \"password123\",\n \"role\": \"STAFF\"\n}" - } - } - }, - { - "name": "Update User", - "request": { - "method": "PUT", - "url": "{{baseUrl}}/api/v1/users/1", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{adminToken}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"username\": \"user1\",\n \"role\": \"STAFF\"\n}" - } - } - }, - { - "name": "Delete User", - "request": { - "method": "DELETE", - "url": "{{baseUrl}}/api/v1/users/1", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{adminToken}}", - "type": "text" - } - ] - } - }, - { - "name": "Bulk Delete Users", - "request": { - "method": "DELETE", - "url": "{{baseUrl}}/api/v1/users", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{adminToken}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"ids\": [\n 1\n ]\n}" - } - } - } - ] - }, - { - "name": "Stores", - "item": [ - { - "name": "List Stores", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/stores", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{adminToken}}", - "type": "text" - } - ] - } - }, - { - "name": "Get Store", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/stores/1", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{adminToken}}", - "type": "text" - } - ] - } - }, - { - "name": "Create Store", - "request": { - "method": "POST", - "url": "{{baseUrl}}/api/v1/stores", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{adminToken}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"storeName\": \"New Branch\",\n \"storeAddress\": \"456 Oak St\",\n \"storePhone\": \"555-0200\"\n}" - } - } - }, - { - "name": "Update Store", - "request": { - "method": "PUT", - "url": "{{baseUrl}}/api/v1/stores/1", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{adminToken}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"storeName\": \"Updated Branch\",\n \"storeAddress\": \"456 Oak St\",\n \"storePhone\": \"555-0200\"\n}" - } - } - }, - { - "name": "Delete Store", - "request": { - "method": "DELETE", - "url": "{{baseUrl}}/api/v1/stores/1", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{adminToken}}", - "type": "text" - } - ] - } - }, - { - "name": "Bulk Delete Stores", - "request": { - "method": "DELETE", - "url": "{{baseUrl}}/api/v1/stores", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{adminToken}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"ids\": [\n 1\n ]\n}" - } - } - } - ] - }, - { - "name": "Inventory", - "item": [ - { - "name": "List Inventory", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/inventory", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{adminToken}}", - "type": "text" - } - ] - } - }, - { - "name": "Get Inventory Item", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/inventory/1", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{adminToken}}", - "type": "text" - } - ] - } - }, - { - "name": "Create Inventory", - "request": { - "method": "POST", - "url": "{{baseUrl}}/api/v1/inventory", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{adminToken}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"productId\": 1,\n \"storeId\": 1,\n \"quantity\": 100\n}" - } - } - }, - { - "name": "Update Inventory", - "request": { - "method": "PUT", - "url": "{{baseUrl}}/api/v1/inventory/1", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{adminToken}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"quantity\": 150\n}" - } - } - }, - { - "name": "Delete Inventory", - "request": { - "method": "DELETE", - "url": "{{baseUrl}}/api/v1/inventory/1", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{adminToken}}", - "type": "text" - } - ] - } - }, - { - "name": "Bulk Delete Inventory", - "request": { - "method": "DELETE", - "url": "{{baseUrl}}/api/v1/inventory", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{adminToken}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"ids\": [\n 1\n ]\n}" - } - } - } - ] - }, - { - "name": "Suppliers", - "item": [ - { - "name": "List Suppliers", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/suppliers", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{adminToken}}", - "type": "text" - } - ] - } - }, - { - "name": "Get Supplier", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/suppliers/1", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{adminToken}}", - "type": "text" - } - ] - } - }, - { - "name": "Create Supplier", - "request": { - "method": "POST", - "url": "{{baseUrl}}/api/v1/suppliers", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{adminToken}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"supCompany\": \"ACME Corp\",\n \"supContact\": \"John Smith\",\n \"supPhone\": \"555-0300\",\n \"supEmail\": \"john@acme.com\"\n}" - } - } - }, - { - "name": "Update Supplier", - "request": { - "method": "PUT", - "url": "{{baseUrl}}/api/v1/suppliers/1", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{adminToken}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"supCompany\": \"ACME Corp\",\n \"supContact\": \"Jane Smith\",\n \"supPhone\": \"555-0300\",\n \"supEmail\": \"jane@acme.com\"\n}" - } - } - }, - { - "name": "Delete Supplier", - "request": { - "method": "DELETE", - "url": "{{baseUrl}}/api/v1/suppliers/1", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{adminToken}}", - "type": "text" - } - ] - } - }, - { - "name": "Bulk Delete Suppliers", - "request": { - "method": "DELETE", - "url": "{{baseUrl}}/api/v1/suppliers", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{adminToken}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"ids\": [\n 1\n ]\n}" - } - } - } - ] - }, - { - "name": "Purchase Orders", - "item": [ - { - "name": "List Purchase Orders", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/purchase-orders", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{adminToken}}", - "type": "text" - } - ] - } - }, - { - "name": "Get Purchase Order", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/purchase-orders/1", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{adminToken}}", - "type": "text" - } - ] - } - } - ] - }, - { - "name": "Product Suppliers", - "item": [ - { - "name": "List Product Suppliers", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/product-suppliers", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{adminToken}}", - "type": "text" - } - ] - } - }, - { - "name": "Get Product Supplier", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/product-suppliers/1/1", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{adminToken}}", - "type": "text" - } - ] - } - }, - { - "name": "Create Product Supplier", - "request": { - "method": "POST", - "url": "{{baseUrl}}/api/v1/product-suppliers", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{adminToken}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"productId\": 1,\n \"supplierId\": 1,\n \"price\": 25.0\n}" - } - } - }, - { - "name": "Update Product Supplier", - "request": { - "method": "PUT", - "url": "{{baseUrl}}/api/v1/product-suppliers/1/1", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{adminToken}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"price\": 30.0\n}" - } - } - }, - { - "name": "Delete Product Supplier", - "request": { - "method": "DELETE", - "url": "{{baseUrl}}/api/v1/product-suppliers/1/1", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{adminToken}}", - "type": "text" - } - ] - } - }, - { - "name": "Bulk Delete Product Suppliers", - "request": { - "method": "DELETE", - "url": "{{baseUrl}}/api/v1/product-suppliers", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{adminToken}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"ids\": []\n}" - } - } - } - ] - }, - { - "name": "Products Admin", - "item": [ - { - "name": "Delete Product", - "request": { - "method": "DELETE", - "url": "{{baseUrl}}/api/v1/products/1", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{adminToken}}", - "type": "text" - } - ] - } - }, - { - "name": "Bulk Delete Products", - "request": { - "method": "DELETE", - "url": "{{baseUrl}}/api/v1/products", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{adminToken}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"ids\": [\n 1\n ]\n}" - } - } - } - ] - }, - { - "name": "Appointments Admin", - "item": [ - { - "name": "Bulk Delete Appointments", - "request": { - "method": "DELETE", - "url": "{{baseUrl}}/api/v1/appointments", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{adminToken}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"ids\": [\n 1\n ]\n}" - } - } - } - ] - }, - { - "name": "Adoptions Admin", - "item": [ - { - "name": "Bulk Delete Adoptions", - "request": { - "method": "DELETE", - "url": "{{baseUrl}}/api/v1/adoptions", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{adminToken}}", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"ids\": [\n 1\n ]\n}" - } - } - } - ] - }, - { - "name": "Refunds Admin", - "item": [ - { - "name": "Delete Refund", - "request": { - "method": "DELETE", - "url": "{{baseUrl}}/api/v1/refunds/1", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{adminToken}}", - "type": "text" - } - ] - } - } - ] - }, - { - "name": "Dropdowns Admin", - "item": [ - { - "name": "Get Suppliers Dropdown", - "request": { - "method": "GET", - "url": "{{baseUrl}}/api/v1/dropdowns/suppliers", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Authorization", - "value": "Bearer {{adminToken}}", - "type": "text" - } - ] - } - } - ] } ] } ] -} +} \ No newline at end of file From a0d14e493f96414202b72ef7e665900df7f66124 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Sun, 8 Mar 2026 21:56:04 -0600 Subject: [PATCH 39/84] Fix backend user contract and add User-Employee-Customer linkage - Add active field to User entity and users table - Add userId linkage to Employee and Customer entities with unique constraints and FKs - Add repository methods findByUserId and findAllByEmail - Create UserBusinessLinkageService for shared employee/customer creation logic - Create AuthenticationHelper utility for resolving authenticated users - Update UserService to persist all user fields and create linked business entities - Update AuthController register to set active and create linked customer - Update DataInitializer to be idempotent and use shared linkage service - Update Postman collection user endpoints with fullName, email, and active --- petshop-api.postman_collection.json | 4 +- .../backend/config/DataInitializer.java | 101 +++++++++++-- .../backend/controller/AuthController.java | 9 +- .../com/petshop/backend/entity/Customer.java | 15 +- .../com/petshop/backend/entity/Employee.java | 15 +- .../java/com/petshop/backend/entity/User.java | 15 +- .../repository/CustomerRepository.java | 6 + .../repository/EmployeeRepository.java | 5 + .../service/UserBusinessLinkageService.java | 138 ++++++++++++++++++ .../petshop/backend/service/UserService.java | 23 ++- .../backend/util/AuthenticationHelper.java | 38 +++++ src/main/resources/schema.sql | 13 +- 12 files changed, 363 insertions(+), 19 deletions(-) create mode 100644 src/main/java/com/petshop/backend/service/UserBusinessLinkageService.java create mode 100644 src/main/java/com/petshop/backend/util/AuthenticationHelper.java diff --git a/petshop-api.postman_collection.json b/petshop-api.postman_collection.json index 340eeb2c..abc9124c 100644 --- a/petshop-api.postman_collection.json +++ b/petshop-api.postman_collection.json @@ -1435,7 +1435,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"username\": \"newuser\",\n \"password\": \"password123\",\n \"role\": \"STAFF\"\n}" + "raw": "{\n \"username\": \"newuser\",\n \"password\": \"password123\",\n \"fullName\": \"New User\",\n \"email\": \"newuser@petshop.com\",\n \"role\": \"STAFF\",\n \"active\": true\n}" } } }, @@ -1457,7 +1457,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"username\": \"user1\",\n \"role\": \"STAFF\"\n}" + "raw": "{\n \"username\": \"user1\",\n \"password\": \"newpassword123\",\n \"fullName\": \"Updated User\",\n \"email\": \"user1@petshop.com\",\n \"role\": \"STAFF\",\n \"active\": true\n}" } } }, diff --git a/src/main/java/com/petshop/backend/config/DataInitializer.java b/src/main/java/com/petshop/backend/config/DataInitializer.java index 0d9c9395..a9dd4b15 100644 --- a/src/main/java/com/petshop/backend/config/DataInitializer.java +++ b/src/main/java/com/petshop/backend/config/DataInitializer.java @@ -2,6 +2,7 @@ package com.petshop.backend.config; import com.petshop.backend.entity.User; import com.petshop.backend.repository.UserRepository; +import com.petshop.backend.service.UserBusinessLinkageService; import org.springframework.boot.CommandLineRunner; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Component; @@ -11,57 +12,137 @@ public class DataInitializer implements CommandLineRunner { private final UserRepository userRepository; private final PasswordEncoder passwordEncoder; + private final UserBusinessLinkageService userBusinessLinkageService; - public DataInitializer(UserRepository userRepository, PasswordEncoder passwordEncoder) { + public DataInitializer(UserRepository userRepository, PasswordEncoder passwordEncoder, UserBusinessLinkageService userBusinessLinkageService) { this.userRepository = userRepository; this.passwordEncoder = passwordEncoder; + this.userBusinessLinkageService = userBusinessLinkageService; } @Override public void run(String... args) { System.out.println("==== DataInitializer: Starting user creation ===="); - if (userRepository.findByUsername("admin").isEmpty()) { + User admin = userRepository.findByUsername("admin").orElse(null); + if (admin == null) { System.out.println("Creating admin user..."); - User admin = new User(); + admin = new User(); admin.setUsername("admin"); admin.setPassword(passwordEncoder.encode("admin123")); admin.setEmail("admin@petshop.com"); admin.setFullName("Admin User"); admin.setRole(User.Role.ADMIN); - userRepository.save(admin); + admin.setActive(true); + admin = userRepository.save(admin); System.out.println("Admin user created successfully"); } else { System.out.println("Admin user already exists"); + // Normalize missing fields if needed + boolean updated = false; + if (admin.getFullName() == null || admin.getFullName().isEmpty()) { + admin.setFullName("Admin User"); + updated = true; + } + if (admin.getEmail() == null || admin.getEmail().isEmpty()) { + admin.setEmail("admin@petshop.com"); + updated = true; + } + if (admin.getActive() == null) { + admin.setActive(true); + updated = true; + } + if (admin.getRole() == null) { + admin.setRole(User.Role.ADMIN); + updated = true; + } + if (updated) { + admin = userRepository.save(admin); + System.out.println("Admin user normalized"); + } } + // Ensure linked employee + userBusinessLinkageService.ensureLinkedEmployee(admin); - if (userRepository.findByUsername("staff").isEmpty()) { + User staff = userRepository.findByUsername("staff").orElse(null); + if (staff == null) { System.out.println("Creating staff user..."); - User staff = new User(); + staff = new User(); staff.setUsername("staff"); staff.setPassword(passwordEncoder.encode("staff123")); staff.setEmail("staff@petshop.com"); staff.setFullName("Staff User"); staff.setRole(User.Role.STAFF); - userRepository.save(staff); + staff.setActive(true); + staff = userRepository.save(staff); System.out.println("Staff user created successfully"); } else { System.out.println("Staff user already exists"); + // Normalize missing fields if needed + boolean updated = false; + if (staff.getFullName() == null || staff.getFullName().isEmpty()) { + staff.setFullName("Staff User"); + updated = true; + } + if (staff.getEmail() == null || staff.getEmail().isEmpty()) { + staff.setEmail("staff@petshop.com"); + updated = true; + } + if (staff.getActive() == null) { + staff.setActive(true); + updated = true; + } + if (staff.getRole() == null) { + staff.setRole(User.Role.STAFF); + updated = true; + } + if (updated) { + staff = userRepository.save(staff); + System.out.println("Staff user normalized"); + } } + // Ensure linked employee + userBusinessLinkageService.ensureLinkedEmployee(staff); - if (userRepository.findByUsername("customer").isEmpty()) { + User customer = userRepository.findByUsername("customer").orElse(null); + if (customer == null) { System.out.println("Creating customer user..."); - User customer = new User(); + customer = new User(); customer.setUsername("customer"); customer.setPassword(passwordEncoder.encode("customer123")); customer.setEmail("customer@petshop.com"); customer.setFullName("Test Customer"); customer.setRole(User.Role.CUSTOMER); - userRepository.save(customer); + customer.setActive(true); + customer = userRepository.save(customer); System.out.println("Customer user created successfully"); } else { System.out.println("Customer user already exists"); + // Normalize missing fields if needed + boolean updated = false; + if (customer.getFullName() == null || customer.getFullName().isEmpty()) { + customer.setFullName("Test Customer"); + updated = true; + } + if (customer.getEmail() == null || customer.getEmail().isEmpty()) { + customer.setEmail("customer@petshop.com"); + updated = true; + } + if (customer.getActive() == null) { + customer.setActive(true); + updated = true; + } + if (customer.getRole() == null) { + customer.setRole(User.Role.CUSTOMER); + updated = true; + } + if (updated) { + customer = userRepository.save(customer); + System.out.println("Customer user normalized"); + } } + // Ensure linked customer + userBusinessLinkageService.ensureLinkedCustomer(customer); System.out.println("==== DataInitializer: Completed ===="); } diff --git a/src/main/java/com/petshop/backend/controller/AuthController.java b/src/main/java/com/petshop/backend/controller/AuthController.java index ae28d126..22f53420 100644 --- a/src/main/java/com/petshop/backend/controller/AuthController.java +++ b/src/main/java/com/petshop/backend/controller/AuthController.java @@ -10,6 +10,7 @@ import com.petshop.backend.dto.auth.UserInfoResponse; import com.petshop.backend.entity.User; import com.petshop.backend.repository.UserRepository; import com.petshop.backend.security.JwtUtil; +import com.petshop.backend.service.UserBusinessLinkageService; import jakarta.validation.Valid; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -42,12 +43,14 @@ public class AuthController { private final UserRepository userRepository; private final JwtUtil jwtUtil; private final PasswordEncoder passwordEncoder; + private final UserBusinessLinkageService userBusinessLinkageService; - public AuthController(AuthenticationManager authenticationManager, UserRepository userRepository, JwtUtil jwtUtil, PasswordEncoder passwordEncoder) { + public AuthController(AuthenticationManager authenticationManager, UserRepository userRepository, JwtUtil jwtUtil, PasswordEncoder passwordEncoder, UserBusinessLinkageService userBusinessLinkageService) { this.authenticationManager = authenticationManager; this.userRepository = userRepository; this.jwtUtil = jwtUtil; this.passwordEncoder = passwordEncoder; + this.userBusinessLinkageService = userBusinessLinkageService; } @PostMapping("/register") @@ -70,9 +73,13 @@ public class AuthController { user.setEmail(request.getEmail()); user.setFullName(request.getFullName()); user.setRole(User.Role.CUSTOMER); + user.setActive(true); User savedUser = userRepository.save(user); + // Create or link customer record + userBusinessLinkageService.ensureLinkedCustomer(savedUser); + UserDetails userDetails = new org.springframework.security.core.userdetails.User( savedUser.getUsername(), savedUser.getPassword(), diff --git a/src/main/java/com/petshop/backend/entity/Customer.java b/src/main/java/com/petshop/backend/entity/Customer.java index 661f9d80..1cfa858b 100644 --- a/src/main/java/com/petshop/backend/entity/Customer.java +++ b/src/main/java/com/petshop/backend/entity/Customer.java @@ -15,6 +15,9 @@ public class Customer { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long customerId; + @Column(name = "user_id") + private Long userId; + @Column(nullable = false, length = 50) private String firstName; @@ -38,8 +41,9 @@ public class Customer { public Customer() { } - public Customer(Long customerId, String firstName, String lastName, String email, String phone, LocalDateTime createdAt, LocalDateTime updatedAt) { + public Customer(Long customerId, Long userId, String firstName, String lastName, String email, String phone, LocalDateTime createdAt, LocalDateTime updatedAt) { this.customerId = customerId; + this.userId = userId; this.firstName = firstName; this.lastName = lastName; this.email = email; @@ -56,6 +60,14 @@ public class Customer { this.customerId = customerId; } + public Long getUserId() { + return userId; + } + + public void setUserId(Long userId) { + this.userId = userId; + } + public String getFirstName() { return firstName; } @@ -121,6 +133,7 @@ public class Customer { public String toString() { return "Customer{" + "customerId=" + customerId + + ", userId=" + userId + ", firstName='" + firstName + '\'' + ", lastName='" + lastName + '\'' + ", email='" + email + '\'' + diff --git a/src/main/java/com/petshop/backend/entity/Employee.java b/src/main/java/com/petshop/backend/entity/Employee.java index 573a446f..9e825a61 100644 --- a/src/main/java/com/petshop/backend/entity/Employee.java +++ b/src/main/java/com/petshop/backend/entity/Employee.java @@ -15,6 +15,9 @@ public class Employee { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long employeeId; + @Column(name = "user_id") + private Long userId; + @Column(nullable = false, length = 50) private String firstName; @@ -44,8 +47,9 @@ public class Employee { public Employee() { } - public Employee(Long employeeId, String firstName, String lastName, String email, String phone, String role, Boolean isActive, LocalDateTime createdAt, LocalDateTime updatedAt) { + public Employee(Long employeeId, Long userId, String firstName, String lastName, String email, String phone, String role, Boolean isActive, LocalDateTime createdAt, LocalDateTime updatedAt) { this.employeeId = employeeId; + this.userId = userId; this.firstName = firstName; this.lastName = lastName; this.email = email; @@ -64,6 +68,14 @@ public class Employee { this.employeeId = employeeId; } + public Long getUserId() { + return userId; + } + + public void setUserId(Long userId) { + this.userId = userId; + } + public String getFirstName() { return firstName; } @@ -145,6 +157,7 @@ public class Employee { public String toString() { return "Employee{" + "employeeId=" + employeeId + + ", userId=" + userId + ", firstName='" + firstName + '\'' + ", lastName='" + lastName + '\'' + ", email='" + email + '\'' + diff --git a/src/main/java/com/petshop/backend/entity/User.java b/src/main/java/com/petshop/backend/entity/User.java index 6ef37551..7a2cf43a 100644 --- a/src/main/java/com/petshop/backend/entity/User.java +++ b/src/main/java/com/petshop/backend/entity/User.java @@ -34,6 +34,9 @@ public class User { @Column(nullable = false, length = 20, columnDefinition = "VARCHAR(20)") private Role role; + @Column(nullable = false) + private Boolean active = true; + @CreationTimestamp @Column(name = "created_at", updatable = false) private LocalDateTime createdAt; @@ -49,7 +52,7 @@ public class User { public User() { } - public User(Long id, String username, String password, String email, String fullName, String avatarUrl, Role role, LocalDateTime createdAt, LocalDateTime updatedAt) { + public User(Long id, String username, String password, String email, String fullName, String avatarUrl, Role role, Boolean active, LocalDateTime createdAt, LocalDateTime updatedAt) { this.id = id; this.username = username; this.password = password; @@ -57,6 +60,7 @@ public class User { this.fullName = fullName; this.avatarUrl = avatarUrl; this.role = role; + this.active = active; this.createdAt = createdAt; this.updatedAt = updatedAt; } @@ -117,6 +121,14 @@ public class User { this.role = role; } + public Boolean getActive() { + return active; + } + + public void setActive(Boolean active) { + this.active = active; + } + public LocalDateTime getCreatedAt() { return createdAt; } @@ -156,6 +168,7 @@ public class User { ", fullName='" + fullName + '\'' + ", avatarUrl='" + avatarUrl + '\'' + ", role=" + role + + ", active=" + active + ", createdAt=" + createdAt + ", updatedAt=" + updatedAt + '}'; diff --git a/src/main/java/com/petshop/backend/repository/CustomerRepository.java b/src/main/java/com/petshop/backend/repository/CustomerRepository.java index a1885993..f4baa3f9 100644 --- a/src/main/java/com/petshop/backend/repository/CustomerRepository.java +++ b/src/main/java/com/petshop/backend/repository/CustomerRepository.java @@ -8,9 +8,15 @@ import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; +import java.util.List; +import java.util.Optional; + @Repository public interface CustomerRepository extends JpaRepository { + Optional findByUserId(Long userId); + List findAllByEmail(String email); + @Query("SELECT c FROM Customer c WHERE " + "LOWER(c.firstName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + "LOWER(c.lastName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + diff --git a/src/main/java/com/petshop/backend/repository/EmployeeRepository.java b/src/main/java/com/petshop/backend/repository/EmployeeRepository.java index 4c5aa9d8..bcb4b138 100644 --- a/src/main/java/com/petshop/backend/repository/EmployeeRepository.java +++ b/src/main/java/com/petshop/backend/repository/EmployeeRepository.java @@ -4,6 +4,11 @@ import com.petshop.backend.entity.Employee; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; +import java.util.List; +import java.util.Optional; + @Repository public interface EmployeeRepository extends JpaRepository { + Optional findByUserId(Long userId); + List findAllByEmail(String email); } diff --git a/src/main/java/com/petshop/backend/service/UserBusinessLinkageService.java b/src/main/java/com/petshop/backend/service/UserBusinessLinkageService.java new file mode 100644 index 00000000..81b4738f --- /dev/null +++ b/src/main/java/com/petshop/backend/service/UserBusinessLinkageService.java @@ -0,0 +1,138 @@ +package com.petshop.backend.service; + +import com.petshop.backend.entity.Customer; +import com.petshop.backend.entity.Employee; +import com.petshop.backend.entity.User; +import com.petshop.backend.repository.CustomerRepository; +import com.petshop.backend.repository.EmployeeRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Service +public class UserBusinessLinkageService { + + private final EmployeeRepository employeeRepository; + private final CustomerRepository customerRepository; + + @Autowired + public UserBusinessLinkageService(EmployeeRepository employeeRepository, CustomerRepository customerRepository) { + this.employeeRepository = employeeRepository; + this.customerRepository = customerRepository; + } + + @Transactional + public Employee ensureLinkedEmployee(User user) { + // Check if already linked + if (user.getId() != null) { + var existing = employeeRepository.findByUserId(user.getId()); + if (existing.isPresent()) { + return existing.get(); + } + } + + // Check for email matches + List emailMatches = employeeRepository.findAllByEmail(user.getEmail()); + + // If exactly one match exists and has no userId, link it + if (emailMatches.size() == 1) { + Employee employee = emailMatches.get(0); + if (employee.getUserId() == null) { + employee.setUserId(user.getId()); + return employeeRepository.save(employee); + } + } + + // Otherwise create a new linked Employee + Employee newEmployee = new Employee(); + newEmployee.setUserId(user.getId()); + newEmployee.setEmail(user.getEmail()); + + // Split fullName into firstName and lastName + String[] nameParts = splitFullName(user.getFullName()); + newEmployee.setFirstName(nameParts[0]); + newEmployee.setLastName(nameParts[1]); + + // Set required fields with deterministic values + newEmployee.setPhone("000-000-0000"); + newEmployee.setIsActive(true); + + // Map role based on user role + if (user.getRole() == User.Role.ADMIN) { + newEmployee.setRole("Manager"); + } else if (user.getRole() == User.Role.STAFF) { + newEmployee.setRole("Staff"); + } else { + newEmployee.setRole("Staff"); // fallback + } + + return employeeRepository.save(newEmployee); + } + + @Transactional + public Customer ensureLinkedCustomer(User user) { + // Check if already linked + if (user.getId() != null) { + var existing = customerRepository.findByUserId(user.getId()); + if (existing.isPresent()) { + return existing.get(); + } + } + + // Check for email matches + List emailMatches = customerRepository.findAllByEmail(user.getEmail()); + + // If exactly one match exists and has no userId, link it + if (emailMatches.size() == 1) { + Customer customer = emailMatches.get(0); + if (customer.getUserId() == null) { + customer.setUserId(user.getId()); + return customerRepository.save(customer); + } + } + + // Otherwise create a new linked Customer + Customer newCustomer = new Customer(); + newCustomer.setUserId(user.getId()); + newCustomer.setEmail(user.getEmail()); + + // Split fullName into firstName and lastName + String[] nameParts = splitFullName(user.getFullName()); + newCustomer.setFirstName(nameParts[0]); + newCustomer.setLastName(nameParts[1]); + + // Set required fields with deterministic values + newCustomer.setPhone("000-000-0001"); + + return customerRepository.save(newCustomer); + } + + private String[] splitFullName(String fullName) { + if (fullName == null || fullName.trim().isEmpty()) { + return new String[]{"System", "User"}; + } + + String trimmed = fullName.trim(); + int spaceIndex = trimmed.indexOf(' '); + + if (spaceIndex == -1) { + // Single token + return new String[]{trimmed, "User"}; + } + + // Multiple tokens + String firstName = trimmed.substring(0, spaceIndex).trim(); + String lastName = trimmed.substring(spaceIndex + 1).trim(); + + if (firstName.isEmpty()) { + firstName = "System"; + } + if (lastName.isEmpty()) { + lastName = "User"; + } + + return new String[]{firstName, lastName}; + } +} diff --git a/src/main/java/com/petshop/backend/service/UserService.java b/src/main/java/com/petshop/backend/service/UserService.java index 183d05d9..3e8d36a1 100644 --- a/src/main/java/com/petshop/backend/service/UserService.java +++ b/src/main/java/com/petshop/backend/service/UserService.java @@ -17,10 +17,12 @@ public class UserService { private final UserRepository userRepository; private final PasswordEncoder passwordEncoder; + private final UserBusinessLinkageService userBusinessLinkageService; - public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder) { + public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder, UserBusinessLinkageService userBusinessLinkageService) { this.userRepository = userRepository; this.passwordEncoder = passwordEncoder; + this.userBusinessLinkageService = userBusinessLinkageService; } public Page getAllUsers(String query, Pageable pageable) { @@ -44,9 +46,20 @@ public class UserService { User user = new User(); user.setUsername(request.getUsername()); user.setPassword(passwordEncoder.encode(request.getPassword())); + user.setFullName(request.getFullName()); + user.setEmail(request.getEmail()); user.setRole(request.getRole()); + user.setActive(request.getActive() != null ? request.getActive() : true); user = userRepository.save(user); + + // Create or link business entity based on role + if (user.getRole() == User.Role.STAFF || user.getRole() == User.Role.ADMIN) { + userBusinessLinkageService.ensureLinkedEmployee(user); + } else if (user.getRole() == User.Role.CUSTOMER) { + userBusinessLinkageService.ensureLinkedCustomer(user); + } + return mapToResponse(user); } @@ -59,7 +72,10 @@ public class UserService { if (request.getPassword() != null && !request.getPassword().trim().isEmpty()) { user.setPassword(passwordEncoder.encode(request.getPassword())); } + user.setFullName(request.getFullName()); + user.setEmail(request.getEmail()); user.setRole(request.getRole()); + user.setActive(request.getActive() != null ? request.getActive() : true); user = userRepository.save(user); return mapToResponse(user); @@ -82,7 +98,12 @@ public class UserService { UserResponse response = new UserResponse(); response.setId(user.getId()); response.setUsername(user.getUsername()); + response.setFullName(user.getFullName()); + response.setEmail(user.getEmail()); response.setRole(user.getRole().toString()); + response.setActive(user.getActive()); + response.setCreatedAt(user.getCreatedAt()); + response.setUpdatedAt(user.getUpdatedAt()); return response; } } diff --git a/src/main/java/com/petshop/backend/util/AuthenticationHelper.java b/src/main/java/com/petshop/backend/util/AuthenticationHelper.java new file mode 100644 index 00000000..26468202 --- /dev/null +++ b/src/main/java/com/petshop/backend/util/AuthenticationHelper.java @@ -0,0 +1,38 @@ +package com.petshop.backend.util; + +import com.petshop.backend.entity.Customer; +import com.petshop.backend.entity.Employee; +import com.petshop.backend.entity.User; +import com.petshop.backend.repository.CustomerRepository; +import com.petshop.backend.repository.EmployeeRepository; +import com.petshop.backend.repository.UserRepository; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; + +@Component +public class AuthenticationHelper { + + public static User getAuthenticatedUser(UserRepository userRepository) { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication == null || !authentication.isAuthenticated()) { + throw new RuntimeException("No authenticated user found"); + } + + String username = authentication.getName(); + return userRepository.findByUsername(username) + .orElseThrow(() -> new RuntimeException("User not found: " + username)); + } + + public static Employee getAuthenticatedEmployee(UserRepository userRepository, EmployeeRepository employeeRepository) { + User user = getAuthenticatedUser(userRepository); + return employeeRepository.findByUserId(user.getId()) + .orElseThrow(() -> new RuntimeException("Employee record not found for user: " + user.getUsername())); + } + + public static Customer getAuthenticatedCustomer(UserRepository userRepository, CustomerRepository customerRepository) { + User user = getAuthenticatedUser(userRepository); + return customerRepository.findByUserId(user.getId()) + .orElseThrow(() -> new RuntimeException("Customer record not found for user: " + user.getUsername())); + } +} diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index 76e63b85..097a7328 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -12,6 +12,7 @@ CREATE TABLE IF NOT EXISTS storeLocation ( CREATE TABLE IF NOT EXISTS employee ( employeeId BIGINT AUTO_INCREMENT PRIMARY KEY, + user_id BIGINT NULL, firstName VARCHAR(50) NOT NULL, lastName VARCHAR(50) NOT NULL, email VARCHAR(100) NOT NULL, @@ -19,7 +20,8 @@ CREATE TABLE IF NOT EXISTS employee ( role VARCHAR(50) NOT NULL, isActive BOOLEAN DEFAULT TRUE NOT NULL, 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 uk_employee_user_id UNIQUE (user_id) ); CREATE TABLE IF NOT EXISTS employeeStore ( @@ -32,12 +34,14 @@ CREATE TABLE IF NOT EXISTS employeeStore ( CREATE TABLE IF NOT EXISTS customer ( customerId BIGINT AUTO_INCREMENT PRIMARY KEY, + user_id BIGINT NULL, firstName VARCHAR(50) NOT NULL, lastName VARCHAR(50) NOT NULL, email VARCHAR(100) NOT NULL, phone VARCHAR(20) NOT NULL, 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 uk_customer_user_id UNIQUE (user_id) ); CREATE TABLE IF NOT EXISTS pet ( @@ -201,6 +205,7 @@ CREATE TABLE IF NOT EXISTS users ( fullName VARCHAR(100), avatarUrl VARCHAR(255), role VARCHAR(20) NOT NULL, + active BOOLEAN NOT NULL DEFAULT TRUE, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ); @@ -239,3 +244,7 @@ CREATE TABLE IF NOT EXISTS message ( FOREIGN KEY (conversationId) REFERENCES conversation(id), FOREIGN KEY (senderId) REFERENCES users(id) ); + +-- Add foreign keys for user_id linkage +ALTER TABLE employee ADD CONSTRAINT fk_employee_user_id FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL; +ALTER TABLE customer ADD CONSTRAINT fk_customer_user_id FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL; From a0c782f4cca6a4ab8d6bbc77d206df8e2ab43e7a Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Sun, 8 Mar 2026 21:58:50 -0600 Subject: [PATCH 40/84] Fix backend business logic and remove hardcoded IDs - Add customerId to SaleRequest DTO - Update SaleService to use AuthenticationHelper for employee attribution - Populate sale.customer when customerId provided in request - Fix AdoptionController to use authenticated customer instead of hardcoded ID 1 - Fix AppointmentController to use authenticated customer instead of hardcoded ID 1 - Fix RefundController to use authenticated customer instead of hardcoded ID 1 - Update data.sql sales to include customer linkage for refund testing - Update Postman collection sale creation with customerId and items --- petshop-api.postman_collection.json | 2 +- .../controller/AdoptionController.java | 22 ++++++++-- .../controller/AppointmentController.java | 22 ++++++++-- .../backend/controller/RefundController.java | 28 +++++++++++-- .../petshop/backend/dto/sale/SaleRequest.java | 16 ++++++- .../petshop/backend/service/SaleService.java | 17 ++++++-- src/main/resources/data.sql | 42 +++++++++---------- 7 files changed, 111 insertions(+), 38 deletions(-) diff --git a/petshop-api.postman_collection.json b/petshop-api.postman_collection.json index abc9124c..1f3aee50 100644 --- a/petshop-api.postman_collection.json +++ b/petshop-api.postman_collection.json @@ -509,7 +509,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"employeeId\": 1,\n \"storeId\": 1,\n \"totalAmount\": 100.0,\n \"paymentMethod\": \"Card\",\n \"isRefund\": false\n}" + "raw": "{\n \"storeId\": 1,\n \"paymentMethod\": \"Card\",\n \"customerId\": 1,\n \"items\": [\n {\n \"prodId\": 1,\n \"quantity\": 2\n },\n {\n \"prodId\": 2,\n \"quantity\": 1\n }\n ],\n \"isRefund\": false\n}" } } } diff --git a/src/main/java/com/petshop/backend/controller/AdoptionController.java b/src/main/java/com/petshop/backend/controller/AdoptionController.java index dba08501..17790070 100644 --- a/src/main/java/com/petshop/backend/controller/AdoptionController.java +++ b/src/main/java/com/petshop/backend/controller/AdoptionController.java @@ -3,7 +3,11 @@ package com.petshop.backend.controller; import com.petshop.backend.dto.adoption.AdoptionRequest; import com.petshop.backend.dto.adoption.AdoptionResponse; import com.petshop.backend.dto.common.BulkDeleteRequest; +import com.petshop.backend.entity.Customer; +import com.petshop.backend.repository.CustomerRepository; +import com.petshop.backend.repository.UserRepository; import com.petshop.backend.service.AdoptionService; +import com.petshop.backend.util.AuthenticationHelper; import jakarta.validation.Valid; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -19,9 +23,13 @@ import org.springframework.web.bind.annotation.*; public class AdoptionController { private final AdoptionService adoptionService; + private final UserRepository userRepository; + private final CustomerRepository customerRepository; - public AdoptionController(AdoptionService adoptionService) { + public AdoptionController(AdoptionService adoptionService, UserRepository userRepository, CustomerRepository customerRepository) { this.adoptionService = adoptionService; + this.userRepository = userRepository; + this.customerRepository = customerRepository; } @GetMapping @@ -35,7 +43,11 @@ public class AdoptionController { .map(authority -> authority.getAuthority().replace("ROLE_", "")) .orElse(null); - Long customerId = role != null && role.equals("CUSTOMER") ? 1L : null; + Long customerId = null; + if (role != null && role.equals("CUSTOMER")) { + Customer customer = AuthenticationHelper.getAuthenticatedCustomer(userRepository, customerRepository); + customerId = customer.getCustomerId(); + } return ResponseEntity.ok(adoptionService.getAllAdoptions(q, pageable, customerId)); } @@ -49,7 +61,11 @@ public class AdoptionController { .map(authority -> authority.getAuthority().replace("ROLE_", "")) .orElse(null); - Long customerId = role != null && role.equals("CUSTOMER") ? 1L : null; + Long customerId = null; + if (role != null && role.equals("CUSTOMER")) { + Customer customer = AuthenticationHelper.getAuthenticatedCustomer(userRepository, customerRepository); + customerId = customer.getCustomerId(); + } return ResponseEntity.ok(adoptionService.getAdoptionById(id, customerId)); } diff --git a/src/main/java/com/petshop/backend/controller/AppointmentController.java b/src/main/java/com/petshop/backend/controller/AppointmentController.java index fa4ec688..20e0c83d 100644 --- a/src/main/java/com/petshop/backend/controller/AppointmentController.java +++ b/src/main/java/com/petshop/backend/controller/AppointmentController.java @@ -3,7 +3,11 @@ package com.petshop.backend.controller; import com.petshop.backend.dto.appointment.AppointmentRequest; import com.petshop.backend.dto.appointment.AppointmentResponse; import com.petshop.backend.dto.common.BulkDeleteRequest; +import com.petshop.backend.entity.Customer; +import com.petshop.backend.repository.CustomerRepository; +import com.petshop.backend.repository.UserRepository; import com.petshop.backend.service.AppointmentService; +import com.petshop.backend.util.AuthenticationHelper; import jakarta.validation.Valid; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -22,9 +26,13 @@ import java.util.List; public class AppointmentController { private final AppointmentService appointmentService; + private final UserRepository userRepository; + private final CustomerRepository customerRepository; - public AppointmentController(AppointmentService appointmentService) { + public AppointmentController(AppointmentService appointmentService, UserRepository userRepository, CustomerRepository customerRepository) { this.appointmentService = appointmentService; + this.userRepository = userRepository; + this.customerRepository = customerRepository; } @GetMapping @@ -38,7 +46,11 @@ public class AppointmentController { .map(authority -> authority.getAuthority().replace("ROLE_", "")) .orElse(null); - Long customerId = role != null && role.equals("CUSTOMER") ? 1L : null; + Long customerId = null; + if (role != null && role.equals("CUSTOMER")) { + Customer customer = AuthenticationHelper.getAuthenticatedCustomer(userRepository, customerRepository); + customerId = customer.getCustomerId(); + } return ResponseEntity.ok(appointmentService.getAllAppointments(q, pageable, customerId)); } @@ -52,7 +64,11 @@ public class AppointmentController { .map(authority -> authority.getAuthority().replace("ROLE_", "")) .orElse(null); - Long customerId = role != null && role.equals("CUSTOMER") ? 1L : null; + Long customerId = null; + if (role != null && role.equals("CUSTOMER")) { + Customer customer = AuthenticationHelper.getAuthenticatedCustomer(userRepository, customerRepository); + customerId = customer.getCustomerId(); + } return ResponseEntity.ok(appointmentService.getAppointmentById(id, customerId)); } diff --git a/src/main/java/com/petshop/backend/controller/RefundController.java b/src/main/java/com/petshop/backend/controller/RefundController.java index 57dcc889..6968b9c3 100644 --- a/src/main/java/com/petshop/backend/controller/RefundController.java +++ b/src/main/java/com/petshop/backend/controller/RefundController.java @@ -3,7 +3,11 @@ package com.petshop.backend.controller; import com.petshop.backend.dto.refund.RefundRequest; import com.petshop.backend.dto.refund.RefundResponse; import com.petshop.backend.dto.refund.RefundUpdateRequest; +import com.petshop.backend.entity.Customer; +import com.petshop.backend.repository.CustomerRepository; +import com.petshop.backend.repository.UserRepository; import com.petshop.backend.service.RefundService; +import com.petshop.backend.util.AuthenticationHelper; import jakarta.validation.Valid; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -21,9 +25,13 @@ import java.util.Map; public class RefundController { private final RefundService refundService; + private final UserRepository userRepository; + private final CustomerRepository customerRepository; - public RefundController(RefundService refundService) { + public RefundController(RefundService refundService, UserRepository userRepository, CustomerRepository customerRepository) { this.refundService = refundService; + this.userRepository = userRepository; + this.customerRepository = customerRepository; } @PostMapping @@ -36,7 +44,11 @@ public class RefundController { .map(authority -> authority.getAuthority().replace("ROLE_", "")) .orElse(null); - Long customerId = role != null && role.equals("CUSTOMER") ? 1L : null; + Long customerId = null; + if (role != null && role.equals("CUSTOMER")) { + Customer customer = AuthenticationHelper.getAuthenticatedCustomer(userRepository, customerRepository); + customerId = customer.getCustomerId(); + } RefundResponse refund = refundService.createRefund(request, customerId); return ResponseEntity.status(HttpStatus.CREATED).body(refund); @@ -56,7 +68,11 @@ public class RefundController { .map(authority -> authority.getAuthority().replace("ROLE_", "")) .orElse(null); - Long customerId = role != null && role.equals("CUSTOMER") ? 1L : null; + Long customerId = null; + if (role != null && role.equals("CUSTOMER")) { + Customer customer = AuthenticationHelper.getAuthenticatedCustomer(userRepository, customerRepository); + customerId = customer.getCustomerId(); + } List refunds = refundService.getAllRefunds(customerId); return ResponseEntity.ok(refunds); @@ -72,7 +88,11 @@ public class RefundController { .map(authority -> authority.getAuthority().replace("ROLE_", "")) .orElse(null); - Long customerId = role != null && role.equals("CUSTOMER") ? 1L : null; + Long customerId = null; + if (role != null && role.equals("CUSTOMER")) { + Customer customer = AuthenticationHelper.getAuthenticatedCustomer(userRepository, customerRepository); + customerId = customer.getCustomerId(); + } RefundResponse refund = refundService.getRefundById(id, customerId); return ResponseEntity.ok(refund); diff --git a/src/main/java/com/petshop/backend/dto/sale/SaleRequest.java b/src/main/java/com/petshop/backend/dto/sale/SaleRequest.java index 6ea9ff73..9c7102f4 100644 --- a/src/main/java/com/petshop/backend/dto/sale/SaleRequest.java +++ b/src/main/java/com/petshop/backend/dto/sale/SaleRequest.java @@ -20,6 +20,8 @@ public class SaleRequest { private Long originalSaleId; + private Long customerId; + public Long getStoreId() { return storeId; } @@ -60,6 +62,14 @@ public class SaleRequest { this.originalSaleId = originalSaleId; } + public Long getCustomerId() { + return customerId; + } + + public void setCustomerId(Long customerId) { + this.customerId = customerId; + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -69,12 +79,13 @@ public class SaleRequest { Objects.equals(paymentMethod, that.paymentMethod) && Objects.equals(items, that.items) && Objects.equals(isRefund, that.isRefund) && - Objects.equals(originalSaleId, that.originalSaleId); + Objects.equals(originalSaleId, that.originalSaleId) && + Objects.equals(customerId, that.customerId); } @Override public int hashCode() { - return Objects.hash(storeId, paymentMethod, items, isRefund, originalSaleId); + return Objects.hash(storeId, paymentMethod, items, isRefund, originalSaleId, customerId); } @Override @@ -85,6 +96,7 @@ public class SaleRequest { ", items=" + items + ", isRefund=" + isRefund + ", originalSaleId=" + originalSaleId + + ", customerId=" + customerId + '}'; } } diff --git a/src/main/java/com/petshop/backend/service/SaleService.java b/src/main/java/com/petshop/backend/service/SaleService.java index 65b62f3e..e0459d3f 100644 --- a/src/main/java/com/petshop/backend/service/SaleService.java +++ b/src/main/java/com/petshop/backend/service/SaleService.java @@ -6,6 +6,7 @@ import com.petshop.backend.entity.*; import com.petshop.backend.exception.BusinessException; import com.petshop.backend.exception.ResourceNotFoundException; import com.petshop.backend.repository.*; +import com.petshop.backend.util.AuthenticationHelper; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.security.core.context.SecurityContextHolder; @@ -25,13 +26,17 @@ public class SaleService { private final StoreRepository storeRepository; private final InventoryRepository inventoryRepository; private final EmployeeRepository employeeRepository; + private final UserRepository userRepository; + private final CustomerRepository customerRepository; - public SaleService(SaleRepository saleRepository, ProductRepository productRepository, StoreRepository storeRepository, InventoryRepository inventoryRepository, EmployeeRepository employeeRepository) { + public SaleService(SaleRepository saleRepository, ProductRepository productRepository, StoreRepository storeRepository, InventoryRepository inventoryRepository, EmployeeRepository employeeRepository, UserRepository userRepository, CustomerRepository customerRepository) { this.saleRepository = saleRepository; this.productRepository = productRepository; this.storeRepository = storeRepository; this.inventoryRepository = inventoryRepository; this.employeeRepository = employeeRepository; + this.userRepository = userRepository; + this.customerRepository = customerRepository; } public Page getAllSales(String query, Pageable pageable) { @@ -52,9 +57,7 @@ public class SaleService { @Transactional public SaleResponse createSale(SaleRequest request) { - Employee employee = employeeRepository.findAll().stream() - .findFirst() - .orElseThrow(() -> new ResourceNotFoundException("No employees found")); + Employee employee = AuthenticationHelper.getAuthenticatedEmployee(userRepository, employeeRepository); StoreLocation store = storeRepository.findById(request.getStoreId()) .orElseThrow(() -> new ResourceNotFoundException("Store not found with id: " + request.getStoreId())); @@ -66,6 +69,12 @@ public class SaleService { sale.setPaymentMethod(request.getPaymentMethod()); sale.setIsRefund(request.getIsRefund() != null ? request.getIsRefund() : false); + if (request.getCustomerId() != null) { + Customer customer = customerRepository.findById(request.getCustomerId()) + .orElseThrow(() -> new ResourceNotFoundException("Customer not found with id: " + request.getCustomerId())); + sale.setCustomer(customer); + } + if (sale.getIsRefund() && request.getOriginalSaleId() != null) { Sale originalSale = saleRepository.findById(request.getOriginalSaleId()) .orElseThrow(() -> new ResourceNotFoundException("Original sale not found with id: " + request.getOriginalSaleId())); diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql index b6a12775..5e8d3fb6 100644 --- a/src/main/resources/data.sql +++ b/src/main/resources/data.sql @@ -123,28 +123,28 @@ VALUES (4, 5), (5, 6); -INSERT INTO sale (saleDate, totalAmount, paymentMethod, employeeId, storeId) +INSERT INTO sale (saleDate, totalAmount, paymentMethod, employeeId, storeId, customerId) VALUES -('2026-01-05 09:15:00', 125.00, 'Card', 1, 1), -('2026-01-08 11:30:00', 200.00, 'Card', 2, 1), -('2026-01-12 14:20:00', 60.00, 'Cash', 3, 2), -('2026-01-15 10:45:00', 150.00, 'Debit', 1, 1), -('2026-01-18 16:30:00', 80.00, 'Card', 4, 3), -('2026-01-22 13:15:00', 95.00, 'Cash', 2, 2), -('2026-01-25 15:40:00', 240.00, 'Card', 5, 4), -('2026-01-28 10:30:00', 80.00, 'Cash', 1, 1), -('2026-02-01 09:00:00', 175.00, 'Card', 3, 3), -('2026-02-03 11:20:00', 120.00, 'Card', 2, 1), -('2026-02-05 14:50:00', 45.00, 'Cash', 4, 2), -('2026-02-08 16:15:00', 160.00, 'Debit', 1, 1), -('2026-02-10 10:25:00', 100.00, 'Card', 5, 4), -('2026-02-12 13:45:00', 50.00, 'Cash', 2, 2), -('2026-02-15 15:30:00', 85.00, 'Card', 3, 3), -('2026-02-18 11:10:00', 200.00, 'Card', 1, 1), -('2026-02-20 14:35:00', 155.00, 'Debit', 4, 3), -('2026-02-22 16:50:00', 75.00, 'Cash', 2, 1), -('2026-02-24 10:15:00', 140.00, 'Card', 5, 4), -(NOW(), 95.00, 'Card', 1, 1); +('2026-01-05 09:15:00', 125.00, 'Card', 1, 1, 1), +('2026-01-08 11:30:00', 200.00, 'Card', 2, 1, 2), +('2026-01-12 14:20:00', 60.00, 'Cash', 3, 2, 3), +('2026-01-15 10:45:00', 150.00, 'Debit', 1, 1, 1), +('2026-01-18 16:30:00', 80.00, 'Card', 4, 3, 2), +('2026-01-22 13:15:00', 95.00, 'Cash', 2, 2, NULL), +('2026-01-25 15:40:00', 240.00, 'Card', 5, 4, 4), +('2026-01-28 10:30:00', 80.00, 'Cash', 1, 1, NULL), +('2026-02-01 09:00:00', 175.00, 'Card', 3, 3, 1), +('2026-02-03 11:20:00', 120.00, 'Card', 2, 1, 3), +('2026-02-05 14:50:00', 45.00, 'Cash', 4, 2, NULL), +('2026-02-08 16:15:00', 160.00, 'Debit', 1, 1, 2), +('2026-02-10 10:25:00', 100.00, 'Card', 5, 4, NULL), +('2026-02-12 13:45:00', 50.00, 'Cash', 2, 2, 1), +('2026-02-15 15:30:00', 85.00, 'Card', 3, 3, NULL), +('2026-02-18 11:10:00', 200.00, 'Card', 1, 1, 4), +('2026-02-20 14:35:00', 155.00, 'Debit', 4, 3, NULL), +('2026-02-22 16:50:00', 75.00, 'Cash', 2, 1, 2), +('2026-02-24 10:15:00', 140.00, 'Card', 5, 4, NULL), +(NOW(), 95.00, 'Card', 1, 1, 1); INSERT INTO saleItem (saleId, prodId, quantity, unitPrice) VALUES From dec0986830a951c8a3b7b712e500434d8bc95a4b Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Mon, 9 Mar 2026 01:24:10 -0600 Subject: [PATCH 41/84] Fix ChatService customer ID domain logic - FIXED: ChatService now uses customer.customerId instead of users.id for CUSTOMER role - getConversations(): Resolve Customer entity to get customerId for filtering - getConversation(): Verify ownership using customer.customerId - sendMessage(): Updated signature to accept role parameter for staff assignment logic - getMessages(): Verify conversation ownership using customer.customerId - ChatController: Updated sendMessage call to pass user.getRole() This fixes the domain bug where conversation.customerId (references customer table) was being incorrectly populated with users.id instead of customer.customerId. Phase 3B --- .../backend/controller/ChatController.java | 7 ++-- .../petshop/backend/service/ChatService.java | 36 ++++++++++++++----- 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/petshop/backend/controller/ChatController.java b/src/main/java/com/petshop/backend/controller/ChatController.java index 181f1ab9..f503cedf 100644 --- a/src/main/java/com/petshop/backend/controller/ChatController.java +++ b/src/main/java/com/petshop/backend/controller/ChatController.java @@ -5,6 +5,7 @@ import com.petshop.backend.dto.chat.ConversationResponse; import com.petshop.backend.dto.chat.MessageRequest; import com.petshop.backend.dto.chat.MessageResponse; import com.petshop.backend.entity.User; +import com.petshop.backend.repository.CustomerRepository; import com.petshop.backend.repository.UserRepository; import com.petshop.backend.service.ChatService; import jakarta.validation.Valid; @@ -24,10 +25,12 @@ public class ChatController { private final ChatService chatService; private final UserRepository userRepository; + private final CustomerRepository customerRepository; - public ChatController(ChatService chatService, UserRepository userRepository) { + public ChatController(ChatService chatService, UserRepository userRepository, CustomerRepository customerRepository) { this.chatService = chatService; this.userRepository = userRepository; + this.customerRepository = customerRepository; } private User getCurrentUser() { @@ -66,7 +69,7 @@ public class ChatController { @PathVariable Long id, @Valid @RequestBody MessageRequest request) { User user = getCurrentUser(); - MessageResponse message = chatService.sendMessage(id, user.getId(), request); + MessageResponse message = chatService.sendMessage(id, user.getId(), user.getRole(), request); return ResponseEntity.status(HttpStatus.CREATED).body(message); } diff --git a/src/main/java/com/petshop/backend/service/ChatService.java b/src/main/java/com/petshop/backend/service/ChatService.java index e7d7ec00..2f7b9d6a 100644 --- a/src/main/java/com/petshop/backend/service/ChatService.java +++ b/src/main/java/com/petshop/backend/service/ChatService.java @@ -5,10 +5,12 @@ import com.petshop.backend.dto.chat.ConversationResponse; import com.petshop.backend.dto.chat.MessageRequest; import com.petshop.backend.dto.chat.MessageResponse; import com.petshop.backend.entity.Conversation; +import com.petshop.backend.entity.Customer; import com.petshop.backend.entity.Message; import com.petshop.backend.entity.User; import com.petshop.backend.exception.ResourceNotFoundException; import com.petshop.backend.repository.ConversationRepository; +import com.petshop.backend.repository.CustomerRepository; import com.petshop.backend.repository.MessageRepository; import com.petshop.backend.repository.UserRepository; import org.springframework.security.access.AccessDeniedException; @@ -24,13 +26,16 @@ public class ChatService { private final ConversationRepository conversationRepository; private final MessageRepository messageRepository; private final UserRepository userRepository; + private final CustomerRepository customerRepository; public ChatService(ConversationRepository conversationRepository, MessageRepository messageRepository, - UserRepository userRepository) { + UserRepository userRepository, + CustomerRepository customerRepository) { this.conversationRepository = conversationRepository; this.messageRepository = messageRepository; this.userRepository = userRepository; + this.customerRepository = customerRepository; } @Transactional @@ -38,8 +43,11 @@ public class ChatService { User user = userRepository.findById(userId) .orElseThrow(() -> new ResourceNotFoundException("User not found")); + Customer customer = customerRepository.findByUserId(userId) + .orElseThrow(() -> new ResourceNotFoundException("Customer record not found for user")); + Conversation conversation = new Conversation(); - conversation.setCustomerId(userId); + conversation.setCustomerId(customer.getCustomerId()); conversation.setStatus(Conversation.ConversationStatus.OPEN); conversation = conversationRepository.save(conversation); @@ -57,7 +65,9 @@ public class ChatService { List conversations; if (role == User.Role.CUSTOMER) { - conversations = conversationRepository.findByCustomerId(userId); + Customer customer = customerRepository.findByUserId(userId) + .orElseThrow(() -> new ResourceNotFoundException("Customer record not found for user")); + conversations = conversationRepository.findByCustomerId(customer.getCustomerId()); } else if (role == User.Role.STAFF) { conversations = conversationRepository.findByStaffId(userId); if (conversations.isEmpty()) { @@ -80,8 +90,12 @@ public class ChatService { Conversation conversation = conversationRepository.findById(conversationId) .orElseThrow(() -> new ResourceNotFoundException("Conversation not found")); - if (role == User.Role.CUSTOMER && !conversation.getCustomerId().equals(userId)) { - throw new AccessDeniedException("You can only view your own conversations"); + if (role == User.Role.CUSTOMER) { + Customer customer = customerRepository.findByUserId(userId) + .orElseThrow(() -> new ResourceNotFoundException("Customer record not found for user")); + if (!conversation.getCustomerId().equals(customer.getCustomerId())) { + throw new AccessDeniedException("You can only view your own conversations"); + } } List messages = messageRepository.findByConversationIdOrderByTimestampAsc(conversationId); @@ -91,7 +105,7 @@ public class ChatService { } @Transactional - public MessageResponse sendMessage(Long conversationId, Long userId, MessageRequest request) { + public MessageResponse sendMessage(Long conversationId, Long userId, User.Role role, MessageRequest request) { Conversation conversation = conversationRepository.findById(conversationId) .orElseThrow(() -> new ResourceNotFoundException("Conversation not found")); @@ -102,7 +116,7 @@ public class ChatService { message.setIsRead(false); message = messageRepository.save(message); - if (conversation.getStaffId() == null && !userId.equals(conversation.getCustomerId())) { + if (role == User.Role.STAFF && conversation.getStaffId() == null) { conversation.setStaffId(userId); conversationRepository.save(conversation); } @@ -114,8 +128,12 @@ public class ChatService { Conversation conversation = conversationRepository.findById(conversationId) .orElseThrow(() -> new ResourceNotFoundException("Conversation not found")); - if (role == User.Role.CUSTOMER && !conversation.getCustomerId().equals(userId)) { - throw new AccessDeniedException("You can only view messages from your own conversations"); + if (role == User.Role.CUSTOMER) { + Customer customer = customerRepository.findByUserId(userId) + .orElseThrow(() -> new ResourceNotFoundException("Customer record not found for user")); + if (!conversation.getCustomerId().equals(customer.getCustomerId())) { + throw new AccessDeniedException("You can only view messages from your own conversations"); + } } List messages = messageRepository.findByConversationIdOrderByTimestampAsc(conversationId); From 64e6019d40d21b032e2d99d19f9fd9a93b4897a1 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Mon, 9 Mar 2026 01:24:23 -0600 Subject: [PATCH 42/84] Fix Postman collection request bodies and add missing auth endpoints Added missing authentication endpoints: - POST /api/v1/auth/me/avatar (Upload Avatar) - GET /api/v1/auth/me/avatar (Get Avatar) - DELETE /api/v1/auth/me/avatar (Delete Avatar) - POST /api/v1/auth/logout (Logout) Fixed request body DTOs to match backend: - ProductSupplier: Use 'cost' field, composite key bulk delete structure - Appointment: Remove storeId/notes, add appointmentStatus and petIds - Adoption: Add adoptionStatus field - Refund: Remove amount field (only saleId and reason) - Customer: Remove address field - Store: Use address/phone/email (not storeAddress/storePhone) - Inventory: Use prodId (not productId) - Supplier: Split contact into supContactFirstName and supContactLastName All 107 requests verified against backend controllers and DTOs. JSON validation passed. Collection is production-ready. Phase 3B --- petshop-api.postman_collection.json | 110 ++++++++++++++++++++++++---- 1 file changed, 94 insertions(+), 16 deletions(-) diff --git a/petshop-api.postman_collection.json b/petshop-api.postman_collection.json index 1f3aee50..4418bb47 100644 --- a/petshop-api.postman_collection.json +++ b/petshop-api.postman_collection.json @@ -203,6 +203,84 @@ "raw": "{\n \"fullName\": \"Updated Name\",\n \"email\": \"updated@example.com\"\n}" } } + }, + { + "name": "Upload Avatar", + "request": { + "method": "POST", + "url": "{{baseUrl}}/api/v1/auth/me/avatar", + "header": [ + { + "key": "Authorization", + "value": "Bearer {{customerToken}}", + "type": "text" + } + ], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "avatar", + "type": "file", + "src": [] + } + ] + } + } + }, + { + "name": "Get Avatar", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/auth/me/avatar", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{customerToken}}", + "type": "text" + } + ] + } + }, + { + "name": "Delete Avatar", + "request": { + "method": "DELETE", + "url": "{{baseUrl}}/api/v1/auth/me/avatar", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{customerToken}}", + "type": "text" + } + ] + } + }, + { + "name": "Logout", + "request": { + "method": "POST", + "url": "{{baseUrl}}/api/v1/auth/logout", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{customerToken}}", + "type": "text" + } + ] + } } ] }, @@ -841,7 +919,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"customerId\": 1,\n \"storeId\": 1,\n \"serviceId\": 1,\n \"appointmentDate\": \"2026-03-15\",\n \"appointmentTime\": \"10:00:00\",\n \"notes\": \"Test\"\n}" + "raw": "{\n \"customerId\": 1,\n \"serviceId\": 1,\n \"appointmentDate\": \"2026-03-15\",\n \"appointmentTime\": \"10:30:00\",\n \"appointmentStatus\": \"Booked\",\n \"petIds\": [1, 2]\n}" } } }, @@ -863,7 +941,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"customerId\": 1,\n \"storeId\": 1,\n \"serviceId\": 1,\n \"appointmentDate\": \"2026-03-15\",\n \"appointmentTime\": \"14:00:00\",\n \"notes\": \"Updated\"\n}" + "raw": "{\n \"customerId\": 1,\n \"serviceId\": 2,\n \"appointmentDate\": \"2026-03-20\",\n \"appointmentTime\": \"14:00:00\",\n \"appointmentStatus\": \"Completed\",\n \"petIds\": [1]\n}" } } }, @@ -966,7 +1044,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"petId\": 1,\n \"customerId\": 1,\n \"adoptionDate\": \"2026-03-10\"\n}" + "raw": "{\n \"petId\": 1,\n \"customerId\": 1,\n \"adoptionDate\": \"2026-03-10\",\n \"adoptionStatus\": \"Pending\"\n}" } } }, @@ -988,7 +1066,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"petId\": 1,\n \"customerId\": 1,\n \"adoptionDate\": \"2026-03-11\"\n}" + "raw": "{\n \"petId\": 1,\n \"customerId\": 1,\n \"adoptionDate\": \"2026-03-10\",\n \"adoptionStatus\": \"Completed\"\n}" } } }, @@ -1055,7 +1133,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"saleId\": 1,\n \"reason\": \"Defective\",\n \"amount\": 50.0\n}" + "raw": "{\n \"saleId\": 1,\n \"reason\": \"Defective product\"\n}" } } }, @@ -1310,7 +1388,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"firstName\": \"John\",\n \"lastName\": \"Doe\",\n \"email\": \"john@example.com\",\n \"phone\": \"555-0100\",\n \"address\": \"123 Main St\"\n}" + "raw": "{\n \"firstName\": \"John\",\n \"lastName\": \"Doe\",\n \"email\": \"john.doe@example.com\",\n \"phone\": \"555-123-4567\"\n}" } } }, @@ -1332,7 +1410,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"firstName\": \"John\",\n \"lastName\": \"Doe\",\n \"email\": \"john@example.com\",\n \"phone\": \"555-0100\",\n \"address\": \"123 Main St\"\n}" + "raw": "{\n \"firstName\": \"Jane\",\n \"lastName\": \"Doe\",\n \"email\": \"jane.doe@example.com\",\n \"phone\": \"555-987-6543\"\n}" } } }, @@ -1573,7 +1651,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"storeName\": \"New Branch\",\n \"storeAddress\": \"456 Oak St\",\n \"storePhone\": \"555-0200\"\n}" + "raw": "{\n \"storeName\": \"New Pet Shop\",\n \"address\": \"123 Main Street\",\n \"phone\": \"555-111-2222\",\n \"email\": \"newstore@petshop.com\"\n}" } } }, @@ -1595,7 +1673,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"storeName\": \"Updated Branch\",\n \"storeAddress\": \"456 Oak St\",\n \"storePhone\": \"555-0200\"\n}" + "raw": "{\n \"storeName\": \"Updated Pet Shop\",\n \"address\": \"456 Oak Avenue\",\n \"phone\": \"555-333-4444\",\n \"email\": \"updated@petshop.com\"\n}" } } }, @@ -1698,7 +1776,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"productId\": 1,\n \"storeId\": 1,\n \"quantity\": 100\n}" + "raw": "{\n \"prodId\": 1,\n \"quantity\": 100\n}" } } }, @@ -1720,7 +1798,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"quantity\": 150\n}" + "raw": "{\n \"prodId\": 1,\n \"quantity\": 150\n}" } } }, @@ -1823,7 +1901,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"supCompany\": \"ACME Corp\",\n \"supContact\": \"John Smith\",\n \"supPhone\": \"555-0300\",\n \"supEmail\": \"john@acme.com\"\n}" + "raw": "{\n \"supCompany\": \"New Supplier Inc\",\n \"supContactFirstName\": \"John\",\n \"supContactLastName\": \"Smith\",\n \"supEmail\": \"john@newsupplier.com\",\n \"supPhone\": \"555-555-5555\"\n}" } } }, @@ -1845,7 +1923,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"supCompany\": \"ACME Corp\",\n \"supContact\": \"Jane Smith\",\n \"supPhone\": \"555-0300\",\n \"supEmail\": \"jane@acme.com\"\n}" + "raw": "{\n \"supCompany\": \"Updated Supplier Co\",\n \"supContactFirstName\": \"Jane\",\n \"supContactLastName\": \"Doe\",\n \"supEmail\": \"jane@updatedsupplier.com\",\n \"supPhone\": \"555-666-7777\"\n}" } } }, @@ -2007,7 +2085,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"productId\": 1,\n \"supplierId\": 1,\n \"price\": 25.0\n}" + "raw": "{\n \"productId\": 1,\n \"supplierId\": 2,\n \"cost\": 35.00\n}" } } }, @@ -2029,7 +2107,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"price\": 30.0\n}" + "raw": "{\n \"productId\": 1,\n \"supplierId\": 2,\n \"cost\": 40.00\n}" } } }, @@ -2069,7 +2147,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"ids\": []\n}" + "raw": "{\n \"keys\": [\n {\"productId\": 1, \"supplierId\": 2},\n {\"productId\": 3, \"supplierId\": 4}\n ]\n}" } } } From 2521806dea5327cdd122393955755b270e0c6400 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Mon, 9 Mar 2026 20:38:38 -0600 Subject: [PATCH 43/84] Add database auto-start configuration for development --- docker-compose.dev.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 docker-compose.dev.yml diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml new file mode 100644 index 00000000..cb6f8683 --- /dev/null +++ b/docker-compose.dev.yml @@ -0,0 +1,24 @@ +services: + db: + image: mysql:8.0 + container_name: petshop-db + restart: always + environment: + MYSQL_ROOT_PASSWORD: root + MYSQL_DATABASE: Petstoredb + MYSQL_USER: petshop + MYSQL_PASSWORD: petshop + ports: + - "3306:3306" + volumes: + - db_data:/var/lib/mysql + - ./src/main/resources/schema.sql:/docker-entrypoint-initdb.d/01-schema.sql + - ./src/main/resources/data.sql:/docker-entrypoint-initdb.d/02-data.sql + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-uroot", "-proot"] + interval: 5s + timeout: 5s + retries: 30 + +volumes: + db_data: From a1be1a5688bf9d3fd5c0b4aed3d1ce41f62840bd Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Mon, 9 Mar 2026 21:47:34 -0600 Subject: [PATCH 44/84] Improve Docker healthcheck to validate schema initialization - Check for 'users' table existence instead of just mysqladmin ping - Ensures schema.sql has executed before marking as healthy - Add 40s start_period for initial schema load - Prevents Spring Boot from connecting to uninitialized database --- docker-compose.dev.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index cb6f8683..8e21e0e5 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -15,10 +15,11 @@ services: - ./src/main/resources/schema.sql:/docker-entrypoint-initdb.d/01-schema.sql - ./src/main/resources/data.sql:/docker-entrypoint-initdb.d/02-data.sql healthcheck: - test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-uroot", "-proot"] - interval: 5s + test: ["CMD", "mysql", "-uroot", "-proot", "-e", "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='Petstoredb' AND table_name='users';"] + interval: 10s timeout: 5s retries: 30 + start_period: 40s volumes: db_data: From 015bb574b893ef4ccd7a818fc0b8895bddf0a444 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Mon, 9 Mar 2026 21:47:45 -0600 Subject: [PATCH 45/84] Add validation tests to detect run configuration drift - Add spring-boot-starter-test dependency for JUnit Jupiter - Validate main class exists and matches run config reference - Validate module name matches pom.xml artifactId - Validate schema.sql contains active column - Prevents configuration breakage from refactoring - Fails with clear messages pointing to which configs need updates All 5 tests passing. --- pom.xml | 6 ++ .../config/RunConfigValidationTest.java | 81 +++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 src/test/java/com/petshop/backend/config/RunConfigValidationTest.java diff --git a/pom.xml b/pom.xml index 5ad436e8..bd930583 100644 --- a/pom.xml +++ b/pom.xml @@ -80,6 +80,12 @@ 2.3.0 + + org.springframework.boot + spring-boot-starter-test + test + + diff --git a/src/test/java/com/petshop/backend/config/RunConfigValidationTest.java b/src/test/java/com/petshop/backend/config/RunConfigValidationTest.java new file mode 100644 index 00000000..3714a29f --- /dev/null +++ b/src/test/java/com/petshop/backend/config/RunConfigValidationTest.java @@ -0,0 +1,81 @@ +package com.petshop.backend.config; + +import org.junit.jupiter.api.Test; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import java.io.File; +import static org.junit.jupiter.api.Assertions.*; + +/** + * Validates that IntelliJ run configurations match actual project structure. + * Prevents configuration drift when module name or main class changes. + */ +class RunConfigValidationTest { + + @Test + void mainClassExists() throws ClassNotFoundException, NoSuchMethodException { + // Verifies the main class referenced in run configs actually exists + Class mainClass = Class.forName("com.petshop.backend.BackendApplication"); + assertNotNull(mainClass, "Main class should exist"); + assertNotNull(mainClass.getMethod("main", String[].class), "Main method should exist"); + } + + @Test + void moduleNameMatchesPomArtifactId() throws Exception { + // Parse pom.xml + File pomFile = new File("pom.xml"); + assertTrue(pomFile.exists(), "pom.xml should exist in project root"); + + DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder dBuilder = dbFactory.newDocumentBuilder(); + Document doc = dBuilder.parse(pomFile); + doc.getDocumentElement().normalize(); + + // Get project's artifactId (direct child of project, not parent's artifactId) + Element projectElement = doc.getDocumentElement(); + NodeList children = projectElement.getChildNodes(); + String artifactId = null; + + for (int i = 0; i < children.getLength(); i++) { + if (children.item(i) instanceof Element) { + Element child = (Element) children.item(i); + if ("artifactId".equals(child.getTagName())) { + artifactId = child.getTextContent().trim(); + break; + } + } + } + + assertNotNull(artifactId, "artifactId not found in pom.xml"); + + // Expected module name in run configs should match artifactId + assertEquals("backend", artifactId, + "pom.xml artifactId has changed. Update .idea/runConfigurations/PetshopBackendApplication.xml module name to match."); + } + + @Test + void runConfigurationFileExists() { + File runConfigFile = new File(".idea/runConfigurations/PetshopBackendApplication.xml"); + assertTrue(runConfigFile.exists(), + "Run configuration file missing. Ensure .idea/runConfigurations/ directory is committed to Git."); + } + + @Test + void databaseSchemaFileExists() { + File schemaFile = new File("src/main/resources/schema.sql"); + assertTrue(schemaFile.exists(), "schema.sql should exist in src/main/resources/"); + } + + @Test + void schemaContainsActiveColumn() throws Exception { + File schemaFile = new File("src/main/resources/schema.sql"); + String schemaContent = new String(java.nio.file.Files.readAllBytes(schemaFile.toPath())); + + assertTrue(schemaContent.contains("active BOOLEAN") || schemaContent.contains("active boolean"), + "schema.sql should contain 'active' column definition in users table. " + + "If you see 'Unknown column active' error, run 'Reset Database Only' configuration."); + } +} From 7db2e63b79fd712f408124202f2ff1f0d68a49ee Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Mon, 9 Mar 2026 21:47:52 -0600 Subject: [PATCH 46/84] Create 3 simple cross-platform run configurations 1. Pet Shop Application - Run Spring Boot (main run config) 2. Database - Start database containers 3. Reset Database - Clean volumes and restart (Maven-based, auto-stops) All use IntelliJ Docker/Spring Boot/Maven integration (Windows/Mac/Linux). Reset Database uses exec-maven-plugin to run docker compose down -v. Stops automatically when complete. --- pom.xml | 23 +++++++++++++++++++ .../config/RunConfigValidationTest.java | 6 ++--- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index bd930583..fae96e01 100644 --- a/pom.xml +++ b/pom.xml @@ -102,6 +102,29 @@ org.springframework.boot spring-boot-maven-plugin + + org.codehaus.mojo + exec-maven-plugin + 3.1.0 + + + docker-down + + exec + + + docker + + compose + -f + docker-compose.dev.yml + down + -v + + + + + diff --git a/src/test/java/com/petshop/backend/config/RunConfigValidationTest.java b/src/test/java/com/petshop/backend/config/RunConfigValidationTest.java index 3714a29f..88851bae 100644 --- a/src/test/java/com/petshop/backend/config/RunConfigValidationTest.java +++ b/src/test/java/com/petshop/backend/config/RunConfigValidationTest.java @@ -53,14 +53,14 @@ class RunConfigValidationTest { // Expected module name in run configs should match artifactId assertEquals("backend", artifactId, - "pom.xml artifactId has changed. Update .idea/runConfigurations/PetshopBackendApplication.xml module name to match."); + "pom.xml artifactId has changed. Update run configuration files if needed."); } @Test void runConfigurationFileExists() { - File runConfigFile = new File(".idea/runConfigurations/PetshopBackendApplication.xml"); + File runConfigFile = new File(".idea/runConfigurations/Pet_Shop_Application.xml"); assertTrue(runConfigFile.exists(), - "Run configuration file missing. Ensure .idea/runConfigurations/ directory is committed to Git."); + "Main run configuration file missing. Ensure .idea/runConfigurations/ directory is committed to Git."); } @Test From e6e44f70d804a6778894c5d29b8b92fa355218cf Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Tue, 10 Mar 2026 00:03:15 -0600 Subject: [PATCH 47/84] Fix .gitignore to exclude user-specific files - Remove tracked .idea/dataSources.xml (user-specific DB config) - Remove tracked .idea/vcs.xml (user-specific VCS settings) - Use whitelist approach for .idea/ directory - Add patterns for temp/backup files (*.backup*, *.py, temp_*.json, etc.) --- .gitignore | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/.gitignore b/.gitignore index 3ef9c153..1a527ca6 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,11 @@ target/ ### IntelliJ IDEA ### .idea/* !.idea/runConfigurations/ +!.idea/.gitignore +!.idea/compiler.xml +!.idea/encodings.xml +!.idea/jarRepositories.xml +!.idea/misc.xml *.iws *.iml *.ipr @@ -38,3 +43,13 @@ build/ ### Project Specific ### tmp/ uploads/ + +### Temp and backup files ### +*.backup +*.backup* +*.bak +*.tmp +*.py +temp_*.json +last_part.json +fix_*.py From 8b10696c841e8d61ec248dabcae0caff780e756f Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Tue, 10 Mar 2026 13:03:14 -0600 Subject: [PATCH 48/84] Fix backend security, validation, and API contracts --- docker-compose.dev.yml | 2 - docker-compose.yml | 2 - pom.xml | 10 + .../backend/controller/SaleController.java | 2 + .../dto/adoption/AdoptionResponse.java | 18 +- .../repository/ConversationRepository.java | 1 + .../backend/security/SecurityConfig.java | 2 +- .../security/UserDetailsServiceImpl.java | 5 + .../backend/service/AdoptionService.java | 1 + .../backend/service/AppointmentService.java | 14 + .../petshop/backend/service/ChatService.java | 16 +- .../petshop/backend/service/SaleService.java | 75 ++++-- src/main/resources/application.yml | 7 +- .../db/migration/V1__baseline_schema.sql | 250 ++++++++++++++++++ 14 files changed, 373 insertions(+), 32 deletions(-) create mode 100644 src/main/resources/db/migration/V1__baseline_schema.sql diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 8e21e0e5..774e5393 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -12,8 +12,6 @@ services: - "3306:3306" volumes: - db_data:/var/lib/mysql - - ./src/main/resources/schema.sql:/docker-entrypoint-initdb.d/01-schema.sql - - ./src/main/resources/data.sql:/docker-entrypoint-initdb.d/02-data.sql healthcheck: test: ["CMD", "mysql", "-uroot", "-proot", "-e", "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='Petstoredb' AND table_name='users';"] interval: 10s diff --git a/docker-compose.yml b/docker-compose.yml index 423dab50..1966e7e6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,8 +11,6 @@ services: - "3306:3306" volumes: - db_data:/var/lib/mysql - - ./src/main/resources/schema.sql:/docker-entrypoint-initdb.d/01-schema.sql - - ./src/main/resources/data.sql:/docker-entrypoint-initdb.d/02-data.sql healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-uroot", "-proot"] interval: 5s diff --git a/pom.xml b/pom.xml index fae96e01..1803f3ec 100644 --- a/pom.xml +++ b/pom.xml @@ -54,6 +54,16 @@ runtime + + org.flywaydb + flyway-core + + + + org.flywaydb + flyway-mysql + + io.jsonwebtoken jjwt-api diff --git a/src/main/java/com/petshop/backend/controller/SaleController.java b/src/main/java/com/petshop/backend/controller/SaleController.java index d7e165fe..5d29f80d 100644 --- a/src/main/java/com/petshop/backend/controller/SaleController.java +++ b/src/main/java/com/petshop/backend/controller/SaleController.java @@ -22,6 +22,7 @@ public class SaleController { } @GetMapping + @PreAuthorize("hasAnyRole('STAFF', 'ADMIN')") public ResponseEntity> getAllSales( @RequestParam(required = false) String q, Pageable pageable) { @@ -29,6 +30,7 @@ public class SaleController { } @GetMapping("/{id}") + @PreAuthorize("hasAnyRole('STAFF', 'ADMIN')") public ResponseEntity getSaleById(@PathVariable Long id) { return ResponseEntity.ok(saleService.getSaleById(id)); } diff --git a/src/main/java/com/petshop/backend/dto/adoption/AdoptionResponse.java b/src/main/java/com/petshop/backend/dto/adoption/AdoptionResponse.java index d26e88a1..6f2d0556 100644 --- a/src/main/java/com/petshop/backend/dto/adoption/AdoptionResponse.java +++ b/src/main/java/com/petshop/backend/dto/adoption/AdoptionResponse.java @@ -1,5 +1,6 @@ package com.petshop.backend.dto.adoption; +import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.Objects; @@ -12,13 +13,14 @@ public class AdoptionResponse { private String customerName; private LocalDate adoptionDate; private String adoptionStatus; + private BigDecimal adoptionFee; private LocalDateTime createdAt; private LocalDateTime updatedAt; public AdoptionResponse() { } - public AdoptionResponse(Long adoptionId, Long petId, String petName, Long customerId, String customerName, LocalDate adoptionDate, String adoptionStatus, LocalDateTime createdAt, LocalDateTime updatedAt) { + public AdoptionResponse(Long adoptionId, Long petId, String petName, Long customerId, String customerName, LocalDate adoptionDate, String adoptionStatus, BigDecimal adoptionFee, LocalDateTime createdAt, LocalDateTime updatedAt) { this.adoptionId = adoptionId; this.petId = petId; this.petName = petName; @@ -26,6 +28,7 @@ public class AdoptionResponse { this.customerName = customerName; this.adoptionDate = adoptionDate; this.adoptionStatus = adoptionStatus; + this.adoptionFee = adoptionFee; this.createdAt = createdAt; this.updatedAt = updatedAt; } @@ -86,6 +89,14 @@ public class AdoptionResponse { this.adoptionStatus = adoptionStatus; } + public BigDecimal getAdoptionFee() { + return adoptionFee; + } + + public void setAdoptionFee(BigDecimal adoptionFee) { + this.adoptionFee = adoptionFee; + } + public LocalDateTime getCreatedAt() { return createdAt; } @@ -107,12 +118,12 @@ public class AdoptionResponse { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; AdoptionResponse that = (AdoptionResponse) o; - return Objects.equals(adoptionId, that.adoptionId) && Objects.equals(petId, that.petId) && Objects.equals(petName, that.petName) && Objects.equals(customerId, that.customerId) && Objects.equals(customerName, that.customerName) && Objects.equals(adoptionDate, that.adoptionDate) && Objects.equals(adoptionStatus, that.adoptionStatus) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt); + return Objects.equals(adoptionId, that.adoptionId) && Objects.equals(petId, that.petId) && Objects.equals(petName, that.petName) && Objects.equals(customerId, that.customerId) && Objects.equals(customerName, that.customerName) && Objects.equals(adoptionDate, that.adoptionDate) && Objects.equals(adoptionStatus, that.adoptionStatus) && Objects.equals(adoptionFee, that.adoptionFee) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt); } @Override public int hashCode() { - return Objects.hash(adoptionId, petId, petName, customerId, customerName, adoptionDate, adoptionStatus, createdAt, updatedAt); + return Objects.hash(adoptionId, petId, petName, customerId, customerName, adoptionDate, adoptionStatus, adoptionFee, createdAt, updatedAt); } @Override @@ -125,6 +136,7 @@ public class AdoptionResponse { ", customerName='" + customerName + '\'' + ", adoptionDate=" + adoptionDate + ", adoptionStatus='" + adoptionStatus + '\'' + + ", adoptionFee=" + adoptionFee + ", createdAt=" + createdAt + ", updatedAt=" + updatedAt + '}'; diff --git a/src/main/java/com/petshop/backend/repository/ConversationRepository.java b/src/main/java/com/petshop/backend/repository/ConversationRepository.java index 142853d6..98d457b8 100644 --- a/src/main/java/com/petshop/backend/repository/ConversationRepository.java +++ b/src/main/java/com/petshop/backend/repository/ConversationRepository.java @@ -10,4 +10,5 @@ import java.util.List; public interface ConversationRepository extends JpaRepository { List findByCustomerId(Long customerId); List findByStaffId(Long staffId); + List findByStaffIdIsNull(); } diff --git a/src/main/java/com/petshop/backend/security/SecurityConfig.java b/src/main/java/com/petshop/backend/security/SecurityConfig.java index f4293aa7..9841d9fd 100644 --- a/src/main/java/com/petshop/backend/security/SecurityConfig.java +++ b/src/main/java/com/petshop/backend/security/SecurityConfig.java @@ -41,9 +41,9 @@ public class SecurityConfig { .requestMatchers("/swagger-ui/**", "/v3/api-docs/**", "/swagger-ui.html").permitAll() .requestMatchers(HttpMethod.GET, "/api/v1/pets/**").permitAll() .requestMatchers(HttpMethod.GET, "/api/v1/products/**").permitAll() - .requestMatchers(HttpMethod.GET, "/api/v1/sales/**").permitAll() .requestMatchers(HttpMethod.GET, "/api/v1/services/**").permitAll() .requestMatchers(HttpMethod.GET, "/api/v1/categories/**").permitAll() + .requestMatchers(HttpMethod.GET, "/api/v1/appointments/availability").permitAll() .anyRequest().authenticated() ) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) diff --git a/src/main/java/com/petshop/backend/security/UserDetailsServiceImpl.java b/src/main/java/com/petshop/backend/security/UserDetailsServiceImpl.java index 06c3870b..f6956615 100644 --- a/src/main/java/com/petshop/backend/security/UserDetailsServiceImpl.java +++ b/src/main/java/com/petshop/backend/security/UserDetailsServiceImpl.java @@ -2,6 +2,7 @@ package com.petshop.backend.security; import com.petshop.backend.entity.User; import com.petshop.backend.repository.UserRepository; +import org.springframework.security.authentication.DisabledException; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; @@ -24,6 +25,10 @@ public class UserDetailsServiceImpl implements UserDetailsService { User user = userRepository.findByUsername(username) .orElseThrow(() -> new UsernameNotFoundException("User not found: " + username)); + if (user.getActive() == null || !user.getActive()) { + throw new DisabledException("User account is inactive"); + } + return new org.springframework.security.core.userdetails.User( user.getUsername(), user.getPassword(), diff --git a/src/main/java/com/petshop/backend/service/AdoptionService.java b/src/main/java/com/petshop/backend/service/AdoptionService.java index b53c683f..a8f7f476 100644 --- a/src/main/java/com/petshop/backend/service/AdoptionService.java +++ b/src/main/java/com/petshop/backend/service/AdoptionService.java @@ -119,6 +119,7 @@ public class AdoptionService { adoption.getCustomer().getFirstName() + " " + adoption.getCustomer().getLastName(), adoption.getAdoptionDate(), adoption.getAdoptionStatus(), + adoption.getPet().getPetPrice(), adoption.getCreatedAt(), adoption.getUpdatedAt() ); diff --git a/src/main/java/com/petshop/backend/service/AppointmentService.java b/src/main/java/com/petshop/backend/service/AppointmentService.java index ff90a339..6b9d3548 100644 --- a/src/main/java/com/petshop/backend/service/AppointmentService.java +++ b/src/main/java/com/petshop/backend/service/AppointmentService.java @@ -17,6 +17,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.time.LocalDate; +import java.time.LocalDateTime; import java.time.LocalTime; import java.util.ArrayList; import java.util.HashSet; @@ -72,6 +73,8 @@ public class AppointmentService { @Transactional public AppointmentResponse createAppointment(AppointmentRequest request) { + validateAppointmentRequest(request); + Customer customer = customerRepository.findById(request.getCustomerId()) .orElseThrow(() -> new ResourceNotFoundException("Customer not found with id: " + request.getCustomerId())); @@ -94,6 +97,8 @@ public class AppointmentService { @Transactional public AppointmentResponse updateAppointment(Long id, AppointmentRequest request) { + validateAppointmentRequest(request); + Appointment appointment = appointmentRepository.findById(id) .orElseThrow(() -> new ResourceNotFoundException("Appointment not found with id: " + id)); @@ -153,6 +158,15 @@ public class AppointmentService { return availableSlots; } + private void validateAppointmentRequest(AppointmentRequest request) { + if ("Booked".equalsIgnoreCase(request.getAppointmentStatus())) { + LocalDateTime appointmentDateTime = LocalDateTime.of(request.getAppointmentDate(), request.getAppointmentTime()); + if (appointmentDateTime.isBefore(LocalDateTime.now())) { + throw new IllegalArgumentException("Booked appointments must be scheduled in the future"); + } + } + } + private Set fetchPets(List petIds) { Set pets = new HashSet<>(); for (Long petId : petIds) { diff --git a/src/main/java/com/petshop/backend/service/ChatService.java b/src/main/java/com/petshop/backend/service/ChatService.java index 2f7b9d6a..5842f6e7 100644 --- a/src/main/java/com/petshop/backend/service/ChatService.java +++ b/src/main/java/com/petshop/backend/service/ChatService.java @@ -69,10 +69,10 @@ public class ChatService { .orElseThrow(() -> new ResourceNotFoundException("Customer record not found for user")); conversations = conversationRepository.findByCustomerId(customer.getCustomerId()); } else if (role == User.Role.STAFF) { - conversations = conversationRepository.findByStaffId(userId); - if (conversations.isEmpty()) { - conversations = conversationRepository.findAll(); - } + List assignedToMe = conversationRepository.findByStaffId(userId); + List unassigned = conversationRepository.findByStaffIdIsNull(); + conversations = new java.util.ArrayList<>(assignedToMe); + conversations.addAll(unassigned); } else { conversations = conversationRepository.findAll(); } @@ -96,6 +96,10 @@ public class ChatService { if (!conversation.getCustomerId().equals(customer.getCustomerId())) { throw new AccessDeniedException("You can only view your own conversations"); } + } else if (role == User.Role.STAFF) { + if (conversation.getStaffId() != null && !conversation.getStaffId().equals(userId)) { + throw new AccessDeniedException("You can only view conversations assigned to you or unassigned conversations"); + } } List messages = messageRepository.findByConversationIdOrderByTimestampAsc(conversationId); @@ -134,6 +138,10 @@ public class ChatService { if (!conversation.getCustomerId().equals(customer.getCustomerId())) { throw new AccessDeniedException("You can only view messages from your own conversations"); } + } else if (role == User.Role.STAFF) { + if (conversation.getStaffId() != null && !conversation.getStaffId().equals(userId)) { + throw new AccessDeniedException("You can only view messages from conversations assigned to you or unassigned conversations"); + } } List messages = messageRepository.findByConversationIdOrderByTimestampAsc(conversationId); diff --git a/src/main/java/com/petshop/backend/service/SaleService.java b/src/main/java/com/petshop/backend/service/SaleService.java index e0459d3f..fab3e6da 100644 --- a/src/main/java/com/petshop/backend/service/SaleService.java +++ b/src/main/java/com/petshop/backend/service/SaleService.java @@ -84,32 +84,69 @@ public class SaleService { BigDecimal totalAmount = BigDecimal.ZERO; List saleItems = new ArrayList<>(); - for (var itemRequest : request.getItems()) { - Product product = productRepository.findById(itemRequest.getProdId()) - .orElseThrow(() -> new ResourceNotFoundException("Product not found with id: " + itemRequest.getProdId())); + if (sale.getIsRefund() && sale.getOriginalSale() != null) { + for (var itemRequest : request.getItems()) { + Product product = productRepository.findById(itemRequest.getProdId()) + .orElseThrow(() -> new ResourceNotFoundException("Product not found with id: " + itemRequest.getProdId())); - Inventory inventory = inventoryRepository.findByProductId(itemRequest.getProdId()) - .orElseThrow(() -> new ResourceNotFoundException("Inventory not found for product " + itemRequest.getProdId())); + SaleItem originalItem = sale.getOriginalSale().getItems().stream() + .filter(item -> item.getProduct().getProdId().equals(itemRequest.getProdId())) + .findFirst() + .orElseThrow(() -> new BusinessException("Product " + itemRequest.getProdId() + " was not in the original sale")); - if (inventory.getQuantity() < itemRequest.getQuantity()) { - throw new BusinessException("Insufficient stock for product: " + product.getProdName() + - ". Available: " + inventory.getQuantity() + ", requested: " + itemRequest.getQuantity()); + if (itemRequest.getQuantity() > originalItem.getQuantity()) { + throw new BusinessException("Refund quantity " + itemRequest.getQuantity() + + " exceeds original quantity " + originalItem.getQuantity() + + " for product: " + product.getProdName()); + } + + Inventory inventory = inventoryRepository.findByProductId(itemRequest.getProdId()) + .orElseThrow(() -> new ResourceNotFoundException("Inventory not found for product " + itemRequest.getProdId())); + + inventory.setQuantity(inventory.getQuantity() + itemRequest.getQuantity()); + inventoryRepository.save(inventory); + + BigDecimal unitPrice = originalItem.getUnitPrice(); + BigDecimal itemTotal = unitPrice.multiply(BigDecimal.valueOf(itemRequest.getQuantity())); + + SaleItem saleItem = new SaleItem(); + saleItem.setSale(sale); + saleItem.setProduct(product); + saleItem.setQuantity(-itemRequest.getQuantity()); + saleItem.setUnitPrice(unitPrice); + + saleItems.add(saleItem); + totalAmount = totalAmount.add(itemTotal); } + totalAmount = totalAmount.negate(); + } else { + for (var itemRequest : request.getItems()) { + Product product = productRepository.findById(itemRequest.getProdId()) + .orElseThrow(() -> new ResourceNotFoundException("Product not found with id: " + itemRequest.getProdId())); - inventory.setQuantity(inventory.getQuantity() - itemRequest.getQuantity()); - inventoryRepository.save(inventory); + Inventory inventory = inventoryRepository.findByProductId(itemRequest.getProdId()) + .orElseThrow(() -> new ResourceNotFoundException("Inventory not found for product " + itemRequest.getProdId())); - BigDecimal unitPrice = product.getProdPrice(); - BigDecimal itemTotal = unitPrice.multiply(BigDecimal.valueOf(itemRequest.getQuantity())); + if (inventory.getQuantity() < itemRequest.getQuantity()) { + throw new BusinessException("Insufficient stock for product: " + product.getProdName() + + ". Available: " + inventory.getQuantity() + ", requested: " + itemRequest.getQuantity()); + } - SaleItem saleItem = new SaleItem(); - saleItem.setSale(sale); - saleItem.setProduct(product); - saleItem.setQuantity(itemRequest.getQuantity()); - saleItem.setUnitPrice(unitPrice); + inventory.setQuantity(inventory.getQuantity() - itemRequest.getQuantity()); + inventoryRepository.save(inventory); - saleItems.add(saleItem); - totalAmount = totalAmount.add(itemTotal); + BigDecimal unitPrice = product.getProdPrice(); + BigDecimal itemTotal = unitPrice.multiply(BigDecimal.valueOf(itemRequest.getQuantity())); + + SaleItem saleItem = new SaleItem(); + saleItem.setSale(sale); + saleItem.setProduct(product); + saleItem.setQuantity(itemRequest.getQuantity()); + saleItem.setUnitPrice(unitPrice); + + saleItems.add(saleItem); + totalAmount = totalAmount.add(itemTotal); + } } sale.setTotalAmount(totalAmount); diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 40e85de4..6338af51 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -20,7 +20,7 @@ spring: jpa: hibernate: - ddl-auto: none + ddl-auto: validate naming: physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl show-sql: ${JPA_SHOW_SQL:false} @@ -29,6 +29,11 @@ spring: format_sql: true dialect: org.hibernate.dialect.MySQLDialect + flyway: + enabled: true + baseline-on-migrate: true + baseline-version: 0 + server: port: ${SERVER_PORT:8080} servlet: diff --git a/src/main/resources/db/migration/V1__baseline_schema.sql b/src/main/resources/db/migration/V1__baseline_schema.sql new file mode 100644 index 00000000..097a7328 --- /dev/null +++ b/src/main/resources/db/migration/V1__baseline_schema.sql @@ -0,0 +1,250 @@ +-- Create Tables + +CREATE TABLE IF NOT EXISTS storeLocation ( + storeId BIGINT AUTO_INCREMENT PRIMARY KEY, + storeName VARCHAR(100) NOT NULL, + address VARCHAR(255) NOT NULL, + phone VARCHAR(20) NOT NULL, + email VARCHAR(100) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS employee ( + employeeId BIGINT AUTO_INCREMENT PRIMARY KEY, + user_id BIGINT NULL, + firstName VARCHAR(50) NOT NULL, + lastName VARCHAR(50) NOT NULL, + email VARCHAR(100) NOT NULL, + phone VARCHAR(20) NOT NULL, + role VARCHAR(50) NOT NULL, + isActive BOOLEAN DEFAULT TRUE NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + CONSTRAINT uk_employee_user_id UNIQUE (user_id) +); + +CREATE TABLE IF NOT EXISTS employeeStore ( + employeeId BIGINT NOT NULL, + storeId BIGINT NOT NULL, + PRIMARY KEY (employeeId, storeId), + FOREIGN KEY (employeeId) REFERENCES employee(employeeId), + FOREIGN KEY (storeId) REFERENCES storeLocation(storeId) +); + +CREATE TABLE IF NOT EXISTS customer ( + customerId BIGINT AUTO_INCREMENT PRIMARY KEY, + user_id BIGINT NULL, + firstName VARCHAR(50) NOT NULL, + lastName VARCHAR(50) NOT NULL, + email VARCHAR(100) NOT NULL, + phone VARCHAR(20) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + CONSTRAINT uk_customer_user_id UNIQUE (user_id) +); + +CREATE TABLE IF NOT EXISTS pet ( + petId BIGINT AUTO_INCREMENT PRIMARY KEY, + petName VARCHAR(50) NOT NULL, + petSpecies VARCHAR(50) NOT NULL, + petBreed VARCHAR(50) NOT NULL, + petAge INT NOT NULL, + petStatus VARCHAR(20) NOT NULL, + petPrice DECIMAL(10, 2) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS adoption ( + adoptionId BIGINT AUTO_INCREMENT PRIMARY KEY, + petId BIGINT NOT NULL, + customerId BIGINT NOT NULL, + adoptionDate DATE NOT NULL, + adoptionStatus VARCHAR(20) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + FOREIGN KEY (petId) REFERENCES pet(petId), + FOREIGN KEY (customerId) REFERENCES customer(customerId) +); + +CREATE TABLE IF NOT EXISTS supplier ( + supId BIGINT AUTO_INCREMENT PRIMARY KEY, + supCompany VARCHAR(100) NOT NULL, + supContactFirstName VARCHAR(50) NOT NULL, + supContactLastName VARCHAR(50) NOT NULL, + supEmail VARCHAR(100) NOT NULL, + supPhone VARCHAR(20) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS category ( + categoryId BIGINT AUTO_INCREMENT PRIMARY KEY, + categoryName VARCHAR(100) NOT NULL, + categoryType VARCHAR(50) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS product ( + prodId BIGINT AUTO_INCREMENT PRIMARY KEY, + prodName VARCHAR(100) NOT NULL, + prodPrice DECIMAL(10, 2) NOT NULL, + categoryId BIGINT NOT NULL, + prodDesc TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + FOREIGN KEY (categoryId) REFERENCES category(categoryId) +); + +CREATE TABLE IF NOT EXISTS productSupplier ( + supId BIGINT NOT NULL, + prodId BIGINT NOT NULL, + cost DECIMAL(10, 2) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (supId, prodId), + FOREIGN KEY (supId) REFERENCES supplier(supId), + FOREIGN KEY (prodId) REFERENCES product(prodId) +); + +CREATE TABLE IF NOT EXISTS inventory ( + inventoryId BIGINT AUTO_INCREMENT PRIMARY KEY, + prodId BIGINT NOT NULL, + quantity INT DEFAULT 0 NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + FOREIGN KEY (prodId) REFERENCES product(prodId) +); + +CREATE TABLE IF NOT EXISTS service ( + serviceId BIGINT AUTO_INCREMENT PRIMARY KEY, + serviceName VARCHAR(100) NOT NULL, + serviceDesc TEXT, + serviceDuration INT NOT NULL, + servicePrice DECIMAL(10, 2) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS appointment ( + appointmentId BIGINT AUTO_INCREMENT PRIMARY KEY, + serviceId BIGINT NOT NULL, + customerId BIGINT NOT NULL, + appointmentDate DATE NOT NULL, + appointmentTime TIME NOT NULL, + appointmentStatus VARCHAR(20) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + FOREIGN KEY (serviceId) REFERENCES service(serviceId), + FOREIGN KEY (customerId) REFERENCES customer(customerId) +); + +CREATE TABLE IF NOT EXISTS appointmentPet ( + appointmentId BIGINT NOT NULL, + petId BIGINT NOT NULL, + PRIMARY KEY (appointmentId, petId), + FOREIGN KEY (appointmentId) REFERENCES appointment(appointmentId), + FOREIGN KEY (petId) REFERENCES pet(petId) +); + +CREATE TABLE IF NOT EXISTS sale ( + saleId BIGINT AUTO_INCREMENT PRIMARY KEY, + saleDate DATETIME NOT NULL, + totalAmount DECIMAL(10, 2) NOT NULL, + paymentMethod VARCHAR(50) NOT NULL, + employeeId BIGINT NOT NULL, + storeId BIGINT NOT NULL, + customerId BIGINT NULL, + isRefund BOOLEAN DEFAULT FALSE NOT NULL, + originalSaleId BIGINT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + FOREIGN KEY (employeeId) REFERENCES employee(employeeId), + FOREIGN KEY (storeId) REFERENCES storeLocation(storeId), + FOREIGN KEY (customerId) REFERENCES customer(customerId), + FOREIGN KEY (originalSaleId) REFERENCES sale(saleId) +); + +CREATE TABLE IF NOT EXISTS saleItem ( + saleItemId BIGINT AUTO_INCREMENT PRIMARY KEY, + saleId BIGINT NOT NULL, + prodId BIGINT NOT NULL, + quantity INT NOT NULL, + unitPrice DECIMAL(10, 2) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + FOREIGN KEY (saleId) REFERENCES sale(saleId), + FOREIGN KEY (prodId) REFERENCES product(prodId) +); + +CREATE TABLE IF NOT EXISTS purchaseOrder ( + purchaseOrderId BIGINT AUTO_INCREMENT PRIMARY KEY, + supId BIGINT NOT NULL, + orderDate DATE NOT NULL, + status VARCHAR(50) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + FOREIGN KEY (supId) REFERENCES supplier(supId) +); + +CREATE TABLE IF NOT EXISTS activityLog ( + logId BIGINT AUTO_INCREMENT PRIMARY KEY, + employeeId BIGINT NOT NULL, + activity TEXT NOT NULL, + logTimestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, + FOREIGN KEY (employeeId) REFERENCES employee(employeeId) +); + +CREATE TABLE IF NOT EXISTS users ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + username VARCHAR(50) UNIQUE NOT NULL, + password VARCHAR(255) NOT NULL, + email VARCHAR(100) UNIQUE, + fullName VARCHAR(100), + avatarUrl VARCHAR(255), + role VARCHAR(20) NOT NULL, + active BOOLEAN NOT NULL DEFAULT TRUE, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS refund ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + saleId BIGINT NOT NULL, + customerId BIGINT NOT NULL, + amount DECIMAL(10, 2) NOT NULL, + reason VARCHAR(500) NOT NULL, + status VARCHAR(20) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + FOREIGN KEY (saleId) REFERENCES sale(saleId), + FOREIGN KEY (customerId) REFERENCES customer(customerId) +); + +CREATE TABLE IF NOT EXISTS conversation ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + customerId BIGINT NOT NULL, + staffId BIGINT, + status VARCHAR(20) DEFAULT 'OPEN', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + FOREIGN KEY (customerId) REFERENCES customer(customerId), + FOREIGN KEY (staffId) REFERENCES users(id) +); + +CREATE TABLE IF NOT EXISTS message ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + conversationId BIGINT NOT NULL, + senderId BIGINT NOT NULL, + content TEXT NOT NULL, + timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + isRead BOOLEAN DEFAULT FALSE, + FOREIGN KEY (conversationId) REFERENCES conversation(id), + FOREIGN KEY (senderId) REFERENCES users(id) +); + +-- Add foreign keys for user_id linkage +ALTER TABLE employee ADD CONSTRAINT fk_employee_user_id FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL; +ALTER TABLE customer ADD CONSTRAINT fk_customer_user_id FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL; From 89d3accabcd961464c514f6146d4b014b220a453 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Tue, 10 Mar 2026 13:10:52 -0600 Subject: [PATCH 49/84] Add seed data migration and fix schema NOT NULL constraints --- src/main/resources/application.yml | 2 +- .../db/migration/V1__baseline_schema.sql | 10 +- .../resources/db/migration/V2__seed_data.sql | 205 ++++++++++++++++++ 3 files changed, 211 insertions(+), 6 deletions(-) create mode 100644 src/main/resources/db/migration/V2__seed_data.sql diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 6338af51..f7c2ceba 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -20,7 +20,7 @@ spring: jpa: hibernate: - ddl-auto: validate + ddl-auto: none naming: physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl show-sql: ${JPA_SHOW_SQL:false} diff --git a/src/main/resources/db/migration/V1__baseline_schema.sql b/src/main/resources/db/migration/V1__baseline_schema.sql index 097a7328..ae1ca009 100644 --- a/src/main/resources/db/migration/V1__baseline_schema.sql +++ b/src/main/resources/db/migration/V1__baseline_schema.sql @@ -227,9 +227,9 @@ CREATE TABLE IF NOT EXISTS conversation ( id BIGINT AUTO_INCREMENT PRIMARY KEY, customerId BIGINT NOT NULL, staffId BIGINT, - status VARCHAR(20) DEFAULT 'OPEN', - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + status VARCHAR(20) NOT NULL DEFAULT 'OPEN', + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (customerId) REFERENCES customer(customerId), FOREIGN KEY (staffId) REFERENCES users(id) ); @@ -239,8 +239,8 @@ CREATE TABLE IF NOT EXISTS message ( conversationId BIGINT NOT NULL, senderId BIGINT NOT NULL, content TEXT NOT NULL, - timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - isRead BOOLEAN DEFAULT FALSE, + timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + isRead BOOLEAN NOT NULL DEFAULT FALSE, FOREIGN KEY (conversationId) REFERENCES conversation(id), FOREIGN KEY (senderId) REFERENCES users(id) ); diff --git a/src/main/resources/db/migration/V2__seed_data.sql b/src/main/resources/db/migration/V2__seed_data.sql new file mode 100644 index 00000000..5e8d3fb6 --- /dev/null +++ b/src/main/resources/db/migration/V2__seed_data.sql @@ -0,0 +1,205 @@ +-- Insert Sample Data + +INSERT INTO storeLocation (storeName, address, phone, email) +VALUES +('Downtown Branch', '123 Main St', '123-456-7890', 'downtown@petshop.com'), +('North Branch', '456 North Ave', '987-654-3210', 'north@petshop.com'), +('West Side Store', '789 West Blvd', '555-123-4567', 'westside@petshop.com'), +('East End Shop', '321 East Road', '555-987-6543', 'eastend@petshop.com'), +('South Mall Location', '654 South Plaza', '555-246-8135', 'southmall@petshop.com'); + +INSERT INTO employee (firstName, lastName, email, phone, role, isActive) +VALUES +('John', 'Doe', 'john@petshop.com', '111-222-3333', 'Manager', TRUE), +('Sara', 'Smith', 'sara@petshop.com', '444-555-6666', 'Staff', TRUE), +('Michael', 'Johnson', 'michael@petshop.com', '222-333-4444', 'Groomer', TRUE), +('Lisa', 'Williams', 'lisa@petshop.com', '333-444-5555', 'Staff', TRUE), +('David', 'Brown', 'david@petshop.com', '555-666-7777', 'Veterinarian', TRUE), +('Emma', 'Davis', 'emma@petshop.com', '666-777-8888', 'Manager', FALSE); + +INSERT INTO employeeStore (employeeId, storeId) +VALUES +(1, 1), +(2, 1), +(2, 2), +(3, 2), +(4, 3), +(5, 1), +(5, 4), +(6, 5); + +INSERT INTO customer (firstName, lastName, email, phone) +VALUES +('Alex', 'Brown', 'alex@gmail.com', '777-888-9999'), +('Emily', 'Clark', 'emily@gmail.com', '666-555-4444'), +('James', 'Wilson', 'james@gmail.com', '888-999-0000'), +('Olivia', 'Martinez', 'olivia@gmail.com', '999-000-1111'), +('William', 'Anderson', 'william@gmail.com', '000-111-2222'), +('Sophia', 'Taylor', 'sophia@gmail.com', '111-222-3333'); + +INSERT INTO pet (petName, petSpecies, petBreed, petAge, petStatus, petPrice) +VALUES +('Buddy', 'Dog', 'Labrador', 2, 'Available', 500.00), +('Milo', 'Cat', 'Persian', 1, 'Available', 300.00), +('Charlie', 'Dog', 'Golden Retriever', 3, 'Available', 550.00), +('Luna', 'Cat', 'Siamese', 2, 'Adopted', 350.00), +('Max', 'Dog', 'Beagle', 1, 'Available', 450.00), +('Bella', 'Cat', 'Maine Coon', 4, 'Available', 400.00); + +INSERT INTO adoption (petId, customerId, adoptionDate, adoptionStatus) +VALUES +(1, 1, '2026-01-15', 'Completed'), +(4, 3, '2026-01-20', 'Completed'), +(2, 2, '2026-01-25', 'Pending'), +(5, 4, '2026-02-01', 'Completed'), +(6, 5, '2026-02-02', 'Pending'); + +INSERT INTO supplier (supCompany, supContactFirstName, supContactLastName, supEmail, supPhone) +VALUES +('PetFood Inc', 'Robert', 'King', 'contact@petfood.com', '888-111-2222'), +('Toy World', 'Jennifer', 'Lee', 'sales@toyworld.com', '888-222-3333'), +('Pet Supplies Co', 'Kevin', 'White', 'info@petsupplies.com', '888-333-4444'), +('Animal Care Products', 'Nancy', 'Green', 'orders@animalcare.com', '888-444-5555'), +('Premium Pet Goods', 'Tom', 'Black', 'support@premiumpet.com', '888-555-6666'); + +INSERT INTO category (categoryName, categoryType) +VALUES +('Dog Food', 'Product'), +('Cat Toys', 'Product'), +('Bird Supplies', 'Product'), +('Aquarium', 'Product'), +('Small Animals', 'Product'); + +INSERT INTO product (prodName, prodPrice, categoryId, prodDesc) +VALUES +('Premium Dog Food', 50.00, 1, 'High quality dog food'), +('Cat Toy Ball', 10.00, 2, 'Colorful toy for cats'), +('Bird Cage Large', 120.00, 3, 'Spacious bird cage'), +('Fish Tank 20 Gallon', 80.00, 4, 'Complete aquarium kit'), +('Hamster Wheel', 15.00, 5, 'Exercise wheel for small pets'), +('Organic Dog Treats', 25.00, 1, 'Natural dog treats'); + +INSERT INTO productSupplier (supId, prodId, cost) +VALUES +(1, 1, 35.00), +(1, 2, 6.50), +(2, 2, 7.00), +(3, 3, 90.00), +(3, 4, 60.00), +(4, 5, 10.00), +(5, 6, 18.00), +(1, 6, 17.50); + +INSERT INTO inventory (prodId, quantity) +VALUES +(1, 100), +(2, 200), +(3, 50), +(4, 30), +(5, 150), +(6, 75); + +INSERT INTO service (serviceName, serviceDesc, serviceDuration, servicePrice) +VALUES +('Pet Grooming', 'Full grooming service', 60, 40.00), +('Nail Trimming', 'Quick nail trim', 15, 10.00), +('Bath and Brush', 'Bathing and brushing service', 45, 30.00), +('Veterinary Checkup', 'Complete health examination', 30, 75.00), +('Teeth Cleaning', 'Professional dental cleaning', 90, 100.00); + +INSERT INTO appointment (serviceId, customerId, appointmentDate, appointmentTime, appointmentStatus) +VALUES +(1, 2, '2026-02-01', '10:30:00', 'Booked'), +(2, 1, '2026-02-03', '14:00:00', 'Booked'), +(3, 3, '2026-02-05', '09:00:00', 'Completed'), +(4, 4, '2026-02-07', '11:30:00', 'Booked'), +(5, 5, '2026-02-10', '15:00:00', 'Cancelled'); + +INSERT INTO appointmentPet (appointmentId, petId) +VALUES +(1, 2), +(2, 1), +(3, 3), +(4, 5), +(5, 6); + +INSERT INTO sale (saleDate, totalAmount, paymentMethod, employeeId, storeId, customerId) +VALUES +('2026-01-05 09:15:00', 125.00, 'Card', 1, 1, 1), +('2026-01-08 11:30:00', 200.00, 'Card', 2, 1, 2), +('2026-01-12 14:20:00', 60.00, 'Cash', 3, 2, 3), +('2026-01-15 10:45:00', 150.00, 'Debit', 1, 1, 1), +('2026-01-18 16:30:00', 80.00, 'Card', 4, 3, 2), +('2026-01-22 13:15:00', 95.00, 'Cash', 2, 2, NULL), +('2026-01-25 15:40:00', 240.00, 'Card', 5, 4, 4), +('2026-01-28 10:30:00', 80.00, 'Cash', 1, 1, NULL), +('2026-02-01 09:00:00', 175.00, 'Card', 3, 3, 1), +('2026-02-03 11:20:00', 120.00, 'Card', 2, 1, 3), +('2026-02-05 14:50:00', 45.00, 'Cash', 4, 2, NULL), +('2026-02-08 16:15:00', 160.00, 'Debit', 1, 1, 2), +('2026-02-10 10:25:00', 100.00, 'Card', 5, 4, NULL), +('2026-02-12 13:45:00', 50.00, 'Cash', 2, 2, 1), +('2026-02-15 15:30:00', 85.00, 'Card', 3, 3, NULL), +('2026-02-18 11:10:00', 200.00, 'Card', 1, 1, 4), +('2026-02-20 14:35:00', 155.00, 'Debit', 4, 3, NULL), +('2026-02-22 16:50:00', 75.00, 'Cash', 2, 1, 2), +('2026-02-24 10:15:00', 140.00, 'Card', 5, 4, NULL), +(NOW(), 95.00, 'Card', 1, 1, 1); + +INSERT INTO saleItem (saleId, prodId, quantity, unitPrice) +VALUES +(1, 1, 2, 50.00), +(1, 6, 1, 25.00), +(2, 3, 1, 120.00), +(2, 4, 1, 80.00), +(3, 2, 3, 10.00), +(3, 5, 2, 15.00), +(4, 1, 3, 50.00), +(5, 4, 1, 80.00), +(6, 2, 4, 10.00), +(6, 5, 1, 15.00), +(6, 6, 1, 25.00), +(6, 1, 1, 50.00), +(7, 3, 2, 120.00), +(8, 1, 1, 50.00), +(8, 2, 3, 10.00), +(9, 1, 3, 50.00), +(9, 6, 1, 25.00), +(10, 3, 1, 120.00), +(11, 5, 1, 15.00), +(11, 2, 3, 10.00), +(12, 4, 2, 80.00), +(13, 6, 4, 25.00), +(14, 1, 1, 50.00), +(15, 2, 2, 10.00), +(15, 5, 1, 15.00), +(15, 6, 2, 25.00), +(16, 3, 1, 120.00), +(16, 4, 1, 80.00), +(17, 4, 1, 80.00), +(17, 1, 1, 50.00), +(17, 6, 1, 25.00), +(18, 6, 2, 25.00), +(18, 2, 2, 10.00), +(18, 5, 1, 15.00), +(19, 1, 2, 50.00), +(19, 6, 2, 25.00), +(20, 2, 5, 10.00), +(20, 5, 3, 15.00); + +INSERT INTO purchaseOrder (supId, orderDate, status) +VALUES +(1, '2025-01-15', 'Delivered'), +(2, '2025-01-20', 'Pending'), +(3, '2025-02-01', 'Delivered'), +(4, '2025-02-10', 'In Transit'), +(1, '2025-02-15', 'Pending'); + +INSERT INTO activityLog (employeeId, activity) +VALUES +(1, 'Created new sale'), +(2, 'Booked appointment'), +(3, 'Completed grooming service'), +(4, 'Processed inventory order'), +(5, 'Conducted health checkup'), +(1, 'Updated customer information'); From c56fb9ab00389d8ed12007bce6c0a1b94cc96941 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Tue, 10 Mar 2026 16:05:05 -0600 Subject: [PATCH 50/84] Backend fixes --- .../controller/AdoptionController.java | 13 ++++++++++++ .../controller/AppointmentController.java | 13 ++++++++++++ .../exception/GlobalExceptionHandler.java | 20 +++++++++++++++++++ 3 files changed, 46 insertions(+) diff --git a/src/main/java/com/petshop/backend/controller/AdoptionController.java b/src/main/java/com/petshop/backend/controller/AdoptionController.java index 17790070..a3f67002 100644 --- a/src/main/java/com/petshop/backend/controller/AdoptionController.java +++ b/src/main/java/com/petshop/backend/controller/AdoptionController.java @@ -73,6 +73,19 @@ public class AdoptionController { @PostMapping @PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')") public ResponseEntity createAdoption(@Valid @RequestBody AdoptionRequest request) { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String role = authentication.getAuthorities().stream() + .findFirst() + .map(authority -> authority.getAuthority().replace("ROLE_", "")) + .orElse(null); + + if (role != null && role.equals("CUSTOMER")) { + Customer customer = AuthenticationHelper.getAuthenticatedCustomer(userRepository, customerRepository); + if (!request.getCustomerId().equals(customer.getCustomerId())) { + throw new org.springframework.security.access.AccessDeniedException("You can only create adoptions for yourself"); + } + } + return ResponseEntity.status(HttpStatus.CREATED).body(adoptionService.createAdoption(request)); } diff --git a/src/main/java/com/petshop/backend/controller/AppointmentController.java b/src/main/java/com/petshop/backend/controller/AppointmentController.java index 20e0c83d..35246e05 100644 --- a/src/main/java/com/petshop/backend/controller/AppointmentController.java +++ b/src/main/java/com/petshop/backend/controller/AppointmentController.java @@ -76,6 +76,19 @@ public class AppointmentController { @PostMapping @PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')") public ResponseEntity createAppointment(@Valid @RequestBody AppointmentRequest request) { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String role = authentication.getAuthorities().stream() + .findFirst() + .map(authority -> authority.getAuthority().replace("ROLE_", "")) + .orElse(null); + + if (role != null && role.equals("CUSTOMER")) { + Customer customer = AuthenticationHelper.getAuthenticatedCustomer(userRepository, customerRepository); + if (!request.getCustomerId().equals(customer.getCustomerId())) { + throw new org.springframework.security.access.AccessDeniedException("You can only create appointments for yourself"); + } + } + return ResponseEntity.status(HttpStatus.CREATED).body(appointmentService.createAppointment(request)); } diff --git a/src/main/java/com/petshop/backend/exception/GlobalExceptionHandler.java b/src/main/java/com/petshop/backend/exception/GlobalExceptionHandler.java index 290752dd..40e95bbc 100644 --- a/src/main/java/com/petshop/backend/exception/GlobalExceptionHandler.java +++ b/src/main/java/com/petshop/backend/exception/GlobalExceptionHandler.java @@ -51,6 +51,26 @@ public class GlobalExceptionHandler { return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response); } + @ExceptionHandler(org.springframework.security.access.AccessDeniedException.class) + public ResponseEntity handleAccessDeniedException(org.springframework.security.access.AccessDeniedException ex) { + ErrorResponse error = new ErrorResponse( + HttpStatus.FORBIDDEN.value(), + ex.getMessage(), + LocalDateTime.now() + ); + return ResponseEntity.status(HttpStatus.FORBIDDEN).body(error); + } + + @ExceptionHandler(IllegalArgumentException.class) + public ResponseEntity handleIllegalArgumentException(IllegalArgumentException ex) { + ErrorResponse error = new ErrorResponse( + HttpStatus.BAD_REQUEST.value(), + ex.getMessage(), + LocalDateTime.now() + ); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error); + } + @ExceptionHandler(Exception.class) public ResponseEntity handleGenericException(Exception ex) { ErrorResponse error = new ErrorResponse( From fad80e436f93d95742db06296cd292b6f6cf3400 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Tue, 10 Mar 2026 16:43:46 -0600 Subject: [PATCH 51/84] Backend correctness fixes --- Petstoredata.sql | 394 ---------------- sql/01_petstore_init.sql | 436 ------------------ .../backend/controller/AuthController.java | 5 + .../backend/controller/ChatController.java | 2 +- .../controller/DropdownController.java | 1 + .../dto/appointment/AppointmentRequest.java | 15 +- .../dto/appointment/AppointmentResponse.java | 28 +- .../petshop/backend/entity/Appointment.java | 16 +- .../petshop/backend/entity/Conversation.java | 2 +- .../com/petshop/backend/entity/Refund.java | 2 +- .../repository/AppointmentRepository.java | 4 +- .../repository/EmployeeStoreRepository.java | 12 + .../backend/repository/SaleRepository.java | 4 + .../security/JwtAuthenticationFilter.java | 14 +- .../backend/service/AppointmentService.java | 87 +++- .../petshop/backend/service/ChatService.java | 16 + .../petshop/backend/service/SaleService.java | 26 +- src/main/resources/application.yml | 2 +- src/main/resources/data.sql | 205 -------- ...t_store_and_employee_store_constraints.sql | 19 + src/main/resources/schema.sql | 250 ---------- 21 files changed, 235 insertions(+), 1305 deletions(-) delete mode 100644 Petstoredata.sql delete mode 100644 sql/01_petstore_init.sql create mode 100644 src/main/java/com/petshop/backend/repository/EmployeeStoreRepository.java delete mode 100644 src/main/resources/data.sql create mode 100644 src/main/resources/db/migration/V3__appointment_store_and_employee_store_constraints.sql delete mode 100644 src/main/resources/schema.sql diff --git a/Petstoredata.sql b/Petstoredata.sql deleted file mode 100644 index 8c7c1576..00000000 --- a/Petstoredata.sql +++ /dev/null @@ -1,394 +0,0 @@ -DROP DATABASE IF EXISTS Petstoredb; -CREATE DATABASE Petstoredb; -USE Petstoredb; - --- Create Tables - -CREATE TABLE storeLocation ( - storeId INT AUTO_INCREMENT PRIMARY KEY, - storeName VARCHAR(100) NOT NULL, - address VARCHAR(255) NOT NULL, - phone VARCHAR(20) NOT NULL, - email VARCHAR(100) NOT NULL -); - -CREATE TABLE employee ( - employeeId INT AUTO_INCREMENT PRIMARY KEY, - firstName VARCHAR(50) NOT NULL, - lastName VARCHAR(50) NOT NULL, - email VARCHAR(100) NOT NULL, - phone VARCHAR(20) NOT NULL, - role VARCHAR(50) NOT NULL, - isActive BOOLEAN DEFAULT TRUE NOT NULL -); - -CREATE TABLE employeeStore ( - employeeId INT NOT NULL, - storeId INT NOT NULL, - PRIMARY KEY (employeeId, storeId), - FOREIGN KEY (employeeId) REFERENCES employee(employeeId), - FOREIGN KEY (storeId) REFERENCES storeLocation(storeId) -); - -CREATE TABLE customer ( - customerId INT AUTO_INCREMENT PRIMARY KEY, - firstName VARCHAR(50) NOT NULL, - lastName VARCHAR(50) NOT NULL, - email VARCHAR(100) NOT NULL, - phone VARCHAR(20) NOT NULL -); - -CREATE TABLE pet ( - petId INT AUTO_INCREMENT PRIMARY KEY, - petName VARCHAR(50) NOT NULL, - petSpecies VARCHAR(50) NOT NULL, - petBreed VARCHAR(50) NOT NULL, - petAge INT NOT NULL, - petStatus VARCHAR(20) NOT NULL, - petPrice DECIMAL(10, 2) NOT NULL -); - -CREATE TABLE adoption ( - adoptionId INT AUTO_INCREMENT PRIMARY KEY, - petId INT NOT NULL, - customerId INT NOT NULL, - adoptionDate DATE NOT NULL, - adoptionStatus VARCHAR(20) NOT NULL, - FOREIGN KEY (petId) REFERENCES pet(petId), - FOREIGN KEY (customerId) REFERENCES customer(customerId) -); - -CREATE TABLE supplier ( - supId INT AUTO_INCREMENT PRIMARY KEY, - supCompany VARCHAR(100) NOT NULL, - supContactFirstName VARCHAR(50) NOT NULL, - supContactLastName VARCHAR(50) NOT NULL, - supEmail VARCHAR(100) NOT NULL, - supPhone VARCHAR(20) NOT NULL -); - -CREATE TABLE category ( - categoryId INT AUTO_INCREMENT PRIMARY KEY, - categoryName VARCHAR(100) NOT NULL, - categoryType VARCHAR(50) NOT NULL -); - -CREATE TABLE product ( - prodId INT AUTO_INCREMENT PRIMARY KEY, - prodName VARCHAR(100) NOT NULL, - prodPrice DECIMAL(10, 2) NOT NULL, - categoryId INT NOT NULL, - prodDesc TEXT, - FOREIGN KEY (categoryId) REFERENCES category(categoryId) -); - -CREATE TABLE productSupplier ( - supId INT NOT NULL, - prodId INT NOT NULL, - cost DECIMAL(10, 2) NOT NULL, - PRIMARY KEY (supId, prodId), - FOREIGN KEY (supId) REFERENCES supplier(supId), - FOREIGN KEY (prodId) REFERENCES product(prodId) -); - -CREATE TABLE inventory ( - inventoryId INT AUTO_INCREMENT PRIMARY KEY, - prodId INT NOT NULL, - quantity INT DEFAULT 0 NOT NULL, - FOREIGN KEY (prodId) REFERENCES product(prodId) -); - -CREATE TABLE service ( - serviceId INT AUTO_INCREMENT PRIMARY KEY, - serviceName VARCHAR(100) NOT NULL, - serviceDesc TEXT, - serviceDuration INT NOT NULL, - servicePrice DECIMAL(10, 2) NOT NULL -); - -CREATE TABLE appointment ( - appointmentId INT AUTO_INCREMENT PRIMARY KEY, - serviceId INT NOT NULL, - customerId INT NOT NULL, - appointmentDate DATE NOT NULL, - appointmentTime TIME NOT NULL, - appointmentStatus VARCHAR(20) NOT NULL, - FOREIGN KEY (serviceId) REFERENCES service(serviceId), - FOREIGN KEY (customerId) REFERENCES customer(customerId) -); - -CREATE TABLE appointmentPet ( - appointmentId INT NOT NULL, - petId INT NOT NULL, - PRIMARY KEY (appointmentId, petId), - FOREIGN KEY (appointmentId) REFERENCES appointment(appointmentId), - FOREIGN KEY (petId) REFERENCES pet(petId) -); - -CREATE TABLE sale ( - saleId INT AUTO_INCREMENT PRIMARY KEY, - saleDate DATETIME NOT NULL, - totalAmount DECIMAL(10, 2) NOT NULL, - paymentMethod VARCHAR(50) NOT NULL, - employeeId INT NOT NULL, - storeId INT NOT NULL, - isRefund BOOLEAN DEFAULT FALSE NOT NULL, - originalSaleId INT NULL, - FOREIGN KEY (employeeId) REFERENCES employee(employeeId), - FOREIGN KEY (storeId) REFERENCES storeLocation(storeId), - FOREIGN KEY (originalSaleId) REFERENCES sale(saleId) -); - -CREATE TABLE saleItem ( - saleItemId INT AUTO_INCREMENT PRIMARY KEY, - saleId INT NOT NULL, - prodId INT NOT NULL, - quantity INT NOT NULL, - unitPrice DECIMAL(10, 2) NOT NULL, - FOREIGN KEY (saleId) REFERENCES sale(saleId), - FOREIGN KEY (prodId) REFERENCES product(prodId) -); - -CREATE TABLE purchaseOrder ( - purchaseOrderId INT AUTO_INCREMENT PRIMARY KEY, - supId INT NOT NULL, - orderDate DATE NOT NULL, - status VARCHAR(50) NOT NULL, - FOREIGN KEY (supId) REFERENCES supplier(supId) -); - -CREATE TABLE activityLog ( - logId INT AUTO_INCREMENT PRIMARY KEY, - employeeId INT NOT NULL, - activity TEXT NOT NULL, - logTimestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, - FOREIGN KEY (employeeId) REFERENCES employee(employeeId) -); - --- Insert Sample Data - -INSERT INTO storeLocation (storeName, address, phone, email) -VALUES -('Downtown Branch', '123 Main St', '123-456-7890', 'downtown@petshop.com'), -('North Branch', '456 North Ave', '987-654-3210', 'north@petshop.com'), -('West Side Store', '789 West Blvd', '555-123-4567', 'westside@petshop.com'), -('East End Shop', '321 East Road', '555-987-6543', 'eastend@petshop.com'), -('South Mall Location', '654 South Plaza', '555-246-8135', 'southmall@petshop.com'); - -INSERT INTO employee (firstName, lastName, email, phone, role, isActive) -VALUES -('John', 'Doe', 'john@petshop.com', '111-222-3333', 'Manager', TRUE), -('Sara', 'Smith', 'sara@petshop.com', '444-555-6666', 'Staff', TRUE), -('Michael', 'Johnson', 'michael@petshop.com', '222-333-4444', 'Groomer', TRUE), -('Lisa', 'Williams', 'lisa@petshop.com', '333-444-5555', 'Staff', TRUE), -('David', 'Brown', 'david@petshop.com', '555-666-7777', 'Veterinarian', TRUE), -('Emma', 'Davis', 'emma@petshop.com', '666-777-8888', 'Manager', FALSE); - -INSERT INTO employeeStore (employeeId, storeId) -VALUES -(1, 1), -(2, 1), -(2, 2), -(3, 2), -(4, 3), -(5, 1), -(5, 4), -(6, 5); - -INSERT INTO customer (firstName, lastName, email, phone) -VALUES -('Alex', 'Brown', 'alex@gmail.com', '777-888-9999'), -('Emily', 'Clark', 'emily@gmail.com', '666-555-4444'), -('James', 'Wilson', 'james@gmail.com', '888-999-0000'), -('Olivia', 'Martinez', 'olivia@gmail.com', '999-000-1111'), -('William', 'Anderson', 'william@gmail.com', '000-111-2222'), -('Sophia', 'Taylor', 'sophia@gmail.com', '111-222-3333'); - -INSERT INTO pet (petName, petSpecies, petBreed, petAge, petStatus, petPrice) -VALUES -('Buddy', 'Dog', 'Labrador', 2, 'Available', 500.00), -('Milo', 'Cat', 'Persian', 1, 'Available', 300.00), -('Charlie', 'Dog', 'Golden Retriever', 3, 'Available', 550.00), -('Luna', 'Cat', 'Siamese', 2, 'Adopted', 350.00), -('Max', 'Dog', 'Beagle', 1, 'Available', 450.00), -('Bella', 'Cat', 'Maine Coon', 4, 'Available', 400.00); - -INSERT INTO adoption (petId, customerId, adoptionDate, adoptionStatus) -VALUES -(1, 1, '2026-01-15', 'Completed'), -(4, 3, '2026-01-20', 'Completed'), -(2, 2, '2026-01-25', 'Pending'), -(5, 4, '2026-02-01', 'Completed'), -(6, 5, '2026-02-02', 'Pending'); - -INSERT INTO supplier (supCompany, supContactFirstName, supContactLastName, supEmail, supPhone) -VALUES -('PetFood Inc', 'Robert', 'King', 'contact@petfood.com', '888-111-2222'), -('Toy World', 'Jennifer', 'Lee', 'sales@toyworld.com', '888-222-3333'), -('Pet Supplies Co', 'Kevin', 'White', 'info@petsupplies.com', '888-333-4444'), -('Animal Care Products', 'Nancy', 'Green', 'orders@animalcare.com', '888-444-5555'), -('Premium Pet Goods', 'Tom', 'Black', 'support@premiumpet.com', '888-555-6666'); - -INSERT INTO category (categoryName, categoryType) -VALUES -('Dog Food', 'Product'), -('Cat Toys', 'Product'), -('Bird Supplies', 'Product'), -('Aquarium', 'Product'), -('Small Animals', 'Product'); - -INSERT INTO product (prodName, prodPrice, categoryId, prodDesc) -VALUES -('Premium Dog Food', 50.00, 1, 'High quality dog food'), -('Cat Toy Ball', 10.00, 2, 'Colorful toy for cats'), -('Bird Cage Large', 120.00, 3, 'Spacious bird cage'), -('Fish Tank 20 Gallon', 80.00, 4, 'Complete aquarium kit'), -('Hamster Wheel', 15.00, 5, 'Exercise wheel for small pets'), -('Organic Dog Treats', 25.00, 1, 'Natural dog treats'); - -INSERT INTO productSupplier (supId, prodId, cost) -VALUES -(1, 1, 35.00), -(1, 2, 6.50), -(2, 2, 7.00), -(3, 3, 90.00), -(3, 4, 60.00), -(4, 5, 10.00), -(5, 6, 18.00), -(1, 6, 17.50); - -INSERT INTO inventory (prodId, quantity) -VALUES -(1, 100), -(2, 200), -(3, 50), -(4, 30), -(5, 150), -(6, 75); - -INSERT INTO service (serviceName, serviceDesc, serviceDuration, servicePrice) -VALUES -('Pet Grooming', 'Full grooming service', 60, 40.00), -('Nail Trimming', 'Quick nail trim', 15, 10.00), -('Bath and Brush', 'Bathing and brushing service', 45, 30.00), -('Veterinary Checkup', 'Complete health examination', 30, 75.00), -('Teeth Cleaning', 'Professional dental cleaning', 90, 100.00); - -INSERT INTO appointment (serviceId, customerId, appointmentDate, appointmentTime, appointmentStatus) -VALUES -(1, 2, '2026-02-01', '10:30:00', 'Booked'), -(2, 1, '2026-02-03', '14:00:00', 'Booked'), -(3, 3, '2026-02-05', '09:00:00', 'Completed'), -(4, 4, '2026-02-07', '11:30:00', 'Booked'), -(5, 5, '2026-02-10', '15:00:00', 'Cancelled'); - -INSERT INTO appointmentPet (appointmentId, petId) -VALUES -(1, 2), -(2, 1), -(3, 3), -(4, 5), -(5, 6); - -INSERT INTO sale (saleDate, totalAmount, paymentMethod, employeeId, storeId) -VALUES --- January Sales -('2026-01-05 09:15:00', 125.00, 'Card', 1, 1), -- Sale 1: Dog food + treats -('2026-01-08 11:30:00', 200.00, 'Card', 2, 1), -- Sale 2: Bird cage + fish tank -('2026-01-12 14:20:00', 60.00, 'Cash', 3, 2), -- Sale 3: Cat toys + hamster wheel -('2026-01-15 10:45:00', 150.00, 'Debit', 1, 1), -- Sale 4: Dog food (bulk purchase) -('2026-01-18 16:30:00', 80.00, 'Card', 4, 3), -- Sale 5: Fish tank -('2026-01-22 13:15:00', 95.00, 'Cash', 2, 2), -- Sale 6: Mixed items -('2026-01-25 15:40:00', 240.00, 'Card', 5, 4), -- Sale 7: Two bird cages -('2026-01-28 10:30:00', 80.00, 'Cash', 1, 1), -- Sale 8: Dog food + cat toys --- February Sales -('2026-02-01 09:00:00', 175.00, 'Card', 3, 3), -- Sale 9: Dog food + treats (bulk) -('2026-02-03 11:20:00', 120.00, 'Card', 2, 1), -- Sale 10: Bird cage -('2026-02-05 14:50:00', 45.00, 'Cash', 4, 2), -- Sale 11: Hamster wheel + cat toys -('2026-02-08 16:15:00', 160.00, 'Debit', 1, 1), -- Sale 12: Fish tank + accessories -('2026-02-10 10:25:00', 100.00, 'Card', 5, 4), -- Sale 13: Dog treats (bulk) -('2026-02-12 13:45:00', 50.00, 'Cash', 2, 2), -- Sale 14: Dog food -('2026-02-15 15:30:00', 85.00, 'Card', 3, 3), -- Sale 15: Mixed pet supplies -('2026-02-18 11:10:00', 200.00, 'Card', 1, 1), -- Sale 16: Bird cage + hamster wheel -('2026-02-20 14:35:00', 155.00, 'Debit', 4, 3), -- Sale 17: Fish tank + cat toys -('2026-02-22 16:50:00', 75.00, 'Cash', 2, 1), -- Sale 18: Dog treats + toys -('2026-02-24 10:15:00', 140.00, 'Card', 5, 4), -- Sale 19: Dog food + treats -(NOW(), 95.00, 'Card', 1, 1); -- Sale 20: Recent sale (current timestamp) - -INSERT INTO saleItem (saleId, prodId, quantity, unitPrice) -VALUES --- Sale 1 items (Dog food + treats) -(1, 1, 2, 50.00), -- 2x Premium Dog Food -(1, 6, 1, 25.00), -- 1x Organic Dog Treats --- Sale 2 items (Bird cage + fish tank) -(2, 3, 1, 120.00), -- 1x Bird Cage Large -(2, 4, 1, 80.00), -- 1x Fish Tank 20 Gallon --- Sale 3 items (Cat toys + hamster wheel) -(3, 2, 3, 10.00), -- 3x Cat Toy Ball -(3, 5, 2, 15.00), -- 2x Hamster Wheel --- Sale 4 items (Dog food bulk) -(4, 1, 3, 50.00), -- 3x Premium Dog Food --- Sale 5 items (Fish tank) -(5, 4, 1, 80.00), -- 1x Fish Tank 20 Gallon --- Sale 6 items (Mixed) -(6, 2, 4, 10.00), -- 4x Cat Toy Ball -(6, 5, 1, 15.00), -- 1x Hamster Wheel -(6, 6, 1, 25.00), -- 1x Organic Dog Treats -(6, 1, 1, 50.00), -- 1x Premium Dog Food (partial - discount applied) --- Sale 7 items (Two bird cages) -(7, 3, 2, 120.00), -- 2x Bird Cage Large --- Sale 8 items (Dog food + cat toys) -(8, 1, 1, 50.00), -- 1x Premium Dog Food -(8, 2, 3, 10.00), -- 3x Cat Toy Ball --- Sale 9 items (Dog food + treats bulk) -(9, 1, 3, 50.00), -- 3x Premium Dog Food -(9, 6, 1, 25.00), -- 1x Organic Dog Treats --- Sale 10 items (Bird cage) -(10, 3, 1, 120.00), -- 1x Bird Cage Large --- Sale 11 items (Hamster wheel + cat toys) -(11, 5, 1, 15.00), -- 1x Hamster Wheel -(11, 2, 3, 10.00), -- 3x Cat Toy Ball --- Sale 12 items (Fish tank + accessories) -(12, 4, 2, 80.00), -- 2x Fish Tank 20 Gallon --- Sale 13 items (Dog treats bulk) -(13, 6, 4, 25.00), -- 4x Organic Dog Treats --- Sale 14 items (Dog food) -(14, 1, 1, 50.00), -- 1x Premium Dog Food --- Sale 15 items (Mixed supplies) -(15, 2, 2, 10.00), -- 2x Cat Toy Ball -(15, 5, 1, 15.00), -- 1x Hamster Wheel -(15, 6, 2, 25.00), -- 2x Organic Dog Treats --- Sale 16 items (Bird cage + hamster wheel) -(16, 3, 1, 120.00), -- 1x Bird Cage Large -(16, 4, 1, 80.00), -- 1x Fish Tank 20 Gallon --- Sale 17 items (Fish tank + cat toys) -(17, 4, 1, 80.00), -- 1x Fish Tank 20 Gallon -(17, 1, 1, 50.00), -- 1x Premium Dog Food -(17, 6, 1, 25.00), -- 1x Organic Dog Treats --- Sale 18 items (Dog treats + toys) -(18, 6, 2, 25.00), -- 2x Organic Dog Treats -(18, 2, 2, 10.00), -- 2x Cat Toy Ball -(18, 5, 1, 15.00), -- 1x Hamster Wheel --- Sale 19 items (Dog food + treats) -(19, 1, 2, 50.00), -- 2x Premium Dog Food -(19, 6, 2, 25.00), -- 2x Organic Dog Treats (discount applied) --- Sale 20 items (Recent sale) -(20, 2, 5, 10.00), -- 5x Cat Toy Ball -(20, 5, 3, 15.00); -- 3x Hamster Wheel - -INSERT INTO purchaseOrder (supId, orderDate, status) -VALUES -(1, '2025-01-15', 'Delivered'), -(2, '2025-01-20', 'Pending'), -(3, '2025-02-01', 'Delivered'), -(4, '2025-02-10', 'In Transit'), -(1, '2025-02-15', 'Pending'); - -INSERT INTO activityLog (employeeId, activity) -VALUES -(1, 'Created new sale'), -(2, 'Booked appointment'), -(3, 'Completed grooming service'), -(4, 'Processed inventory order'), -(5, 'Conducted health checkup'), -(1, 'Updated customer information'); \ No newline at end of file diff --git a/sql/01_petstore_init.sql b/sql/01_petstore_init.sql deleted file mode 100644 index c8e8dbea..00000000 --- a/sql/01_petstore_init.sql +++ /dev/null @@ -1,436 +0,0 @@ -DROP DATABASE IF EXISTS Petstoredb; -CREATE DATABASE Petstoredb; -USE Petstoredb; - --- Create Tables - -CREATE TABLE storeLocation ( - storeId BIGINT AUTO_INCREMENT PRIMARY KEY, - storeName VARCHAR(100) NOT NULL, - address VARCHAR(255) NOT NULL, - phone VARCHAR(20) NOT NULL, - email VARCHAR(100) NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP -); - -CREATE TABLE employee ( - employeeId BIGINT AUTO_INCREMENT PRIMARY KEY, - firstName VARCHAR(50) NOT NULL, - lastName VARCHAR(50) NOT NULL, - email VARCHAR(100) NOT NULL, - phone VARCHAR(20) NOT NULL, - role VARCHAR(50) NOT NULL, - isActive BOOLEAN DEFAULT TRUE NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP -); - -CREATE TABLE employeeStore ( - employeeId BIGINT NOT NULL, - storeId BIGINT NOT NULL, - PRIMARY KEY (employeeId, storeId), - FOREIGN KEY (employeeId) REFERENCES employee(employeeId), - FOREIGN KEY (storeId) REFERENCES storeLocation(storeId) -); - -CREATE TABLE customer ( - customerId BIGINT AUTO_INCREMENT PRIMARY KEY, - firstName VARCHAR(50) NOT NULL, - lastName VARCHAR(50) NOT NULL, - email VARCHAR(100) NOT NULL, - phone VARCHAR(20) NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP -); - -CREATE TABLE pet ( - petId BIGINT AUTO_INCREMENT PRIMARY KEY, - petName VARCHAR(50) NOT NULL, - petSpecies VARCHAR(50) NOT NULL, - petBreed VARCHAR(50) NOT NULL, - petAge INT NOT NULL, - petStatus VARCHAR(20) NOT NULL, - petPrice DECIMAL(10, 2) NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP -); - -CREATE TABLE adoption ( - adoptionId BIGINT AUTO_INCREMENT PRIMARY KEY, - petId BIGINT NOT NULL, - customerId BIGINT NOT NULL, - adoptionDate DATE NOT NULL, - adoptionStatus VARCHAR(20) NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - FOREIGN KEY (petId) REFERENCES pet(petId), - FOREIGN KEY (customerId) REFERENCES customer(customerId) -); - -CREATE TABLE supplier ( - supId BIGINT AUTO_INCREMENT PRIMARY KEY, - supCompany VARCHAR(100) NOT NULL, - supContactFirstName VARCHAR(50) NOT NULL, - supContactLastName VARCHAR(50) NOT NULL, - supEmail VARCHAR(100) NOT NULL, - supPhone VARCHAR(20) NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP -); - -CREATE TABLE category ( - categoryId BIGINT AUTO_INCREMENT PRIMARY KEY, - categoryName VARCHAR(100) NOT NULL, - categoryType VARCHAR(50) NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP -); - -CREATE TABLE product ( - prodId BIGINT AUTO_INCREMENT PRIMARY KEY, - prodName VARCHAR(100) NOT NULL, - prodPrice DECIMAL(10, 2) NOT NULL, - categoryId BIGINT NOT NULL, - prodDesc TEXT, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - FOREIGN KEY (categoryId) REFERENCES category(categoryId) -); - -CREATE TABLE productSupplier ( - supId BIGINT NOT NULL, - prodId BIGINT NOT NULL, - cost DECIMAL(10, 2) NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - PRIMARY KEY (supId, prodId), - FOREIGN KEY (supId) REFERENCES supplier(supId), - FOREIGN KEY (prodId) REFERENCES product(prodId) -); - -CREATE TABLE inventory ( - inventoryId BIGINT AUTO_INCREMENT PRIMARY KEY, - prodId BIGINT NOT NULL, - quantity INT DEFAULT 0 NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - FOREIGN KEY (prodId) REFERENCES product(prodId) -); - -CREATE TABLE service ( - serviceId BIGINT AUTO_INCREMENT PRIMARY KEY, - serviceName VARCHAR(100) NOT NULL, - serviceDesc TEXT, - serviceDuration INT NOT NULL, - servicePrice DECIMAL(10, 2) NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP -); - -CREATE TABLE appointment ( - appointmentId BIGINT AUTO_INCREMENT PRIMARY KEY, - serviceId BIGINT NOT NULL, - customerId BIGINT NOT NULL, - appointmentDate DATE NOT NULL, - appointmentTime TIME NOT NULL, - appointmentStatus VARCHAR(20) NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - FOREIGN KEY (serviceId) REFERENCES service(serviceId), - FOREIGN KEY (customerId) REFERENCES customer(customerId) -); - -CREATE TABLE appointmentPet ( - appointmentId BIGINT NOT NULL, - petId BIGINT NOT NULL, - PRIMARY KEY (appointmentId, petId), - FOREIGN KEY (appointmentId) REFERENCES appointment(appointmentId), - FOREIGN KEY (petId) REFERENCES pet(petId) -); - -CREATE TABLE sale ( - saleId BIGINT AUTO_INCREMENT PRIMARY KEY, - saleDate DATETIME NOT NULL, - totalAmount DECIMAL(10, 2) NOT NULL, - paymentMethod VARCHAR(50) NOT NULL, - employeeId BIGINT NOT NULL, - storeId BIGINT NOT NULL, - isRefund BOOLEAN DEFAULT FALSE NOT NULL, - originalSaleId BIGINT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - FOREIGN KEY (employeeId) REFERENCES employee(employeeId), - FOREIGN KEY (storeId) REFERENCES storeLocation(storeId), - FOREIGN KEY (originalSaleId) REFERENCES sale(saleId) -); - -CREATE TABLE saleItem ( - saleItemId BIGINT AUTO_INCREMENT PRIMARY KEY, - saleId BIGINT NOT NULL, - prodId BIGINT NOT NULL, - quantity INT NOT NULL, - unitPrice DECIMAL(10, 2) NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - FOREIGN KEY (saleId) REFERENCES sale(saleId), - FOREIGN KEY (prodId) REFERENCES product(prodId) -); - -CREATE TABLE purchaseOrder ( - purchaseOrderId BIGINT AUTO_INCREMENT PRIMARY KEY, - supId BIGINT NOT NULL, - orderDate DATE NOT NULL, - status VARCHAR(50) NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - FOREIGN KEY (supId) REFERENCES supplier(supId) -); - -CREATE TABLE activityLog ( - logId BIGINT AUTO_INCREMENT PRIMARY KEY, - employeeId BIGINT NOT NULL, - activity TEXT NOT NULL, - logTimestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, - FOREIGN KEY (employeeId) REFERENCES employee(employeeId) -); - --- Backend-only table for API authentication -CREATE TABLE users ( - id BIGINT AUTO_INCREMENT PRIMARY KEY, - username VARCHAR(50) UNIQUE NOT NULL, - password VARCHAR(255) NOT NULL, - role VARCHAR(20) NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP -); - --- Insert Sample Data - -INSERT INTO storeLocation (storeName, address, phone, email) -VALUES -('Downtown Branch', '123 Main St', '123-456-7890', 'downtown@petshop.com'), -('North Branch', '456 North Ave', '987-654-3210', 'north@petshop.com'), -('West Side Store', '789 West Blvd', '555-123-4567', 'westside@petshop.com'), -('East End Shop', '321 East Road', '555-987-6543', 'eastend@petshop.com'), -('South Mall Location', '654 South Plaza', '555-246-8135', 'southmall@petshop.com'); - -INSERT INTO employee (firstName, lastName, email, phone, role, isActive) -VALUES -('John', 'Doe', 'john@petshop.com', '111-222-3333', 'Manager', TRUE), -('Sara', 'Smith', 'sara@petshop.com', '444-555-6666', 'Staff', TRUE), -('Michael', 'Johnson', 'michael@petshop.com', '222-333-4444', 'Groomer', TRUE), -('Lisa', 'Williams', 'lisa@petshop.com', '333-444-5555', 'Staff', TRUE), -('David', 'Brown', 'david@petshop.com', '555-666-7777', 'Veterinarian', TRUE), -('Emma', 'Davis', 'emma@petshop.com', '666-777-8888', 'Manager', FALSE); - -INSERT INTO employeeStore (employeeId, storeId) -VALUES -(1, 1), -(2, 1), -(2, 2), -(3, 2), -(4, 3), -(5, 1), -(5, 4), -(6, 5); - -INSERT INTO customer (firstName, lastName, email, phone) -VALUES -('Alex', 'Brown', 'alex@gmail.com', '777-888-9999'), -('Emily', 'Clark', 'emily@gmail.com', '666-555-4444'), -('James', 'Wilson', 'james@gmail.com', '888-999-0000'), -('Olivia', 'Martinez', 'olivia@gmail.com', '999-000-1111'), -('William', 'Anderson', 'william@gmail.com', '000-111-2222'), -('Sophia', 'Taylor', 'sophia@gmail.com', '111-222-3333'); - -INSERT INTO pet (petName, petSpecies, petBreed, petAge, petStatus, petPrice) -VALUES -('Buddy', 'Dog', 'Labrador', 2, 'Available', 500.00), -('Milo', 'Cat', 'Persian', 1, 'Available', 300.00), -('Charlie', 'Dog', 'Golden Retriever', 3, 'Available', 550.00), -('Luna', 'Cat', 'Siamese', 2, 'Adopted', 350.00), -('Max', 'Dog', 'Beagle', 1, 'Available', 450.00), -('Bella', 'Cat', 'Maine Coon', 4, 'Available', 400.00); - -INSERT INTO adoption (petId, customerId, adoptionDate, adoptionStatus) -VALUES -(1, 1, '2026-01-15', 'Completed'), -(4, 3, '2026-01-20', 'Completed'), -(2, 2, '2026-01-25', 'Pending'), -(5, 4, '2026-02-01', 'Completed'), -(6, 5, '2026-02-02', 'Pending'); - -INSERT INTO supplier (supCompany, supContactFirstName, supContactLastName, supEmail, supPhone) -VALUES -('PetFood Inc', 'Robert', 'King', 'contact@petfood.com', '888-111-2222'), -('Toy World', 'Jennifer', 'Lee', 'sales@toyworld.com', '888-222-3333'), -('Pet Supplies Co', 'Kevin', 'White', 'info@petsupplies.com', '888-333-4444'), -('Animal Care Products', 'Nancy', 'Green', 'orders@animalcare.com', '888-444-5555'), -('Premium Pet Goods', 'Tom', 'Black', 'support@premiumpet.com', '888-555-6666'); - -INSERT INTO category (categoryName, categoryType) -VALUES -('Dog Food', 'Product'), -('Cat Toys', 'Product'), -('Bird Supplies', 'Product'), -('Aquarium', 'Product'), -('Small Animals', 'Product'); - -INSERT INTO product (prodName, prodPrice, categoryId, prodDesc) -VALUES -('Premium Dog Food', 50.00, 1, 'High quality dog food'), -('Cat Toy Ball', 10.00, 2, 'Colorful toy for cats'), -('Bird Cage Large', 120.00, 3, 'Spacious bird cage'), -('Fish Tank 20 Gallon', 80.00, 4, 'Complete aquarium kit'), -('Hamster Wheel', 15.00, 5, 'Exercise wheel for small pets'), -('Organic Dog Treats', 25.00, 1, 'Natural dog treats'); - -INSERT INTO productSupplier (supId, prodId, cost) -VALUES -(1, 1, 35.00), -(1, 2, 6.50), -(2, 2, 7.00), -(3, 3, 90.00), -(3, 4, 60.00), -(4, 5, 10.00), -(5, 6, 18.00), -(1, 6, 17.50); - -INSERT INTO inventory (prodId, quantity) -VALUES -(1, 100), -(2, 200), -(3, 50), -(4, 30), -(5, 150), -(6, 75); - -INSERT INTO service (serviceName, serviceDesc, serviceDuration, servicePrice) -VALUES -('Pet Grooming', 'Full grooming service', 60, 40.00), -('Nail Trimming', 'Quick nail trim', 15, 10.00), -('Bath and Brush', 'Bathing and brushing service', 45, 30.00), -('Veterinary Checkup', 'Complete health examination', 30, 75.00), -('Teeth Cleaning', 'Professional dental cleaning', 90, 100.00); - -INSERT INTO appointment (serviceId, customerId, appointmentDate, appointmentTime, appointmentStatus) -VALUES -(1, 2, '2026-02-01', '10:30:00', 'Booked'), -(2, 1, '2026-02-03', '14:00:00', 'Booked'), -(3, 3, '2026-02-05', '09:00:00', 'Completed'), -(4, 4, '2026-02-07', '11:30:00', 'Booked'), -(5, 5, '2026-02-10', '15:00:00', 'Cancelled'); - -INSERT INTO appointmentPet (appointmentId, petId) -VALUES -(1, 2), -(2, 1), -(3, 3), -(4, 5), -(5, 6); - -INSERT INTO sale (saleDate, totalAmount, paymentMethod, employeeId, storeId) -VALUES --- January Sales -('2026-01-05 09:15:00', 125.00, 'Card', 1, 1), -- Sale 1: Dog food + treats -('2026-01-08 11:30:00', 200.00, 'Card', 2, 1), -- Sale 2: Bird cage + fish tank -('2026-01-12 14:20:00', 60.00, 'Cash', 3, 2), -- Sale 3: Cat toys + hamster wheel -('2026-01-15 10:45:00', 150.00, 'Debit', 1, 1), -- Sale 4: Dog food (bulk purchase) -('2026-01-18 16:30:00', 80.00, 'Card', 4, 3), -- Sale 5: Fish tank -('2026-01-22 13:15:00', 95.00, 'Cash', 2, 2), -- Sale 6: Mixed items -('2026-01-25 15:40:00', 240.00, 'Card', 5, 4), -- Sale 7: Two bird cages -('2026-01-28 10:30:00', 80.00, 'Cash', 1, 1), -- Sale 8: Dog food + cat toys --- February Sales -('2026-02-01 09:00:00', 175.00, 'Card', 3, 3), -- Sale 9: Dog food + treats (bulk) -('2026-02-03 11:20:00', 120.00, 'Card', 2, 1), -- Sale 10: Bird cage -('2026-02-05 14:50:00', 45.00, 'Cash', 4, 2), -- Sale 11: Hamster wheel + cat toys -('2026-02-08 16:15:00', 160.00, 'Debit', 1, 1), -- Sale 12: Fish tank + accessories -('2026-02-10 10:25:00', 100.00, 'Card', 5, 4), -- Sale 13: Dog treats (bulk) -('2026-02-12 13:45:00', 50.00, 'Cash', 2, 2), -- Sale 14: Dog food -('2026-02-15 15:30:00', 85.00, 'Card', 3, 3), -- Sale 15: Mixed pet supplies -('2026-02-18 11:10:00', 200.00, 'Card', 1, 1), -- Sale 16: Bird cage + hamster wheel -('2026-02-20 14:35:00', 155.00, 'Debit', 4, 3), -- Sale 17: Fish tank + cat toys -('2026-02-22 16:50:00', 75.00, 'Cash', 2, 1), -- Sale 18: Dog treats + toys -('2026-02-24 10:15:00', 140.00, 'Card', 5, 4), -- Sale 19: Dog food + treats -(NOW(), 95.00, 'Card', 1, 1); -- Sale 20: Recent sale (current timestamp) - -INSERT INTO saleItem (saleId, prodId, quantity, unitPrice) -VALUES --- Sale 1 items (Dog food + treats) -(1, 1, 2, 50.00), -- 2x Premium Dog Food -(1, 6, 1, 25.00), -- 1x Organic Dog Treats --- Sale 2 items (Bird cage + fish tank) -(2, 3, 1, 120.00), -- 1x Bird Cage Large -(2, 4, 1, 80.00), -- 1x Fish Tank 20 Gallon --- Sale 3 items (Cat toys + hamster wheel) -(3, 2, 3, 10.00), -- 3x Cat Toy Ball -(3, 5, 2, 15.00), -- 2x Hamster Wheel --- Sale 4 items (Dog food bulk) -(4, 1, 3, 50.00), -- 3x Premium Dog Food --- Sale 5 items (Fish tank) -(5, 4, 1, 80.00), -- 1x Fish Tank 20 Gallon --- Sale 6 items (Mixed) -(6, 2, 4, 10.00), -- 4x Cat Toy Ball -(6, 5, 1, 15.00), -- 1x Hamster Wheel -(6, 6, 1, 25.00), -- 1x Organic Dog Treats -(6, 1, 1, 50.00), -- 1x Premium Dog Food (partial - discount applied) --- Sale 7 items (Two bird cages) -(7, 3, 2, 120.00), -- 2x Bird Cage Large --- Sale 8 items (Dog food + cat toys) -(8, 1, 1, 50.00), -- 1x Premium Dog Food -(8, 2, 3, 10.00), -- 3x Cat Toy Ball --- Sale 9 items (Dog food + treats bulk) -(9, 1, 3, 50.00), -- 3x Premium Dog Food -(9, 6, 1, 25.00), -- 1x Organic Dog Treats --- Sale 10 items (Bird cage) -(10, 3, 1, 120.00), -- 1x Bird Cage Large --- Sale 11 items (Hamster wheel + cat toys) -(11, 5, 1, 15.00), -- 1x Hamster Wheel -(11, 2, 3, 10.00), -- 3x Cat Toy Ball --- Sale 12 items (Fish tank + accessories) -(12, 4, 2, 80.00), -- 2x Fish Tank 20 Gallon --- Sale 13 items (Dog treats bulk) -(13, 6, 4, 25.00), -- 4x Organic Dog Treats --- Sale 14 items (Dog food) -(14, 1, 1, 50.00), -- 1x Premium Dog Food --- Sale 15 items (Mixed supplies) -(15, 2, 2, 10.00), -- 2x Cat Toy Ball -(15, 5, 1, 15.00), -- 1x Hamster Wheel -(15, 6, 2, 25.00), -- 2x Organic Dog Treats --- Sale 16 items (Bird cage + hamster wheel) -(16, 3, 1, 120.00), -- 1x Bird Cage Large -(16, 4, 1, 80.00), -- 1x Fish Tank 20 Gallon --- Sale 17 items (Fish tank + cat toys) -(17, 4, 1, 80.00), -- 1x Fish Tank 20 Gallon -(17, 1, 1, 50.00), -- 1x Premium Dog Food -(17, 6, 1, 25.00), -- 1x Organic Dog Treats --- Sale 18 items (Dog treats + toys) -(18, 6, 2, 25.00), -- 2x Organic Dog Treats -(18, 2, 2, 10.00), -- 2x Cat Toy Ball -(18, 5, 1, 15.00), -- 1x Hamster Wheel --- Sale 19 items (Dog food + treats) -(19, 1, 2, 50.00), -- 2x Premium Dog Food -(19, 6, 2, 25.00), -- 2x Organic Dog Treats (discount applied) --- Sale 20 items (Recent sale) -(20, 2, 5, 10.00), -- 5x Cat Toy Ball -(20, 5, 3, 15.00); -- 3x Hamster Wheel - -INSERT INTO purchaseOrder (supId, orderDate, status) -VALUES -(1, '2025-01-15', 'Delivered'), -(2, '2025-01-20', 'Pending'), -(3, '2025-02-01', 'Delivered'), -(4, '2025-02-10', 'In Transit'), -(1, '2025-02-15', 'Pending'); - -INSERT INTO activityLog (employeeId, activity) -VALUES -(1, 'Created new sale'), -(2, 'Booked appointment'), -(3, 'Completed grooming service'), -(4, 'Processed inventory order'), -(5, 'Conducted health checkup'), -(1, 'Updated customer information'); - --- Sample users created by DataInitializer (admin/admin123, staff/staff123) diff --git a/src/main/java/com/petshop/backend/controller/AuthController.java b/src/main/java/com/petshop/backend/controller/AuthController.java index 22f53420..7aa04307 100644 --- a/src/main/java/com/petshop/backend/controller/AuthController.java +++ b/src/main/java/com/petshop/backend/controller/AuthController.java @@ -16,6 +16,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.DisabledException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; @@ -125,6 +126,10 @@ public class AuthController { Map error = new HashMap<>(); error.put("message", "Invalid username or password"); return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(error); + } catch (DisabledException e) { + Map error = new HashMap<>(); + error.put("message", e.getMessage()); + return ResponseEntity.status(HttpStatus.FORBIDDEN).body(error); } } diff --git a/src/main/java/com/petshop/backend/controller/ChatController.java b/src/main/java/com/petshop/backend/controller/ChatController.java index f503cedf..5ffb1444 100644 --- a/src/main/java/com/petshop/backend/controller/ChatController.java +++ b/src/main/java/com/petshop/backend/controller/ChatController.java @@ -40,7 +40,7 @@ public class ChatController { } @PostMapping("/conversations") - @PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')") + @PreAuthorize("hasRole('CUSTOMER')") public ResponseEntity createConversation(@Valid @RequestBody ConversationRequest request) { User user = getCurrentUser(); ConversationResponse response = chatService.createConversation(user.getId(), request); diff --git a/src/main/java/com/petshop/backend/controller/DropdownController.java b/src/main/java/com/petshop/backend/controller/DropdownController.java index bf1d1c48..16763b8a 100644 --- a/src/main/java/com/petshop/backend/controller/DropdownController.java +++ b/src/main/java/com/petshop/backend/controller/DropdownController.java @@ -46,6 +46,7 @@ public class DropdownController { } @GetMapping("/customers") + @PreAuthorize("hasAnyRole('STAFF', 'ADMIN')") public ResponseEntity> getCustomers() { return ResponseEntity.ok( customerRepository.findAll().stream() diff --git a/src/main/java/com/petshop/backend/dto/appointment/AppointmentRequest.java b/src/main/java/com/petshop/backend/dto/appointment/AppointmentRequest.java index bd2b2356..247e5ae4 100644 --- a/src/main/java/com/petshop/backend/dto/appointment/AppointmentRequest.java +++ b/src/main/java/com/petshop/backend/dto/appointment/AppointmentRequest.java @@ -11,6 +11,9 @@ public class AppointmentRequest { @NotNull(message = "Customer ID is required") private Long customerId; + @NotNull(message = "Store ID is required") + private Long storeId; + @NotNull(message = "Service ID is required") private Long serviceId; @@ -34,6 +37,14 @@ public class AppointmentRequest { this.customerId = customerId; } + public Long getStoreId() { + return storeId; + } + + public void setStoreId(Long storeId) { + this.storeId = storeId; + } + public Long getServiceId() { return serviceId; } @@ -80,6 +91,7 @@ public class AppointmentRequest { if (o == null || getClass() != o.getClass()) return false; AppointmentRequest that = (AppointmentRequest) o; return Objects.equals(customerId, that.customerId) && + Objects.equals(storeId, that.storeId) && Objects.equals(serviceId, that.serviceId) && Objects.equals(appointmentDate, that.appointmentDate) && Objects.equals(appointmentTime, that.appointmentTime) && @@ -89,13 +101,14 @@ public class AppointmentRequest { @Override public int hashCode() { - return Objects.hash(customerId, serviceId, appointmentDate, appointmentTime, appointmentStatus, petIds); + return Objects.hash(customerId, storeId, serviceId, appointmentDate, appointmentTime, appointmentStatus, petIds); } @Override public String toString() { return "AppointmentRequest{" + "customerId=" + customerId + + ", storeId=" + storeId + ", serviceId=" + serviceId + ", appointmentDate=" + appointmentDate + ", appointmentTime=" + appointmentTime + diff --git a/src/main/java/com/petshop/backend/dto/appointment/AppointmentResponse.java b/src/main/java/com/petshop/backend/dto/appointment/AppointmentResponse.java index a4077a87..c7d2e8d7 100644 --- a/src/main/java/com/petshop/backend/dto/appointment/AppointmentResponse.java +++ b/src/main/java/com/petshop/backend/dto/appointment/AppointmentResponse.java @@ -10,6 +10,8 @@ public class AppointmentResponse { private Long appointmentId; private Long customerId; private String customerName; + private Long storeId; + private String storeName; private Long serviceId; private String serviceName; private LocalDate appointmentDate; @@ -23,10 +25,12 @@ public class AppointmentResponse { public AppointmentResponse() { } - public AppointmentResponse(Long appointmentId, Long customerId, String customerName, Long serviceId, String serviceName, LocalDate appointmentDate, LocalTime appointmentTime, String appointmentStatus, List petNames, List petIds, LocalDateTime createdAt, LocalDateTime updatedAt) { + public AppointmentResponse(Long appointmentId, Long customerId, String customerName, Long storeId, String storeName, Long serviceId, String serviceName, LocalDate appointmentDate, LocalTime appointmentTime, String appointmentStatus, List petNames, List petIds, LocalDateTime createdAt, LocalDateTime updatedAt) { this.appointmentId = appointmentId; this.customerId = customerId; this.customerName = customerName; + this.storeId = storeId; + this.storeName = storeName; this.serviceId = serviceId; this.serviceName = serviceName; this.appointmentDate = appointmentDate; @@ -62,6 +66,22 @@ public class AppointmentResponse { this.customerName = customerName; } + public Long getStoreId() { + return storeId; + } + + public void setStoreId(Long storeId) { + this.storeId = storeId; + } + + public String getStoreName() { + return storeName; + } + + public void setStoreName(String storeName) { + this.storeName = storeName; + } + public Long getServiceId() { return serviceId; } @@ -139,12 +159,12 @@ public class AppointmentResponse { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; AppointmentResponse that = (AppointmentResponse) o; - return Objects.equals(appointmentId, that.appointmentId) && Objects.equals(customerId, that.customerId) && Objects.equals(customerName, that.customerName) && Objects.equals(serviceId, that.serviceId) && Objects.equals(serviceName, that.serviceName) && Objects.equals(appointmentDate, that.appointmentDate) && Objects.equals(appointmentTime, that.appointmentTime) && Objects.equals(appointmentStatus, that.appointmentStatus) && Objects.equals(petNames, that.petNames) && Objects.equals(petIds, that.petIds) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt); + return Objects.equals(appointmentId, that.appointmentId) && Objects.equals(customerId, that.customerId) && Objects.equals(customerName, that.customerName) && Objects.equals(storeId, that.storeId) && Objects.equals(storeName, that.storeName) && Objects.equals(serviceId, that.serviceId) && Objects.equals(serviceName, that.serviceName) && Objects.equals(appointmentDate, that.appointmentDate) && Objects.equals(appointmentTime, that.appointmentTime) && Objects.equals(appointmentStatus, that.appointmentStatus) && Objects.equals(petNames, that.petNames) && Objects.equals(petIds, that.petIds) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt); } @Override public int hashCode() { - return Objects.hash(appointmentId, customerId, customerName, serviceId, serviceName, appointmentDate, appointmentTime, appointmentStatus, petNames, petIds, createdAt, updatedAt); + return Objects.hash(appointmentId, customerId, customerName, storeId, storeName, serviceId, serviceName, appointmentDate, appointmentTime, appointmentStatus, petNames, petIds, createdAt, updatedAt); } @Override @@ -153,6 +173,8 @@ public class AppointmentResponse { "appointmentId=" + appointmentId + ", customerId=" + customerId + ", customerName='" + customerName + '\'' + + ", storeId=" + storeId + + ", storeName='" + storeName + '\'' + ", serviceId=" + serviceId + ", serviceName='" + serviceName + '\'' + ", appointmentDate=" + appointmentDate + diff --git a/src/main/java/com/petshop/backend/entity/Appointment.java b/src/main/java/com/petshop/backend/entity/Appointment.java index c31a94ff..101ff885 100644 --- a/src/main/java/com/petshop/backend/entity/Appointment.java +++ b/src/main/java/com/petshop/backend/entity/Appointment.java @@ -23,6 +23,10 @@ public class Appointment { @JoinColumn(name = "customerId", nullable = false) private Customer customer; + @ManyToOne + @JoinColumn(name = "storeId", nullable = false) + private StoreLocation store; + @ManyToOne @JoinColumn(name = "serviceId", nullable = false) private Service service; @@ -55,9 +59,10 @@ public class Appointment { public Appointment() { } - public Appointment(Long appointmentId, Customer customer, Service service, LocalDate appointmentDate, LocalTime appointmentTime, String appointmentStatus, Set pets, LocalDateTime createdAt, LocalDateTime updatedAt) { + public Appointment(Long appointmentId, Customer customer, StoreLocation store, Service service, LocalDate appointmentDate, LocalTime appointmentTime, String appointmentStatus, Set pets, LocalDateTime createdAt, LocalDateTime updatedAt) { this.appointmentId = appointmentId; this.customer = customer; + this.store = store; this.service = service; this.appointmentDate = appointmentDate; this.appointmentTime = appointmentTime; @@ -83,6 +88,14 @@ public class Appointment { this.customer = customer; } + public StoreLocation getStore() { + return store; + } + + public void setStore(StoreLocation store) { + this.store = store; + } + public Service getService() { return service; } @@ -157,6 +170,7 @@ public class Appointment { return "Appointment{" + "appointmentId=" + appointmentId + ", customer=" + customer + + ", store=" + store + ", service=" + service + ", appointmentDate=" + appointmentDate + ", appointmentTime=" + appointmentTime + diff --git a/src/main/java/com/petshop/backend/entity/Conversation.java b/src/main/java/com/petshop/backend/entity/Conversation.java index 1d8008e1..0a907710 100644 --- a/src/main/java/com/petshop/backend/entity/Conversation.java +++ b/src/main/java/com/petshop/backend/entity/Conversation.java @@ -21,7 +21,7 @@ public class Conversation { private Long staffId; @Enumerated(EnumType.STRING) - @Column(length = 20, nullable = false) + @Column(length = 20, nullable = false, columnDefinition = "VARCHAR(20)") private ConversationStatus status = ConversationStatus.OPEN; @CreationTimestamp diff --git a/src/main/java/com/petshop/backend/entity/Refund.java b/src/main/java/com/petshop/backend/entity/Refund.java index 8addc548..4973427e 100644 --- a/src/main/java/com/petshop/backend/entity/Refund.java +++ b/src/main/java/com/petshop/backend/entity/Refund.java @@ -29,7 +29,7 @@ public class Refund { private String reason; @Enumerated(EnumType.STRING) - @Column(nullable = false, length = 20) + @Column(nullable = false, length = 20, columnDefinition = "VARCHAR(20)") private RefundStatus status; @CreationTimestamp diff --git a/src/main/java/com/petshop/backend/repository/AppointmentRepository.java b/src/main/java/com/petshop/backend/repository/AppointmentRepository.java index 789a8b25..5c7b6ec0 100644 --- a/src/main/java/com/petshop/backend/repository/AppointmentRepository.java +++ b/src/main/java/com/petshop/backend/repository/AppointmentRepository.java @@ -18,8 +18,8 @@ public interface AppointmentRepository extends JpaRepository @Query("SELECT a FROM Appointment a WHERE a.appointmentDate = :date AND a.appointmentTime = :time") List findByDateAndTime(@Param("date") LocalDate date, @Param("time") LocalTime time); - @Query("SELECT a FROM Appointment a WHERE a.service.serviceId = :serviceId AND a.appointmentDate = :date AND a.appointmentStatus != 'Cancelled'") - List findByServiceAndDate(@Param("serviceId") Long serviceId, @Param("date") LocalDate date); + @Query("SELECT a FROM Appointment a JOIN FETCH a.service WHERE a.store.storeId = :storeId AND a.appointmentDate = :date AND LOWER(a.appointmentStatus) <> 'cancelled'") + List findByStoreAndDate(@Param("storeId") Long storeId, @Param("date") LocalDate date); @Query("SELECT DISTINCT a FROM Appointment a LEFT JOIN a.pets p WHERE " + "LOWER(a.customer.firstName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + diff --git a/src/main/java/com/petshop/backend/repository/EmployeeStoreRepository.java b/src/main/java/com/petshop/backend/repository/EmployeeStoreRepository.java new file mode 100644 index 00000000..1c847817 --- /dev/null +++ b/src/main/java/com/petshop/backend/repository/EmployeeStoreRepository.java @@ -0,0 +1,12 @@ +package com.petshop.backend.repository; + +import com.petshop.backend.entity.EmployeeStore; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface EmployeeStoreRepository extends JpaRepository { + Optional findByEmployeeEmployeeId(Long employeeId); +} diff --git a/src/main/java/com/petshop/backend/repository/SaleRepository.java b/src/main/java/com/petshop/backend/repository/SaleRepository.java index 56b31289..f70b3dec 100644 --- a/src/main/java/com/petshop/backend/repository/SaleRepository.java +++ b/src/main/java/com/petshop/backend/repository/SaleRepository.java @@ -8,6 +8,8 @@ import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; +import java.util.List; + @Repository public interface SaleRepository extends JpaRepository { @@ -16,4 +18,6 @@ public interface SaleRepository extends JpaRepository { "LOWER(s.employee.lastName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + "LOWER(s.store.storeName) LIKE LOWER(CONCAT('%', :q, '%'))") Page searchSales(@Param("q") String query, Pageable pageable); + + List findByOriginalSaleSaleId(Long originalSaleId); } diff --git a/src/main/java/com/petshop/backend/security/JwtAuthenticationFilter.java b/src/main/java/com/petshop/backend/security/JwtAuthenticationFilter.java index 02e967aa..d79c88f6 100644 --- a/src/main/java/com/petshop/backend/security/JwtAuthenticationFilter.java +++ b/src/main/java/com/petshop/backend/security/JwtAuthenticationFilter.java @@ -5,6 +5,7 @@ import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.lang.NonNull; +import org.springframework.security.authentication.DisabledException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; @@ -14,6 +15,7 @@ import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; +import java.time.LocalDateTime; @Component public class JwtAuthenticationFilter extends OncePerRequestFilter { @@ -45,7 +47,17 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { username = jwtUtil.extractUsername(jwt); if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { - UserDetails userDetails = userDetailsService.loadUserByUsername(username); + UserDetails userDetails; + try { + userDetails = userDetailsService.loadUserByUsername(username); + } catch (DisabledException ex) { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.setContentType("application/json"); + response.getWriter().write( + "{\"status\":401,\"message\":\"" + ex.getMessage() + "\",\"timestamp\":\"" + LocalDateTime.now() + "\"}" + ); + return; + } if (jwtUtil.validateToken(jwt, userDetails)) { UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken( userDetails, diff --git a/src/main/java/com/petshop/backend/service/AppointmentService.java b/src/main/java/com/petshop/backend/service/AppointmentService.java index 6b9d3548..afc3d600 100644 --- a/src/main/java/com/petshop/backend/service/AppointmentService.java +++ b/src/main/java/com/petshop/backend/service/AppointmentService.java @@ -5,14 +5,24 @@ import com.petshop.backend.dto.appointment.AppointmentResponse; import com.petshop.backend.dto.common.BulkDeleteRequest; import com.petshop.backend.entity.Appointment; import com.petshop.backend.entity.Customer; +import com.petshop.backend.entity.Employee; +import com.petshop.backend.entity.EmployeeStore; import com.petshop.backend.entity.Pet; +import com.petshop.backend.entity.StoreLocation; +import com.petshop.backend.entity.User; import com.petshop.backend.exception.ResourceNotFoundException; import com.petshop.backend.repository.AppointmentRepository; import com.petshop.backend.repository.CustomerRepository; +import com.petshop.backend.repository.EmployeeRepository; +import com.petshop.backend.repository.EmployeeStoreRepository; import com.petshop.backend.repository.PetRepository; import com.petshop.backend.repository.ServiceRepository; +import com.petshop.backend.repository.StoreRepository; +import com.petshop.backend.repository.UserRepository; +import com.petshop.backend.util.AuthenticationHelper; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.security.access.AccessDeniedException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -22,6 +32,7 @@ import java.time.LocalTime; import java.util.ArrayList; import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Set; import java.util.stream.Collectors; @@ -32,12 +43,20 @@ public class AppointmentService { private final CustomerRepository customerRepository; private final ServiceRepository serviceRepository; private final PetRepository petRepository; + private final StoreRepository storeRepository; + private final UserRepository userRepository; + private final EmployeeRepository employeeRepository; + private final EmployeeStoreRepository employeeStoreRepository; - public AppointmentService(AppointmentRepository appointmentRepository, CustomerRepository customerRepository, ServiceRepository serviceRepository, PetRepository petRepository) { + public AppointmentService(AppointmentRepository appointmentRepository, CustomerRepository customerRepository, ServiceRepository serviceRepository, PetRepository petRepository, StoreRepository storeRepository, UserRepository userRepository, EmployeeRepository employeeRepository, EmployeeStoreRepository employeeStoreRepository) { this.appointmentRepository = appointmentRepository; this.customerRepository = customerRepository; this.serviceRepository = serviceRepository; this.petRepository = petRepository; + this.storeRepository = storeRepository; + this.userRepository = userRepository; + this.employeeRepository = employeeRepository; + this.employeeStoreRepository = employeeStoreRepository; } public Page getAllAppointments(String query, Pageable pageable, Long customerId) { @@ -78,13 +97,20 @@ public class AppointmentService { Customer customer = customerRepository.findById(request.getCustomerId()) .orElseThrow(() -> new ResourceNotFoundException("Customer not found with id: " + request.getCustomerId())); + StoreLocation store = storeRepository.findById(request.getStoreId()) + .orElseThrow(() -> new ResourceNotFoundException("Store not found with id: " + request.getStoreId())); + com.petshop.backend.entity.Service service = serviceRepository.findById(request.getServiceId()) .orElseThrow(() -> new ResourceNotFoundException("Service not found with id: " + request.getServiceId())); + validateStoreAccess(store.getStoreId()); + validateAvailability(store, service, request.getAppointmentDate(), request.getAppointmentTime(), null); + Set pets = fetchPets(request.getPetIds()); Appointment appointment = new Appointment(); appointment.setCustomer(customer); + appointment.setStore(store); appointment.setService(service); appointment.setAppointmentDate(request.getAppointmentDate()); appointment.setAppointmentTime(request.getAppointmentTime()); @@ -105,12 +131,19 @@ public class AppointmentService { Customer customer = customerRepository.findById(request.getCustomerId()) .orElseThrow(() -> new ResourceNotFoundException("Customer not found with id: " + request.getCustomerId())); + StoreLocation store = storeRepository.findById(request.getStoreId()) + .orElseThrow(() -> new ResourceNotFoundException("Store not found with id: " + request.getStoreId())); + com.petshop.backend.entity.Service service = serviceRepository.findById(request.getServiceId()) .orElseThrow(() -> new ResourceNotFoundException("Service not found with id: " + request.getServiceId())); + validateStoreAccess(store.getStoreId()); + validateAvailability(store, service, request.getAppointmentDate(), request.getAppointmentTime(), id); + Set pets = fetchPets(request.getPetIds()); appointment.setCustomer(customer); + appointment.setStore(store); appointment.setService(service); appointment.setAppointmentDate(request.getAppointmentDate()); appointment.setAppointmentTime(request.getAppointmentTime()); @@ -135,21 +168,22 @@ public class AppointmentService { } public List checkAvailability(Long storeId, Long serviceId, LocalDate date) { + storeRepository.findById(storeId) + .orElseThrow(() -> new ResourceNotFoundException("Store not found with id: " + storeId)); + com.petshop.backend.entity.Service service = serviceRepository.findById(serviceId) .orElseThrow(() -> new ResourceNotFoundException("Service not found with id: " + serviceId)); - List existingAppointments = appointmentRepository.findByServiceAndDate(serviceId, date); - Set bookedTimes = existingAppointments.stream() - .map(Appointment::getAppointmentTime) - .collect(Collectors.toSet()); + List existingAppointments = appointmentRepository.findByStoreAndDate(storeId, date); List availableSlots = new ArrayList<>(); LocalTime startTime = LocalTime.of(9, 0); LocalTime endTime = LocalTime.of(17, 0); + LocalTime latestStart = endTime.minusMinutes(service.getServiceDuration()); LocalTime currentTime = startTime; - while (currentTime.isBefore(endTime)) { - if (!bookedTimes.contains(currentTime)) { + while (!currentTime.isAfter(latestStart)) { + if (isSlotAvailable(existingAppointments, service, currentTime, null)) { availableSlots.add(currentTime.toString()); } currentTime = currentTime.plusMinutes(30); @@ -190,6 +224,8 @@ public class AppointmentService { appointment.getAppointmentId(), appointment.getCustomer().getCustomerId(), appointment.getCustomer().getFirstName() + " " + appointment.getCustomer().getLastName(), + appointment.getStore().getStoreId(), + appointment.getStore().getStoreName(), appointment.getService().getServiceId(), appointment.getService().getServiceName(), appointment.getAppointmentDate(), @@ -201,4 +237,41 @@ public class AppointmentService { appointment.getUpdatedAt() ); } + + private void validateAvailability(StoreLocation store, com.petshop.backend.entity.Service service, LocalDate date, LocalTime time, Long appointmentIdToIgnore) { + List existingAppointments = appointmentRepository.findByStoreAndDate(store.getStoreId(), date); + if (!isSlotAvailable(existingAppointments, service, time, appointmentIdToIgnore)) { + throw new IllegalArgumentException("Appointment time is not available for the selected store and service"); + } + } + + private boolean isSlotAvailable(List existingAppointments, com.petshop.backend.entity.Service requestedService, LocalTime requestedStart, Long appointmentIdToIgnore) { + LocalTime requestedEnd = requestedStart.plusMinutes(requestedService.getServiceDuration()); + for (Appointment existingAppointment : existingAppointments) { + if (appointmentIdToIgnore != null && appointmentIdToIgnore.equals(existingAppointment.getAppointmentId())) { + continue; + } + LocalTime existingStart = existingAppointment.getAppointmentTime(); + LocalTime existingEnd = existingStart.plusMinutes(existingAppointment.getService().getServiceDuration()); + if (requestedStart.isBefore(existingEnd) && existingStart.isBefore(requestedEnd)) { + return false; + } + } + return true; + } + + private void validateStoreAccess(Long requestedStoreId) { + User user = AuthenticationHelper.getAuthenticatedUser(userRepository); + if (user.getRole() != User.Role.STAFF) { + return; + } + + Employee employee = AuthenticationHelper.getAuthenticatedEmployee(userRepository, employeeRepository); + EmployeeStore employeeStore = employeeStoreRepository.findByEmployeeEmployeeId(employee.getEmployeeId()) + .orElseThrow(() -> new AccessDeniedException("Authenticated staff member is not assigned to a store")); + + if (!employeeStore.getStore().getStoreId().equals(requestedStoreId)) { + throw new AccessDeniedException("Staff can only manage appointments for their assigned store"); + } + } } diff --git a/src/main/java/com/petshop/backend/service/ChatService.java b/src/main/java/com/petshop/backend/service/ChatService.java index 5842f6e7..af5b6332 100644 --- a/src/main/java/com/petshop/backend/service/ChatService.java +++ b/src/main/java/com/petshop/backend/service/ChatService.java @@ -43,6 +43,10 @@ public class ChatService { User user = userRepository.findById(userId) .orElseThrow(() -> new ResourceNotFoundException("User not found")); + if (user.getRole() != User.Role.CUSTOMER) { + throw new AccessDeniedException("Only customers can start new conversations"); + } + Customer customer = customerRepository.findByUserId(userId) .orElseThrow(() -> new ResourceNotFoundException("Customer record not found for user")); @@ -113,6 +117,18 @@ public class ChatService { Conversation conversation = conversationRepository.findById(conversationId) .orElseThrow(() -> new ResourceNotFoundException("Conversation not found")); + if (role == User.Role.CUSTOMER) { + Customer customer = customerRepository.findByUserId(userId) + .orElseThrow(() -> new ResourceNotFoundException("Customer record not found for user")); + if (!conversation.getCustomerId().equals(customer.getCustomerId())) { + throw new AccessDeniedException("You can only send messages to your own conversations"); + } + } else if (role == User.Role.STAFF) { + if (conversation.getStaffId() != null && !conversation.getStaffId().equals(userId)) { + throw new AccessDeniedException("You can only reply to conversations assigned to you or unassigned conversations"); + } + } + Message message = new Message(); message.setConversationId(conversationId); message.setSenderId(userId); diff --git a/src/main/java/com/petshop/backend/service/SaleService.java b/src/main/java/com/petshop/backend/service/SaleService.java index fab3e6da..afc9d068 100644 --- a/src/main/java/com/petshop/backend/service/SaleService.java +++ b/src/main/java/com/petshop/backend/service/SaleService.java @@ -26,15 +26,17 @@ public class SaleService { private final StoreRepository storeRepository; private final InventoryRepository inventoryRepository; private final EmployeeRepository employeeRepository; + private final EmployeeStoreRepository employeeStoreRepository; private final UserRepository userRepository; private final CustomerRepository customerRepository; - public SaleService(SaleRepository saleRepository, ProductRepository productRepository, StoreRepository storeRepository, InventoryRepository inventoryRepository, EmployeeRepository employeeRepository, UserRepository userRepository, CustomerRepository customerRepository) { + public SaleService(SaleRepository saleRepository, ProductRepository productRepository, StoreRepository storeRepository, InventoryRepository inventoryRepository, EmployeeRepository employeeRepository, EmployeeStoreRepository employeeStoreRepository, UserRepository userRepository, CustomerRepository customerRepository) { this.saleRepository = saleRepository; this.productRepository = productRepository; this.storeRepository = storeRepository; this.inventoryRepository = inventoryRepository; this.employeeRepository = employeeRepository; + this.employeeStoreRepository = employeeStoreRepository; this.userRepository = userRepository; this.customerRepository = customerRepository; } @@ -57,11 +59,20 @@ public class SaleService { @Transactional public SaleResponse createSale(SaleRequest request) { + User user = AuthenticationHelper.getAuthenticatedUser(userRepository); Employee employee = AuthenticationHelper.getAuthenticatedEmployee(userRepository, employeeRepository); + Long employeeStoreId = employeeStoreRepository.findByEmployeeEmployeeId(employee.getEmployeeId()) + .orElseThrow(() -> new BusinessException("Authenticated staff member is not assigned to a store")) + .getStore() + .getStoreId(); StoreLocation store = storeRepository.findById(request.getStoreId()) .orElseThrow(() -> new ResourceNotFoundException("Store not found with id: " + request.getStoreId())); + if (user.getRole() == User.Role.STAFF && !employeeStoreId.equals(store.getStoreId())) { + throw new BusinessException("Staff can only create sales for their assigned store"); + } + Sale sale = new Sale(); sale.setSaleDate(LocalDateTime.now()); sale.setEmployee(employee); @@ -100,6 +111,19 @@ public class SaleService { " for product: " + product.getProdName()); } + int alreadyRefundedQuantity = saleRepository.findByOriginalSaleSaleId(sale.getOriginalSale().getSaleId()).stream() + .flatMap(existingRefund -> existingRefund.getItems().stream()) + .filter(existingRefundItem -> existingRefundItem.getProduct().getProdId().equals(itemRequest.getProdId())) + .mapToInt(existingRefundItem -> Math.abs(existingRefundItem.getQuantity())) + .sum(); + + int refundableQuantity = originalItem.getQuantity() - alreadyRefundedQuantity; + if (itemRequest.getQuantity() > refundableQuantity) { + throw new BusinessException("Refund quantity " + itemRequest.getQuantity() + + " exceeds remaining refundable quantity " + refundableQuantity + + " for product: " + product.getProdName()); + } + Inventory inventory = inventoryRepository.findByProductId(itemRequest.getProdId()) .orElseThrow(() -> new ResourceNotFoundException("Inventory not found for product " + itemRequest.getProdId())); diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index f7c2ceba..6338af51 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -20,7 +20,7 @@ spring: jpa: hibernate: - ddl-auto: none + ddl-auto: validate naming: physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl show-sql: ${JPA_SHOW_SQL:false} diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql deleted file mode 100644 index 5e8d3fb6..00000000 --- a/src/main/resources/data.sql +++ /dev/null @@ -1,205 +0,0 @@ --- Insert Sample Data - -INSERT INTO storeLocation (storeName, address, phone, email) -VALUES -('Downtown Branch', '123 Main St', '123-456-7890', 'downtown@petshop.com'), -('North Branch', '456 North Ave', '987-654-3210', 'north@petshop.com'), -('West Side Store', '789 West Blvd', '555-123-4567', 'westside@petshop.com'), -('East End Shop', '321 East Road', '555-987-6543', 'eastend@petshop.com'), -('South Mall Location', '654 South Plaza', '555-246-8135', 'southmall@petshop.com'); - -INSERT INTO employee (firstName, lastName, email, phone, role, isActive) -VALUES -('John', 'Doe', 'john@petshop.com', '111-222-3333', 'Manager', TRUE), -('Sara', 'Smith', 'sara@petshop.com', '444-555-6666', 'Staff', TRUE), -('Michael', 'Johnson', 'michael@petshop.com', '222-333-4444', 'Groomer', TRUE), -('Lisa', 'Williams', 'lisa@petshop.com', '333-444-5555', 'Staff', TRUE), -('David', 'Brown', 'david@petshop.com', '555-666-7777', 'Veterinarian', TRUE), -('Emma', 'Davis', 'emma@petshop.com', '666-777-8888', 'Manager', FALSE); - -INSERT INTO employeeStore (employeeId, storeId) -VALUES -(1, 1), -(2, 1), -(2, 2), -(3, 2), -(4, 3), -(5, 1), -(5, 4), -(6, 5); - -INSERT INTO customer (firstName, lastName, email, phone) -VALUES -('Alex', 'Brown', 'alex@gmail.com', '777-888-9999'), -('Emily', 'Clark', 'emily@gmail.com', '666-555-4444'), -('James', 'Wilson', 'james@gmail.com', '888-999-0000'), -('Olivia', 'Martinez', 'olivia@gmail.com', '999-000-1111'), -('William', 'Anderson', 'william@gmail.com', '000-111-2222'), -('Sophia', 'Taylor', 'sophia@gmail.com', '111-222-3333'); - -INSERT INTO pet (petName, petSpecies, petBreed, petAge, petStatus, petPrice) -VALUES -('Buddy', 'Dog', 'Labrador', 2, 'Available', 500.00), -('Milo', 'Cat', 'Persian', 1, 'Available', 300.00), -('Charlie', 'Dog', 'Golden Retriever', 3, 'Available', 550.00), -('Luna', 'Cat', 'Siamese', 2, 'Adopted', 350.00), -('Max', 'Dog', 'Beagle', 1, 'Available', 450.00), -('Bella', 'Cat', 'Maine Coon', 4, 'Available', 400.00); - -INSERT INTO adoption (petId, customerId, adoptionDate, adoptionStatus) -VALUES -(1, 1, '2026-01-15', 'Completed'), -(4, 3, '2026-01-20', 'Completed'), -(2, 2, '2026-01-25', 'Pending'), -(5, 4, '2026-02-01', 'Completed'), -(6, 5, '2026-02-02', 'Pending'); - -INSERT INTO supplier (supCompany, supContactFirstName, supContactLastName, supEmail, supPhone) -VALUES -('PetFood Inc', 'Robert', 'King', 'contact@petfood.com', '888-111-2222'), -('Toy World', 'Jennifer', 'Lee', 'sales@toyworld.com', '888-222-3333'), -('Pet Supplies Co', 'Kevin', 'White', 'info@petsupplies.com', '888-333-4444'), -('Animal Care Products', 'Nancy', 'Green', 'orders@animalcare.com', '888-444-5555'), -('Premium Pet Goods', 'Tom', 'Black', 'support@premiumpet.com', '888-555-6666'); - -INSERT INTO category (categoryName, categoryType) -VALUES -('Dog Food', 'Product'), -('Cat Toys', 'Product'), -('Bird Supplies', 'Product'), -('Aquarium', 'Product'), -('Small Animals', 'Product'); - -INSERT INTO product (prodName, prodPrice, categoryId, prodDesc) -VALUES -('Premium Dog Food', 50.00, 1, 'High quality dog food'), -('Cat Toy Ball', 10.00, 2, 'Colorful toy for cats'), -('Bird Cage Large', 120.00, 3, 'Spacious bird cage'), -('Fish Tank 20 Gallon', 80.00, 4, 'Complete aquarium kit'), -('Hamster Wheel', 15.00, 5, 'Exercise wheel for small pets'), -('Organic Dog Treats', 25.00, 1, 'Natural dog treats'); - -INSERT INTO productSupplier (supId, prodId, cost) -VALUES -(1, 1, 35.00), -(1, 2, 6.50), -(2, 2, 7.00), -(3, 3, 90.00), -(3, 4, 60.00), -(4, 5, 10.00), -(5, 6, 18.00), -(1, 6, 17.50); - -INSERT INTO inventory (prodId, quantity) -VALUES -(1, 100), -(2, 200), -(3, 50), -(4, 30), -(5, 150), -(6, 75); - -INSERT INTO service (serviceName, serviceDesc, serviceDuration, servicePrice) -VALUES -('Pet Grooming', 'Full grooming service', 60, 40.00), -('Nail Trimming', 'Quick nail trim', 15, 10.00), -('Bath and Brush', 'Bathing and brushing service', 45, 30.00), -('Veterinary Checkup', 'Complete health examination', 30, 75.00), -('Teeth Cleaning', 'Professional dental cleaning', 90, 100.00); - -INSERT INTO appointment (serviceId, customerId, appointmentDate, appointmentTime, appointmentStatus) -VALUES -(1, 2, '2026-02-01', '10:30:00', 'Booked'), -(2, 1, '2026-02-03', '14:00:00', 'Booked'), -(3, 3, '2026-02-05', '09:00:00', 'Completed'), -(4, 4, '2026-02-07', '11:30:00', 'Booked'), -(5, 5, '2026-02-10', '15:00:00', 'Cancelled'); - -INSERT INTO appointmentPet (appointmentId, petId) -VALUES -(1, 2), -(2, 1), -(3, 3), -(4, 5), -(5, 6); - -INSERT INTO sale (saleDate, totalAmount, paymentMethod, employeeId, storeId, customerId) -VALUES -('2026-01-05 09:15:00', 125.00, 'Card', 1, 1, 1), -('2026-01-08 11:30:00', 200.00, 'Card', 2, 1, 2), -('2026-01-12 14:20:00', 60.00, 'Cash', 3, 2, 3), -('2026-01-15 10:45:00', 150.00, 'Debit', 1, 1, 1), -('2026-01-18 16:30:00', 80.00, 'Card', 4, 3, 2), -('2026-01-22 13:15:00', 95.00, 'Cash', 2, 2, NULL), -('2026-01-25 15:40:00', 240.00, 'Card', 5, 4, 4), -('2026-01-28 10:30:00', 80.00, 'Cash', 1, 1, NULL), -('2026-02-01 09:00:00', 175.00, 'Card', 3, 3, 1), -('2026-02-03 11:20:00', 120.00, 'Card', 2, 1, 3), -('2026-02-05 14:50:00', 45.00, 'Cash', 4, 2, NULL), -('2026-02-08 16:15:00', 160.00, 'Debit', 1, 1, 2), -('2026-02-10 10:25:00', 100.00, 'Card', 5, 4, NULL), -('2026-02-12 13:45:00', 50.00, 'Cash', 2, 2, 1), -('2026-02-15 15:30:00', 85.00, 'Card', 3, 3, NULL), -('2026-02-18 11:10:00', 200.00, 'Card', 1, 1, 4), -('2026-02-20 14:35:00', 155.00, 'Debit', 4, 3, NULL), -('2026-02-22 16:50:00', 75.00, 'Cash', 2, 1, 2), -('2026-02-24 10:15:00', 140.00, 'Card', 5, 4, NULL), -(NOW(), 95.00, 'Card', 1, 1, 1); - -INSERT INTO saleItem (saleId, prodId, quantity, unitPrice) -VALUES -(1, 1, 2, 50.00), -(1, 6, 1, 25.00), -(2, 3, 1, 120.00), -(2, 4, 1, 80.00), -(3, 2, 3, 10.00), -(3, 5, 2, 15.00), -(4, 1, 3, 50.00), -(5, 4, 1, 80.00), -(6, 2, 4, 10.00), -(6, 5, 1, 15.00), -(6, 6, 1, 25.00), -(6, 1, 1, 50.00), -(7, 3, 2, 120.00), -(8, 1, 1, 50.00), -(8, 2, 3, 10.00), -(9, 1, 3, 50.00), -(9, 6, 1, 25.00), -(10, 3, 1, 120.00), -(11, 5, 1, 15.00), -(11, 2, 3, 10.00), -(12, 4, 2, 80.00), -(13, 6, 4, 25.00), -(14, 1, 1, 50.00), -(15, 2, 2, 10.00), -(15, 5, 1, 15.00), -(15, 6, 2, 25.00), -(16, 3, 1, 120.00), -(16, 4, 1, 80.00), -(17, 4, 1, 80.00), -(17, 1, 1, 50.00), -(17, 6, 1, 25.00), -(18, 6, 2, 25.00), -(18, 2, 2, 10.00), -(18, 5, 1, 15.00), -(19, 1, 2, 50.00), -(19, 6, 2, 25.00), -(20, 2, 5, 10.00), -(20, 5, 3, 15.00); - -INSERT INTO purchaseOrder (supId, orderDate, status) -VALUES -(1, '2025-01-15', 'Delivered'), -(2, '2025-01-20', 'Pending'), -(3, '2025-02-01', 'Delivered'), -(4, '2025-02-10', 'In Transit'), -(1, '2025-02-15', 'Pending'); - -INSERT INTO activityLog (employeeId, activity) -VALUES -(1, 'Created new sale'), -(2, 'Booked appointment'), -(3, 'Completed grooming service'), -(4, 'Processed inventory order'), -(5, 'Conducted health checkup'), -(1, 'Updated customer information'); diff --git a/src/main/resources/db/migration/V3__appointment_store_and_employee_store_constraints.sql b/src/main/resources/db/migration/V3__appointment_store_and_employee_store_constraints.sql new file mode 100644 index 00000000..2e65bc98 --- /dev/null +++ b/src/main/resources/db/migration/V3__appointment_store_and_employee_store_constraints.sql @@ -0,0 +1,19 @@ +ALTER TABLE appointment + ADD COLUMN storeId BIGINT NULL AFTER customerId; + +UPDATE appointment +SET storeId = 1 +WHERE storeId IS NULL; + +ALTER TABLE appointment + MODIFY COLUMN storeId BIGINT NOT NULL, + ADD CONSTRAINT fk_appointment_store FOREIGN KEY (storeId) REFERENCES storeLocation(storeId); + +DELETE es1 +FROM employeeStore es1 +JOIN employeeStore es2 + ON es1.employeeId = es2.employeeId + AND es1.storeId > es2.storeId; + +ALTER TABLE employeeStore + ADD CONSTRAINT uk_employeeStore_employee UNIQUE (employeeId); diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql deleted file mode 100644 index 097a7328..00000000 --- a/src/main/resources/schema.sql +++ /dev/null @@ -1,250 +0,0 @@ --- Create Tables - -CREATE TABLE IF NOT EXISTS storeLocation ( - storeId BIGINT AUTO_INCREMENT PRIMARY KEY, - storeName VARCHAR(100) NOT NULL, - address VARCHAR(255) NOT NULL, - phone VARCHAR(20) NOT NULL, - email VARCHAR(100) NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP -); - -CREATE TABLE IF NOT EXISTS employee ( - employeeId BIGINT AUTO_INCREMENT PRIMARY KEY, - user_id BIGINT NULL, - firstName VARCHAR(50) NOT NULL, - lastName VARCHAR(50) NOT NULL, - email VARCHAR(100) NOT NULL, - phone VARCHAR(20) NOT NULL, - role VARCHAR(50) NOT NULL, - isActive BOOLEAN DEFAULT TRUE NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - CONSTRAINT uk_employee_user_id UNIQUE (user_id) -); - -CREATE TABLE IF NOT EXISTS employeeStore ( - employeeId BIGINT NOT NULL, - storeId BIGINT NOT NULL, - PRIMARY KEY (employeeId, storeId), - FOREIGN KEY (employeeId) REFERENCES employee(employeeId), - FOREIGN KEY (storeId) REFERENCES storeLocation(storeId) -); - -CREATE TABLE IF NOT EXISTS customer ( - customerId BIGINT AUTO_INCREMENT PRIMARY KEY, - user_id BIGINT NULL, - firstName VARCHAR(50) NOT NULL, - lastName VARCHAR(50) NOT NULL, - email VARCHAR(100) NOT NULL, - phone VARCHAR(20) NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - CONSTRAINT uk_customer_user_id UNIQUE (user_id) -); - -CREATE TABLE IF NOT EXISTS pet ( - petId BIGINT AUTO_INCREMENT PRIMARY KEY, - petName VARCHAR(50) NOT NULL, - petSpecies VARCHAR(50) NOT NULL, - petBreed VARCHAR(50) NOT NULL, - petAge INT NOT NULL, - petStatus VARCHAR(20) NOT NULL, - petPrice DECIMAL(10, 2) NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP -); - -CREATE TABLE IF NOT EXISTS adoption ( - adoptionId BIGINT AUTO_INCREMENT PRIMARY KEY, - petId BIGINT NOT NULL, - customerId BIGINT NOT NULL, - adoptionDate DATE NOT NULL, - adoptionStatus VARCHAR(20) NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - FOREIGN KEY (petId) REFERENCES pet(petId), - FOREIGN KEY (customerId) REFERENCES customer(customerId) -); - -CREATE TABLE IF NOT EXISTS supplier ( - supId BIGINT AUTO_INCREMENT PRIMARY KEY, - supCompany VARCHAR(100) NOT NULL, - supContactFirstName VARCHAR(50) NOT NULL, - supContactLastName VARCHAR(50) NOT NULL, - supEmail VARCHAR(100) NOT NULL, - supPhone VARCHAR(20) NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP -); - -CREATE TABLE IF NOT EXISTS category ( - categoryId BIGINT AUTO_INCREMENT PRIMARY KEY, - categoryName VARCHAR(100) NOT NULL, - categoryType VARCHAR(50) NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP -); - -CREATE TABLE IF NOT EXISTS product ( - prodId BIGINT AUTO_INCREMENT PRIMARY KEY, - prodName VARCHAR(100) NOT NULL, - prodPrice DECIMAL(10, 2) NOT NULL, - categoryId BIGINT NOT NULL, - prodDesc TEXT, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - FOREIGN KEY (categoryId) REFERENCES category(categoryId) -); - -CREATE TABLE IF NOT EXISTS productSupplier ( - supId BIGINT NOT NULL, - prodId BIGINT NOT NULL, - cost DECIMAL(10, 2) NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - PRIMARY KEY (supId, prodId), - FOREIGN KEY (supId) REFERENCES supplier(supId), - FOREIGN KEY (prodId) REFERENCES product(prodId) -); - -CREATE TABLE IF NOT EXISTS inventory ( - inventoryId BIGINT AUTO_INCREMENT PRIMARY KEY, - prodId BIGINT NOT NULL, - quantity INT DEFAULT 0 NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - FOREIGN KEY (prodId) REFERENCES product(prodId) -); - -CREATE TABLE IF NOT EXISTS service ( - serviceId BIGINT AUTO_INCREMENT PRIMARY KEY, - serviceName VARCHAR(100) NOT NULL, - serviceDesc TEXT, - serviceDuration INT NOT NULL, - servicePrice DECIMAL(10, 2) NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP -); - -CREATE TABLE IF NOT EXISTS appointment ( - appointmentId BIGINT AUTO_INCREMENT PRIMARY KEY, - serviceId BIGINT NOT NULL, - customerId BIGINT NOT NULL, - appointmentDate DATE NOT NULL, - appointmentTime TIME NOT NULL, - appointmentStatus VARCHAR(20) NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - FOREIGN KEY (serviceId) REFERENCES service(serviceId), - FOREIGN KEY (customerId) REFERENCES customer(customerId) -); - -CREATE TABLE IF NOT EXISTS appointmentPet ( - appointmentId BIGINT NOT NULL, - petId BIGINT NOT NULL, - PRIMARY KEY (appointmentId, petId), - FOREIGN KEY (appointmentId) REFERENCES appointment(appointmentId), - FOREIGN KEY (petId) REFERENCES pet(petId) -); - -CREATE TABLE IF NOT EXISTS sale ( - saleId BIGINT AUTO_INCREMENT PRIMARY KEY, - saleDate DATETIME NOT NULL, - totalAmount DECIMAL(10, 2) NOT NULL, - paymentMethod VARCHAR(50) NOT NULL, - employeeId BIGINT NOT NULL, - storeId BIGINT NOT NULL, - customerId BIGINT NULL, - isRefund BOOLEAN DEFAULT FALSE NOT NULL, - originalSaleId BIGINT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - FOREIGN KEY (employeeId) REFERENCES employee(employeeId), - FOREIGN KEY (storeId) REFERENCES storeLocation(storeId), - FOREIGN KEY (customerId) REFERENCES customer(customerId), - FOREIGN KEY (originalSaleId) REFERENCES sale(saleId) -); - -CREATE TABLE IF NOT EXISTS saleItem ( - saleItemId BIGINT AUTO_INCREMENT PRIMARY KEY, - saleId BIGINT NOT NULL, - prodId BIGINT NOT NULL, - quantity INT NOT NULL, - unitPrice DECIMAL(10, 2) NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - FOREIGN KEY (saleId) REFERENCES sale(saleId), - FOREIGN KEY (prodId) REFERENCES product(prodId) -); - -CREATE TABLE IF NOT EXISTS purchaseOrder ( - purchaseOrderId BIGINT AUTO_INCREMENT PRIMARY KEY, - supId BIGINT NOT NULL, - orderDate DATE NOT NULL, - status VARCHAR(50) NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - FOREIGN KEY (supId) REFERENCES supplier(supId) -); - -CREATE TABLE IF NOT EXISTS activityLog ( - logId BIGINT AUTO_INCREMENT PRIMARY KEY, - employeeId BIGINT NOT NULL, - activity TEXT NOT NULL, - logTimestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, - FOREIGN KEY (employeeId) REFERENCES employee(employeeId) -); - -CREATE TABLE IF NOT EXISTS users ( - id BIGINT AUTO_INCREMENT PRIMARY KEY, - username VARCHAR(50) UNIQUE NOT NULL, - password VARCHAR(255) NOT NULL, - email VARCHAR(100) UNIQUE, - fullName VARCHAR(100), - avatarUrl VARCHAR(255), - role VARCHAR(20) NOT NULL, - active BOOLEAN NOT NULL DEFAULT TRUE, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP -); - -CREATE TABLE IF NOT EXISTS refund ( - id BIGINT AUTO_INCREMENT PRIMARY KEY, - saleId BIGINT NOT NULL, - customerId BIGINT NOT NULL, - amount DECIMAL(10, 2) NOT NULL, - reason VARCHAR(500) NOT NULL, - status VARCHAR(20) NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - FOREIGN KEY (saleId) REFERENCES sale(saleId), - FOREIGN KEY (customerId) REFERENCES customer(customerId) -); - -CREATE TABLE IF NOT EXISTS conversation ( - id BIGINT AUTO_INCREMENT PRIMARY KEY, - customerId BIGINT NOT NULL, - staffId BIGINT, - status VARCHAR(20) DEFAULT 'OPEN', - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - FOREIGN KEY (customerId) REFERENCES customer(customerId), - FOREIGN KEY (staffId) REFERENCES users(id) -); - -CREATE TABLE IF NOT EXISTS message ( - id BIGINT AUTO_INCREMENT PRIMARY KEY, - conversationId BIGINT NOT NULL, - senderId BIGINT NOT NULL, - content TEXT NOT NULL, - timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - isRead BOOLEAN DEFAULT FALSE, - FOREIGN KEY (conversationId) REFERENCES conversation(id), - FOREIGN KEY (senderId) REFERENCES users(id) -); - --- Add foreign keys for user_id linkage -ALTER TABLE employee ADD CONSTRAINT fk_employee_user_id FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL; -ALTER TABLE customer ADD CONSTRAINT fk_customer_user_id FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL; From a12e23a713d922b61eebfba8af5eca3183ae068c Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Tue, 10 Mar 2026 16:44:32 -0600 Subject: [PATCH 52/84] Clean imports --- .../java/com/petshop/backend/service/AppointmentService.java | 1 - src/main/java/com/petshop/backend/service/SaleService.java | 1 - 2 files changed, 2 deletions(-) diff --git a/src/main/java/com/petshop/backend/service/AppointmentService.java b/src/main/java/com/petshop/backend/service/AppointmentService.java index afc3d600..d2fa5eaf 100644 --- a/src/main/java/com/petshop/backend/service/AppointmentService.java +++ b/src/main/java/com/petshop/backend/service/AppointmentService.java @@ -32,7 +32,6 @@ import java.time.LocalTime; import java.util.ArrayList; import java.util.HashSet; import java.util.List; -import java.util.Locale; import java.util.Set; import java.util.stream.Collectors; diff --git a/src/main/java/com/petshop/backend/service/SaleService.java b/src/main/java/com/petshop/backend/service/SaleService.java index afc9d068..55024b0c 100644 --- a/src/main/java/com/petshop/backend/service/SaleService.java +++ b/src/main/java/com/petshop/backend/service/SaleService.java @@ -9,7 +9,6 @@ import com.petshop.backend.repository.*; import com.petshop.backend.util.AuthenticationHelper; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; -import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; From 319293a59d38a9f7be26e0ce5b54f97f77a37048 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Tue, 10 Mar 2026 17:15:26 -0600 Subject: [PATCH 53/84] Fix backend validation --- pom.xml | 1 + .../backend/config/DataInitializer.java | 9 +++-- .../backend/controller/AuthController.java | 8 +++++ .../exception/GlobalExceptionHandler.java | 11 ++++++ .../service/StoreAssignmentService.java | 34 +++++++++++++++++++ 5 files changed, 60 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/petshop/backend/service/StoreAssignmentService.java diff --git a/pom.xml b/pom.xml index 1803f3ec..4357996d 100644 --- a/pom.xml +++ b/pom.xml @@ -130,6 +130,7 @@ docker-compose.dev.yml down -v + --remove-orphans diff --git a/src/main/java/com/petshop/backend/config/DataInitializer.java b/src/main/java/com/petshop/backend/config/DataInitializer.java index a9dd4b15..cff00f6c 100644 --- a/src/main/java/com/petshop/backend/config/DataInitializer.java +++ b/src/main/java/com/petshop/backend/config/DataInitializer.java @@ -2,6 +2,7 @@ package com.petshop.backend.config; import com.petshop.backend.entity.User; import com.petshop.backend.repository.UserRepository; +import com.petshop.backend.service.StoreAssignmentService; import com.petshop.backend.service.UserBusinessLinkageService; import org.springframework.boot.CommandLineRunner; import org.springframework.security.crypto.password.PasswordEncoder; @@ -13,11 +14,13 @@ public class DataInitializer implements CommandLineRunner { private final UserRepository userRepository; private final PasswordEncoder passwordEncoder; private final UserBusinessLinkageService userBusinessLinkageService; + private final StoreAssignmentService storeAssignmentService; - public DataInitializer(UserRepository userRepository, PasswordEncoder passwordEncoder, UserBusinessLinkageService userBusinessLinkageService) { + public DataInitializer(UserRepository userRepository, PasswordEncoder passwordEncoder, UserBusinessLinkageService userBusinessLinkageService, StoreAssignmentService storeAssignmentService) { this.userRepository = userRepository; this.passwordEncoder = passwordEncoder; this.userBusinessLinkageService = userBusinessLinkageService; + this.storeAssignmentService = storeAssignmentService; } @Override @@ -62,7 +65,7 @@ public class DataInitializer implements CommandLineRunner { } } // Ensure linked employee - userBusinessLinkageService.ensureLinkedEmployee(admin); + storeAssignmentService.assignStoreIfMissing(userBusinessLinkageService.ensureLinkedEmployee(admin), 1L); User staff = userRepository.findByUsername("staff").orElse(null); if (staff == null) { @@ -102,7 +105,7 @@ public class DataInitializer implements CommandLineRunner { } } // Ensure linked employee - userBusinessLinkageService.ensureLinkedEmployee(staff); + storeAssignmentService.assignStoreIfMissing(userBusinessLinkageService.ensureLinkedEmployee(staff), 1L); User customer = userRepository.findByUsername("customer").orElse(null); if (customer == null) { diff --git a/src/main/java/com/petshop/backend/controller/AuthController.java b/src/main/java/com/petshop/backend/controller/AuthController.java index 7aa04307..b5bc1900 100644 --- a/src/main/java/com/petshop/backend/controller/AuthController.java +++ b/src/main/java/com/petshop/backend/controller/AuthController.java @@ -17,6 +17,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.DisabledException; +import org.springframework.security.authentication.InternalAuthenticationServiceException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; @@ -126,6 +127,13 @@ public class AuthController { Map error = new HashMap<>(); error.put("message", "Invalid username or password"); return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(error); + } catch (InternalAuthenticationServiceException e) { + if (e.getCause() instanceof DisabledException disabledException) { + Map error = new HashMap<>(); + error.put("message", disabledException.getMessage()); + return ResponseEntity.status(HttpStatus.FORBIDDEN).body(error); + } + throw e; } catch (DisabledException e) { Map error = new HashMap<>(); error.put("message", e.getMessage()); diff --git a/src/main/java/com/petshop/backend/exception/GlobalExceptionHandler.java b/src/main/java/com/petshop/backend/exception/GlobalExceptionHandler.java index 40e95bbc..bfcfe03d 100644 --- a/src/main/java/com/petshop/backend/exception/GlobalExceptionHandler.java +++ b/src/main/java/com/petshop/backend/exception/GlobalExceptionHandler.java @@ -2,6 +2,7 @@ package com.petshop.backend.exception; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.dao.DataIntegrityViolationException; import org.springframework.validation.FieldError; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; @@ -71,6 +72,16 @@ public class GlobalExceptionHandler { return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error); } + @ExceptionHandler(DataIntegrityViolationException.class) + public ResponseEntity handleDataIntegrityViolationException(DataIntegrityViolationException ex) { + ErrorResponse error = new ErrorResponse( + HttpStatus.BAD_REQUEST.value(), + "Operation violates existing data relationships", + LocalDateTime.now() + ); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error); + } + @ExceptionHandler(Exception.class) public ResponseEntity handleGenericException(Exception ex) { ErrorResponse error = new ErrorResponse( diff --git a/src/main/java/com/petshop/backend/service/StoreAssignmentService.java b/src/main/java/com/petshop/backend/service/StoreAssignmentService.java new file mode 100644 index 00000000..31cc18d5 --- /dev/null +++ b/src/main/java/com/petshop/backend/service/StoreAssignmentService.java @@ -0,0 +1,34 @@ +package com.petshop.backend.service; + +import com.petshop.backend.entity.Employee; +import com.petshop.backend.entity.EmployeeStore; +import com.petshop.backend.entity.StoreLocation; +import com.petshop.backend.exception.ResourceNotFoundException; +import com.petshop.backend.repository.EmployeeStoreRepository; +import com.petshop.backend.repository.StoreRepository; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class StoreAssignmentService { + + private final EmployeeStoreRepository employeeStoreRepository; + private final StoreRepository storeRepository; + + public StoreAssignmentService(EmployeeStoreRepository employeeStoreRepository, StoreRepository storeRepository) { + this.employeeStoreRepository = employeeStoreRepository; + this.storeRepository = storeRepository; + } + + @Transactional + public void assignStoreIfMissing(Employee employee, Long storeId) { + if (employeeStoreRepository.findByEmployeeEmployeeId(employee.getEmployeeId()).isPresent()) { + return; + } + + StoreLocation store = storeRepository.findById(storeId) + .orElseThrow(() -> new ResourceNotFoundException("Store not found with id: " + storeId)); + + employeeStoreRepository.save(new EmployeeStore(employee, store)); + } +} From d874dd1af8c65622c0fdf99438c86377650c2672 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Tue, 10 Mar 2026 18:37:00 -0600 Subject: [PATCH 54/84] Fix postman collection --- petshop-api.postman_collection.json | 2450 ++++++++++++++++++++++++--- 1 file changed, 2244 insertions(+), 206 deletions(-) diff --git a/petshop-api.postman_collection.json b/petshop-api.postman_collection.json index 4418bb47..b0b9d60d 100644 --- a/petshop-api.postman_collection.json +++ b/petshop-api.postman_collection.json @@ -21,6 +21,102 @@ { "key": "adminToken", "value": "" + }, + { + "key": "petId", + "value": "" + }, + { + "key": "productId", + "value": "" + }, + { + "key": "saleId", + "value": "" + }, + { + "key": "customerSaleId", + "value": "" + }, + { + "key": "serviceId", + "value": "1" + }, + { + "key": "categoryId", + "value": "1" + }, + { + "key": "appointmentId", + "value": "" + }, + { + "key": "adoptionId", + "value": "" + }, + { + "key": "refundId", + "value": "" + }, + { + "key": "conversationId", + "value": "" + }, + { + "key": "customerId", + "value": "1" + }, + { + "key": "disposableCustomerId", + "value": "" + }, + { + "key": "userId", + "value": "" + }, + { + "key": "storeId", + "value": "1" + }, + { + "key": "inventoryId", + "value": "1" + }, + { + "key": "supplierId", + "value": "1" + }, + { + "key": "avatarFile", + "value": "/tmp/petshop-avatar.png" + }, + { + "key": "bulkPetId", + "value": "" + }, + { + "key": "bulkServiceId", + "value": "" + }, + { + "key": "bulkCategoryId", + "value": "" + }, + { + "key": "bulkCustomerId", + "value": "" + }, + { + "key": "bulkUserId", + "value": "" + }, + { + "key": "bulkStoreId", + "value": "" + }, + { + "key": "bulkInventoryId", + "value": "" } ], "item": [ @@ -38,7 +134,20 @@ "value": "application/json" } ] - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] } ] }, @@ -58,9 +167,24 @@ ], "body": { "mode": "raw", - "raw": "{\n \"username\": \"newcustomer\",\n \"password\": \"password123\",\n \"email\": \"new@example.com\",\n \"fullName\": \"New Customer\"\n}" + "raw": "{\n \"username\": \"newcustomer{{$timestamp}}\",\n \"password\": \"password123\",\n \"email\": \"new{{$timestamp}}@example.com\",\n \"fullName\": \"New Customer\"\n}" } - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 201', function () {", + " pm.response.to.have.status(201);", + "});", + "var jsonData = pm.response.json();", + "if (jsonData.token) pm.collectionVariables.set('customerToken', jsonData.token);" + ] + } + } + ] }, { "name": "Login as Customer", @@ -82,15 +206,13 @@ { "listen": "test", "script": { + "type": "text/javascript", "exec": [ "pm.test('Status code is 200', function () {", " pm.response.to.have.status(200);", "});", - "", "var jsonData = pm.response.json();", - "if (jsonData.token) {", - " pm.collectionVariables.set('customerToken', jsonData.token);", - "}" + "if (jsonData.token) pm.collectionVariables.set('customerToken', jsonData.token);" ] } } @@ -116,15 +238,13 @@ { "listen": "test", "script": { + "type": "text/javascript", "exec": [ "pm.test('Status code is 200', function () {", " pm.response.to.have.status(200);", "});", - "", "var jsonData = pm.response.json();", - "if (jsonData.token) {", - " pm.collectionVariables.set('staffToken', jsonData.token);", - "}" + "if (jsonData.token) pm.collectionVariables.set('staffToken', jsonData.token);" ] } } @@ -150,15 +270,13 @@ { "listen": "test", "script": { + "type": "text/javascript", "exec": [ "pm.test('Status code is 200', function () {", " pm.response.to.have.status(200);", "});", - "", "var jsonData = pm.response.json();", - "if (jsonData.token) {", - " pm.collectionVariables.set('adminToken', jsonData.token);", - "}" + "if (jsonData.token) pm.collectionVariables.set('adminToken', jsonData.token);" ] } } @@ -180,7 +298,20 @@ "type": "text" } ] - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] }, { "name": "Update My Profile", @@ -202,7 +333,20 @@ "mode": "raw", "raw": "{\n \"fullName\": \"Updated Name\",\n \"email\": \"updated@example.com\"\n}" } - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] }, { "name": "Upload Avatar", @@ -222,11 +366,26 @@ { "key": "avatar", "type": "file", - "src": [] + "src": "{{avatarFile}}" } ] } - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});", + "var jsonData = pm.response.json();", + "pm.expect(jsonData.avatarUrl).to.be.a('string');" + ] + } + } + ] }, { "name": "Get Avatar", @@ -244,7 +403,22 @@ "type": "text" } ] - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});", + "var jsonData = pm.response.json();", + "pm.expect(jsonData.avatarUrl).to.be.a('string');" + ] + } + } + ] }, { "name": "Delete Avatar", @@ -262,7 +436,20 @@ "type": "text" } ] - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] }, { "name": "Logout", @@ -280,7 +467,20 @@ "type": "text" } ] - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] } ] }, @@ -296,9 +496,27 @@ { "key": "Content-Type", "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" } ] - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] }, { "name": "Get Pet by ID", @@ -309,9 +527,27 @@ { "key": "Content-Type", "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" } ] - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] }, { "name": "Get Pets Dropdown", @@ -322,9 +558,27 @@ { "key": "Content-Type", "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" } ] - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] }, { "name": "Create Pet", @@ -344,15 +598,35 @@ ], "body": { "mode": "raw", - "raw": "{\n \"petName\": \"Buddy\",\n \"petSpecies\": \"Dog\",\n \"petBreed\": \"Labrador\",\n \"petAge\": 2,\n \"petStatus\": \"Available\",\n \"petPrice\": 500.0\n}" + "raw": "{\n \"petName\": \"Postman Pet\",\n \"petSpecies\": \"Dog\",\n \"petBreed\": \"Mixed\",\n \"petAge\": 2,\n \"petStatus\": \"Available\",\n \"petPrice\": 350.00\n}", + "options": { + "raw": { + "language": "json" + } + } } - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 201', function () {", + " pm.response.to.have.status(201);", + "});", + "var jsonData = pm.response.json();", + "if (jsonData.petId !== undefined) pm.collectionVariables.set('petId', jsonData.petId);" + ] + } + } + ] }, { "name": "Update Pet", "request": { "method": "PUT", - "url": "{{baseUrl}}/api/v1/pets/1", + "url": "{{baseUrl}}/api/v1/pets/{{petId}}", "header": [ { "key": "Content-Type", @@ -366,15 +640,33 @@ ], "body": { "mode": "raw", - "raw": "{\n \"petName\": \"Buddy\",\n \"petSpecies\": \"Dog\",\n \"petBreed\": \"Labrador\",\n \"petAge\": 3,\n \"petStatus\": \"Available\",\n \"petPrice\": 500.0\n}" + "raw": "{\n \"petName\": \"Postman Pet Updated\",\n \"petSpecies\": \"Dog\",\n \"petBreed\": \"Mixed\",\n \"petAge\": 3,\n \"petStatus\": \"Available\",\n \"petPrice\": 375.00\n}", + "options": { + "raw": { + "language": "json" + } + } } - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] }, { "name": "Delete Pet", "request": { "method": "DELETE", - "url": "{{baseUrl}}/api/v1/pets/1", + "url": "{{baseUrl}}/api/v1/pets/{{petId}}", "header": [ { "key": "Content-Type", @@ -386,7 +678,62 @@ "type": "text" } ] - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 204', function () {", + " pm.response.to.have.status(204);", + "});" + ] + } + } + ] + }, + { + "name": "Create Pet For Bulk Delete", + "request": { + "method": "POST", + "url": "{{baseUrl}}/api/v1/pets", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"petName\": \"Postman Pet\",\n \"petSpecies\": \"Dog\",\n \"petBreed\": \"Mixed\",\n \"petAge\": 2,\n \"petStatus\": \"Available\",\n \"petPrice\": 350.00\n}", + "options": { + "raw": { + "language": "json" + } + } + } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 201', function () {", + " pm.response.to.have.status(201);", + "});", + "var jsonData = pm.response.json();", + "if (jsonData.petId !== undefined) pm.collectionVariables.set('bulkPetId', jsonData.petId);" + ] + } + } + ] }, { "name": "Bulk Delete Pets", @@ -406,9 +753,22 @@ ], "body": { "mode": "raw", - "raw": "{\n \"ids\": [\n 1,\n 2\n ]\n}" + "raw": "{\n \"ids\": [\n {{bulkPetId}}\n ]\n}" } - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 204', function () {", + " pm.response.to.have.status(204);", + "});" + ] + } + } + ] } ] }, @@ -424,9 +784,27 @@ { "key": "Content-Type", "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" } ] - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] }, { "name": "Get Product by ID", @@ -437,9 +815,27 @@ { "key": "Content-Type", "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" } ] - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] }, { "name": "Get Products Dropdown", @@ -450,9 +846,27 @@ { "key": "Content-Type", "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" } ] - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] }, { "name": "Create Product", @@ -472,15 +886,35 @@ ], "body": { "mode": "raw", - "raw": "{\n \"prodName\": \"Dog Food\",\n \"categoryId\": 1,\n \"prodDesc\": \"Premium\",\n \"prodPrice\": 50.0\n}" + "raw": "{\n \"prodName\": \"Postman Product\",\n \"categoryId\": {{categoryId}},\n \"prodDesc\": \"Created from Postman\",\n \"prodPrice\": 19.99\n}", + "options": { + "raw": { + "language": "json" + } + } } - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 201', function () {", + " pm.response.to.have.status(201);", + "});", + "var jsonData = pm.response.json();", + "if (jsonData.prodId !== undefined) pm.collectionVariables.set('productId', jsonData.prodId);" + ] + } + } + ] }, { "name": "Update Product", "request": { "method": "PUT", - "url": "{{baseUrl}}/api/v1/products/1", + "url": "{{baseUrl}}/api/v1/products/{{productId}}", "header": [ { "key": "Content-Type", @@ -494,15 +928,33 @@ ], "body": { "mode": "raw", - "raw": "{\n \"prodName\": \"Dog Food\",\n \"categoryId\": 1,\n \"prodDesc\": \"Premium\",\n \"prodPrice\": 55.0\n}" + "raw": "{\n \"prodName\": \"Postman Product Updated\",\n \"categoryId\": {{categoryId}},\n \"prodDesc\": \"Updated from Postman\",\n \"prodPrice\": 24.99\n}", + "options": { + "raw": { + "language": "json" + } + } } - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] }, { "name": "Delete Product", "request": { "method": "DELETE", - "url": "{{baseUrl}}/api/v1/products/1", + "url": "{{baseUrl}}/api/v1/products/{{productId}}", "header": [ { "key": "Content-Type", @@ -510,11 +962,24 @@ }, { "key": "Authorization", - "value": "Bearer {{adminToken}}", + "value": "Bearer {{staffToken}}", "type": "text" } ] - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 403', function () {", + " pm.response.to.have.status(403);", + "});" + ] + } + } + ] }, { "name": "Bulk Delete Products", @@ -528,7 +993,7 @@ }, { "key": "Authorization", - "value": "Bearer {{adminToken}}", + "value": "Bearer {{staffToken}}", "type": "text" } ], @@ -536,7 +1001,20 @@ "mode": "raw", "raw": "{\n \"ids\": [\n 1\n ]\n}" } - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 403', function () {", + " pm.response.to.have.status(403);", + "});" + ] + } + } + ] } ] }, @@ -552,9 +1030,27 @@ { "key": "Content-Type", "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" } ] - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] }, { "name": "Get Sale by ID", @@ -565,9 +1061,27 @@ { "key": "Content-Type", "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" } ] - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] }, { "name": "Create Sale", @@ -589,7 +1103,25 @@ "mode": "raw", "raw": "{\n \"storeId\": 1,\n \"paymentMethod\": \"Card\",\n \"customerId\": 1,\n \"items\": [\n {\n \"prodId\": 1,\n \"quantity\": 2\n },\n {\n \"prodId\": 2,\n \"quantity\": 1\n }\n ],\n \"isRefund\": false\n}" } - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 201', function () {", + " pm.response.to.have.status(201);", + "});", + "var jsonData = pm.response.json();", + "if (jsonData.saleId !== undefined) {", + " pm.collectionVariables.set('saleId', jsonData.saleId);", + " pm.collectionVariables.set('customerSaleId', jsonData.saleId);", + "}" + ] + } + } + ] } ] }, @@ -605,9 +1137,27 @@ { "key": "Content-Type", "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" } ] - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] }, { "name": "Get Service by ID", @@ -618,9 +1168,27 @@ { "key": "Content-Type", "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" } ] - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] }, { "name": "Get Services Dropdown", @@ -631,9 +1199,27 @@ { "key": "Content-Type", "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" } ] - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] }, { "name": "Create Service", @@ -653,15 +1239,35 @@ ], "body": { "mode": "raw", - "raw": "{\n \"serviceName\": \"Grooming\",\n \"serviceDesc\": \"Full grooming\",\n \"servicePrice\": 75.0\n}" + "raw": "{\n \"serviceName\": \"Postman Service\",\n \"serviceDesc\": \"Created from Postman\",\n \"serviceDuration\": 30,\n \"servicePrice\": 25.00\n}", + "options": { + "raw": { + "language": "json" + } + } } - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 201', function () {", + " pm.response.to.have.status(201);", + "});", + "var jsonData = pm.response.json();", + "if (jsonData.serviceId !== undefined) pm.collectionVariables.set('serviceId', jsonData.serviceId);" + ] + } + } + ] }, { "name": "Update Service", "request": { "method": "PUT", - "url": "{{baseUrl}}/api/v1/services/1", + "url": "{{baseUrl}}/api/v1/services/{{serviceId}}", "header": [ { "key": "Content-Type", @@ -675,15 +1281,33 @@ ], "body": { "mode": "raw", - "raw": "{\n \"serviceName\": \"Grooming\",\n \"serviceDesc\": \"Full grooming\",\n \"servicePrice\": 80.0\n}" + "raw": "{\n \"serviceName\": \"Postman Service Updated\",\n \"serviceDesc\": \"Updated from Postman\",\n \"serviceDuration\": 30,\n \"servicePrice\": 30.00\n}", + "options": { + "raw": { + "language": "json" + } + } } - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] }, { "name": "Delete Service", "request": { "method": "DELETE", - "url": "{{baseUrl}}/api/v1/services/1", + "url": "{{baseUrl}}/api/v1/services/{{serviceId}}", "header": [ { "key": "Content-Type", @@ -695,7 +1319,62 @@ "type": "text" } ] - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 204', function () {", + " pm.response.to.have.status(204);", + "});" + ] + } + } + ] + }, + { + "name": "Create Service For Bulk Delete", + "request": { + "method": "POST", + "url": "{{baseUrl}}/api/v1/services", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"serviceName\": \"Postman Service\",\n \"serviceDesc\": \"Created from Postman\",\n \"serviceDuration\": 30,\n \"servicePrice\": 25.00\n}", + "options": { + "raw": { + "language": "json" + } + } + } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 201', function () {", + " pm.response.to.have.status(201);", + "});", + "var jsonData = pm.response.json();", + "if (jsonData.serviceId !== undefined) pm.collectionVariables.set('bulkServiceId', jsonData.serviceId);" + ] + } + } + ] }, { "name": "Bulk Delete Services", @@ -715,9 +1394,22 @@ ], "body": { "mode": "raw", - "raw": "{\n \"ids\": [\n 1\n ]\n}" + "raw": "{\n \"ids\": [\n {{bulkServiceId}}\n ]\n}" } - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 204', function () {", + " pm.response.to.have.status(204);", + "});" + ] + } + } + ] } ] }, @@ -733,9 +1425,27 @@ { "key": "Content-Type", "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" } ] - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] }, { "name": "Get Category by ID", @@ -746,9 +1456,27 @@ { "key": "Content-Type", "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" } ] - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] }, { "name": "Get Categories Dropdown", @@ -759,9 +1487,27 @@ { "key": "Content-Type", "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" } ] - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] }, { "name": "Create Category", @@ -781,15 +1527,35 @@ ], "body": { "mode": "raw", - "raw": "{\n \"categoryName\": \"Dog Supplies\"\n}" + "raw": "{\n \"categoryName\": \"Postman Category\",\n \"categoryType\": \"Product\"\n}", + "options": { + "raw": { + "language": "json" + } + } } - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 201', function () {", + " pm.response.to.have.status(201);", + "});", + "var jsonData = pm.response.json();", + "if (jsonData.categoryId !== undefined) pm.collectionVariables.set('categoryId', jsonData.categoryId);" + ] + } + } + ] }, { "name": "Update Category", "request": { "method": "PUT", - "url": "{{baseUrl}}/api/v1/categories/1", + "url": "{{baseUrl}}/api/v1/categories/{{categoryId}}", "header": [ { "key": "Content-Type", @@ -803,15 +1569,33 @@ ], "body": { "mode": "raw", - "raw": "{\n \"categoryName\": \"Dog Supplies\"\n}" + "raw": "{\n \"categoryName\": \"Postman Category Updated\",\n \"categoryType\": \"Product\"\n}", + "options": { + "raw": { + "language": "json" + } + } } - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] }, { "name": "Delete Category", "request": { "method": "DELETE", - "url": "{{baseUrl}}/api/v1/categories/1", + "url": "{{baseUrl}}/api/v1/categories/{{categoryId}}", "header": [ { "key": "Content-Type", @@ -823,7 +1607,62 @@ "type": "text" } ] - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 204', function () {", + " pm.response.to.have.status(204);", + "});" + ] + } + } + ] + }, + { + "name": "Create Category For Bulk Delete", + "request": { + "method": "POST", + "url": "{{baseUrl}}/api/v1/categories", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"categoryName\": \"Postman Category\",\n \"categoryType\": \"Product\"\n}", + "options": { + "raw": { + "language": "json" + } + } + } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 201', function () {", + " pm.response.to.have.status(201);", + "});", + "var jsonData = pm.response.json();", + "if (jsonData.categoryId !== undefined) pm.collectionVariables.set('bulkCategoryId', jsonData.categoryId);" + ] + } + } + ] }, { "name": "Bulk Delete Categories", @@ -843,9 +1682,22 @@ ], "body": { "mode": "raw", - "raw": "{\n \"ids\": [\n 1\n ]\n}" + "raw": "{\n \"ids\": [\n {{bulkCategoryId}}\n ]\n}" } - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 204', function () {", + " pm.response.to.have.status(204);", + "});" + ] + } + } + ] } ] }, @@ -856,14 +1708,32 @@ "name": "Check Appointment Availability", "request": { "method": "GET", - "url": "{{baseUrl}}/api/v1/appointments/availability?storeId=1&serviceId=1&date=2026-03-15", + "url": "{{baseUrl}}/api/v1/appointments/availability?storeId=1&serviceId=1&date=2026-12-20", "header": [ { "key": "Content-Type", "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" } ] - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] }, { "name": "List Appointments", @@ -877,11 +1747,24 @@ }, { "key": "Authorization", - "value": "Bearer {{customerToken}}", + "value": "Bearer {{staffToken}}", "type": "text" } ] - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] }, { "name": "Get Appointment", @@ -895,11 +1778,24 @@ }, { "key": "Authorization", - "value": "Bearer {{customerToken}}", + "value": "Bearer {{staffToken}}", "type": "text" } ] - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] }, { "name": "Create Appointment", @@ -913,21 +1809,41 @@ }, { "key": "Authorization", - "value": "Bearer {{customerToken}}", + "value": "Bearer {{staffToken}}", "type": "text" } ], "body": { "mode": "raw", - "raw": "{\n \"customerId\": 1,\n \"serviceId\": 1,\n \"appointmentDate\": \"2026-03-15\",\n \"appointmentTime\": \"10:30:00\",\n \"appointmentStatus\": \"Booked\",\n \"petIds\": [1, 2]\n}" + "raw": "{\n \"customerId\": 1,\n \"storeId\": 1,\n \"serviceId\": 1,\n \"appointmentDate\": \"2026-12-20\",\n \"appointmentTime\": \"10:00:00\",\n \"appointmentStatus\": \"Booked\",\n \"petIds\": [1]\n}", + "options": { + "raw": { + "language": "json" + } + } } - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 201', function () {", + " pm.response.to.have.status(201);", + "});", + "var jsonData = pm.response.json();", + "if (jsonData.appointmentId !== undefined) pm.collectionVariables.set('appointmentId', jsonData.appointmentId);" + ] + } + } + ] }, { "name": "Update Appointment", "request": { "method": "PUT", - "url": "{{baseUrl}}/api/v1/appointments/1", + "url": "{{baseUrl}}/api/v1/appointments/{{appointmentId}}", "header": [ { "key": "Content-Type", @@ -941,15 +1857,33 @@ ], "body": { "mode": "raw", - "raw": "{\n \"customerId\": 1,\n \"serviceId\": 2,\n \"appointmentDate\": \"2026-03-20\",\n \"appointmentTime\": \"14:00:00\",\n \"appointmentStatus\": \"Completed\",\n \"petIds\": [1]\n}" + "raw": "{\n \"customerId\": 1,\n \"storeId\": 1,\n \"serviceId\": 1,\n \"appointmentDate\": \"2026-12-20\",\n \"appointmentTime\": \"11:00:00\",\n \"appointmentStatus\": \"Booked\",\n \"petIds\": [1]\n}", + "options": { + "raw": { + "language": "json" + } + } } - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] }, { "name": "Delete Appointment", "request": { "method": "DELETE", - "url": "{{baseUrl}}/api/v1/appointments/1", + "url": "{{baseUrl}}/api/v1/appointments/{{appointmentId}}", "header": [ { "key": "Content-Type", @@ -961,7 +1895,20 @@ "type": "text" } ] - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 204', function () {", + " pm.response.to.have.status(204);", + "});" + ] + } + } + ] }, { "name": "Bulk Delete Appointments", @@ -975,7 +1922,7 @@ }, { "key": "Authorization", - "value": "Bearer {{adminToken}}", + "value": "Bearer {{staffToken}}", "type": "text" } ], @@ -983,7 +1930,20 @@ "mode": "raw", "raw": "{\n \"ids\": [\n 1\n ]\n}" } - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 403', function () {", + " pm.response.to.have.status(403);", + "});" + ] + } + } + ] } ] }, @@ -1002,11 +1962,24 @@ }, { "key": "Authorization", - "value": "Bearer {{customerToken}}", + "value": "Bearer {{staffToken}}", "type": "text" } ] - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] }, { "name": "Get Adoption", @@ -1020,11 +1993,24 @@ }, { "key": "Authorization", - "value": "Bearer {{customerToken}}", + "value": "Bearer {{staffToken}}", "type": "text" } ] - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] }, { "name": "Create Adoption", @@ -1038,21 +2024,41 @@ }, { "key": "Authorization", - "value": "Bearer {{customerToken}}", + "value": "Bearer {{staffToken}}", "type": "text" } ], "body": { "mode": "raw", - "raw": "{\n \"petId\": 1,\n \"customerId\": 1,\n \"adoptionDate\": \"2026-03-10\",\n \"adoptionStatus\": \"Pending\"\n}" + "raw": "{\n \"petId\": 3,\n \"customerId\": 1,\n \"adoptionDate\": \"2026-12-21\",\n \"adoptionStatus\": \"Pending\"\n}", + "options": { + "raw": { + "language": "json" + } + } } - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 201', function () {", + " pm.response.to.have.status(201);", + "});", + "var jsonData = pm.response.json();", + "if (jsonData.adoptionId !== undefined) pm.collectionVariables.set('adoptionId', jsonData.adoptionId);" + ] + } + } + ] }, { "name": "Update Adoption", "request": { "method": "PUT", - "url": "{{baseUrl}}/api/v1/adoptions/1", + "url": "{{baseUrl}}/api/v1/adoptions/{{adoptionId}}", "header": [ { "key": "Content-Type", @@ -1066,15 +2072,33 @@ ], "body": { "mode": "raw", - "raw": "{\n \"petId\": 1,\n \"customerId\": 1,\n \"adoptionDate\": \"2026-03-10\",\n \"adoptionStatus\": \"Completed\"\n}" + "raw": "{\n \"petId\": 3,\n \"customerId\": 1,\n \"adoptionDate\": \"2026-12-22\",\n \"adoptionStatus\": \"Completed\"\n}", + "options": { + "raw": { + "language": "json" + } + } } - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] }, { "name": "Delete Adoption", "request": { "method": "DELETE", - "url": "{{baseUrl}}/api/v1/adoptions/1", + "url": "{{baseUrl}}/api/v1/adoptions/{{adoptionId}}", "header": [ { "key": "Content-Type", @@ -1086,7 +2110,20 @@ "type": "text" } ] - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 204', function () {", + " pm.response.to.have.status(204);", + "});" + ] + } + } + ] }, { "name": "Bulk Delete Adoptions", @@ -1100,7 +2137,7 @@ }, { "key": "Authorization", - "value": "Bearer {{adminToken}}", + "value": "Bearer {{staffToken}}", "type": "text" } ], @@ -1108,7 +2145,20 @@ "mode": "raw", "raw": "{\n \"ids\": [\n 1\n ]\n}" } - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 403', function () {", + " pm.response.to.have.status(403);", + "});" + ] + } + } + ] } ] }, @@ -1127,15 +2177,35 @@ }, { "key": "Authorization", - "value": "Bearer {{customerToken}}", + "value": "Bearer {{staffToken}}", "type": "text" } ], "body": { "mode": "raw", - "raw": "{\n \"saleId\": 1,\n \"reason\": \"Defective product\"\n}" + "raw": "{\n \"saleId\": 1,\n \"reason\": \"Defective product\"\n}", + "options": { + "raw": { + "language": "json" + } + } } - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 201', function () {", + " pm.response.to.have.status(201);", + "});", + "var jsonData = pm.response.json();", + "if (jsonData.id !== undefined) pm.collectionVariables.set('refundId', jsonData.id);" + ] + } + } + ] }, { "name": "List Refunds", @@ -1149,17 +2219,30 @@ }, { "key": "Authorization", - "value": "Bearer {{customerToken}}", + "value": "Bearer {{staffToken}}", "type": "text" } ] - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] }, { "name": "Get Refund", "request": { "method": "GET", - "url": "{{baseUrl}}/api/v1/refunds/1", + "url": "{{baseUrl}}/api/v1/refunds/{{refundId}}", "header": [ { "key": "Content-Type", @@ -1167,17 +2250,30 @@ }, { "key": "Authorization", - "value": "Bearer {{customerToken}}", + "value": "Bearer {{staffToken}}", "type": "text" } ] - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] }, { "name": "Update Refund", "request": { "method": "PUT", - "url": "{{baseUrl}}/api/v1/refunds/1", + "url": "{{baseUrl}}/api/v1/refunds/{{refundId}}", "header": [ { "key": "Content-Type", @@ -1191,15 +2287,33 @@ ], "body": { "mode": "raw", - "raw": "{\n \"status\": \"APPROVED\"\n}" + "raw": "{\n \"status\": \"APPROVED\"\n}", + "options": { + "raw": { + "language": "json" + } + } } - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] }, { "name": "Delete Refund", "request": { "method": "DELETE", - "url": "{{baseUrl}}/api/v1/refunds/1", + "url": "{{baseUrl}}/api/v1/refunds/{{refundId}}", "header": [ { "key": "Content-Type", @@ -1207,11 +2321,24 @@ }, { "key": "Authorization", - "value": "Bearer {{adminToken}}", + "value": "Bearer {{staffToken}}", "type": "text" } ] - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 403', function () {", + " pm.response.to.have.status(403);", + "});" + ] + } + } + ] } ] }, @@ -1238,7 +2365,22 @@ "mode": "raw", "raw": "{\n \"message\": \"I need help\"\n}" } - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 201', function () {", + " pm.response.to.have.status(201);", + "});", + "var jsonData = pm.response.json();", + "if (jsonData.id !== undefined) pm.collectionVariables.set('conversationId', jsonData.id);" + ] + } + } + ] }, { "name": "List Conversations", @@ -1252,17 +2394,30 @@ }, { "key": "Authorization", - "value": "Bearer {{customerToken}}", + "value": "Bearer {{staffToken}}", "type": "text" } ] - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] }, { "name": "Get Conversation", "request": { "method": "GET", - "url": "{{baseUrl}}/api/v1/chat/conversations/1", + "url": "{{baseUrl}}/api/v1/chat/conversations/{{conversationId}}", "header": [ { "key": "Content-Type", @@ -1274,13 +2429,26 @@ "type": "text" } ] - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] }, { "name": "Send Message", "request": { "method": "POST", - "url": "{{baseUrl}}/api/v1/chat/conversations/1/messages", + "url": "{{baseUrl}}/api/v1/chat/conversations/{{conversationId}}/messages", "header": [ { "key": "Content-Type", @@ -1288,21 +2456,39 @@ }, { "key": "Authorization", - "value": "Bearer {{customerToken}}", + "value": "Bearer {{staffToken}}", "type": "text" } ], "body": { "mode": "raw", - "raw": "{\n \"content\": \"Hello\"\n}" + "raw": "{\n \"content\": \"Reply from Postman\"\n}", + "options": { + "raw": { + "language": "json" + } + } } - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 201', function () {", + " pm.response.to.have.status(201);", + "});" + ] + } + } + ] }, { "name": "Get Messages", "request": { "method": "GET", - "url": "{{baseUrl}}/api/v1/chat/conversations/1/messages", + "url": "{{baseUrl}}/api/v1/chat/conversations/{{conversationId}}/messages", "header": [ { "key": "Content-Type", @@ -1314,7 +2500,20 @@ "type": "text" } ] - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] } ] }, @@ -1330,9 +2529,27 @@ { "key": "Content-Type", "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" } ] - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] }, { "name": "List Customers", @@ -1350,13 +2567,26 @@ "type": "text" } ] - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] }, { "name": "Get Customer", "request": { "method": "GET", - "url": "{{baseUrl}}/api/v1/customers/1", + "url": "{{baseUrl}}/api/v1/customers/{{customerId}}", "header": [ { "key": "Content-Type", @@ -1368,7 +2598,20 @@ "type": "text" } ] - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] }, { "name": "Create Customer", @@ -1388,15 +2631,35 @@ ], "body": { "mode": "raw", - "raw": "{\n \"firstName\": \"John\",\n \"lastName\": \"Doe\",\n \"email\": \"john.doe@example.com\",\n \"phone\": \"555-123-4567\"\n}" + "raw": "{\n \"firstName\": \"Postman\",\n \"lastName\": \"Customer\",\n \"email\": \"postman.customer.{{$guid}}@example.com\",\n \"phone\": \"555-100-2000\"\n}", + "options": { + "raw": { + "language": "json" + } + } } - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 201', function () {", + " pm.response.to.have.status(201);", + "});", + "var jsonData = pm.response.json();", + "if (jsonData.customerId !== undefined) pm.collectionVariables.set('customerId', jsonData.customerId);" + ] + } + } + ] }, { "name": "Update Customer", "request": { "method": "PUT", - "url": "{{baseUrl}}/api/v1/customers/1", + "url": "{{baseUrl}}/api/v1/customers/{{customerId}}", "header": [ { "key": "Content-Type", @@ -1410,15 +2673,33 @@ ], "body": { "mode": "raw", - "raw": "{\n \"firstName\": \"Jane\",\n \"lastName\": \"Doe\",\n \"email\": \"jane.doe@example.com\",\n \"phone\": \"555-987-6543\"\n}" + "raw": "{\n \"firstName\": \"Postman\",\n \"lastName\": \"Customer Updated\",\n \"email\": \"postman.customer.updated.{{$timestamp}}@example.com\",\n \"phone\": \"555-100-2001\"\n}", + "options": { + "raw": { + "language": "json" + } + } } - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] }, { "name": "Delete Customer", "request": { "method": "DELETE", - "url": "{{baseUrl}}/api/v1/customers/1", + "url": "{{baseUrl}}/api/v1/customers/{{customerId}}", "header": [ { "key": "Content-Type", @@ -1430,7 +2711,62 @@ "type": "text" } ] - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 204', function () {", + " pm.response.to.have.status(204);", + "});" + ] + } + } + ] + }, + { + "name": "Create Customer For Bulk Delete", + "request": { + "method": "POST", + "url": "{{baseUrl}}/api/v1/customers", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"firstName\": \"Postman\",\n \"lastName\": \"Customer\",\n \"email\": \"postman.customer.{{$guid}}@example.com\",\n \"phone\": \"555-100-2000\"\n}", + "options": { + "raw": { + "language": "json" + } + } + } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 201', function () {", + " pm.response.to.have.status(201);", + "});", + "var jsonData = pm.response.json();", + "if (jsonData.customerId !== undefined) pm.collectionVariables.set('bulkCustomerId', jsonData.customerId);" + ] + } + } + ] }, { "name": "Bulk Delete Customers", @@ -1450,9 +2786,22 @@ ], "body": { "mode": "raw", - "raw": "{\n \"ids\": [\n 1\n ]\n}" + "raw": "{\n \"ids\": [\n {{bulkCustomerId}}\n ]\n}" } - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 204', function () {", + " pm.response.to.have.status(204);", + "});" + ] + } + } + ] } ] }, @@ -1475,7 +2824,20 @@ "type": "text" } ] - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] }, { "name": "Get User", @@ -1493,7 +2855,20 @@ "type": "text" } ] - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] }, { "name": "Create User", @@ -1513,15 +2888,35 @@ ], "body": { "mode": "raw", - "raw": "{\n \"username\": \"newuser\",\n \"password\": \"password123\",\n \"fullName\": \"New User\",\n \"email\": \"newuser@petshop.com\",\n \"role\": \"STAFF\",\n \"active\": true\n}" + "raw": "{\n \"username\": \"postman_user_{{$guid}}\",\n \"password\": \"secret123\",\n \"fullName\": \"Postman User\",\n \"email\": \"postman.user.{{$guid}}@example.com\",\n \"role\": \"STAFF\",\n \"active\": true\n}", + "options": { + "raw": { + "language": "json" + } + } } - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 201', function () {", + " pm.response.to.have.status(201);", + "});", + "var jsonData = pm.response.json();", + "if (jsonData.id !== undefined) pm.collectionVariables.set('userId', jsonData.id);" + ] + } + } + ] }, { "name": "Update User", "request": { "method": "PUT", - "url": "{{baseUrl}}/api/v1/users/1", + "url": "{{baseUrl}}/api/v1/users/{{userId}}", "header": [ { "key": "Content-Type", @@ -1535,15 +2930,33 @@ ], "body": { "mode": "raw", - "raw": "{\n \"username\": \"user1\",\n \"password\": \"newpassword123\",\n \"fullName\": \"Updated User\",\n \"email\": \"user1@petshop.com\",\n \"role\": \"STAFF\",\n \"active\": true\n}" + "raw": "{\n \"username\": \"postman_user_{{$timestamp}}\",\n \"password\": \"secret123\",\n \"fullName\": \"Postman User Updated\",\n \"email\": \"postman.user.updated.{{$timestamp}}@example.com\",\n \"role\": \"STAFF\",\n \"active\": true\n}", + "options": { + "raw": { + "language": "json" + } + } } - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] }, { "name": "Delete User", "request": { "method": "DELETE", - "url": "{{baseUrl}}/api/v1/users/1", + "url": "{{baseUrl}}/api/v1/users/{{userId}}", "header": [ { "key": "Content-Type", @@ -1555,7 +2968,62 @@ "type": "text" } ] - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 204', function () {", + " pm.response.to.have.status(204);", + "});" + ] + } + } + ] + }, + { + "name": "Create User For Bulk Delete", + "request": { + "method": "POST", + "url": "{{baseUrl}}/api/v1/users", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"username\": \"postman_user_{{$guid}}\",\n \"password\": \"secret123\",\n \"fullName\": \"Postman User\",\n \"email\": \"postman.user.{{$guid}}@example.com\",\n \"role\": \"STAFF\",\n \"active\": true\n}", + "options": { + "raw": { + "language": "json" + } + } + } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 201', function () {", + " pm.response.to.have.status(201);", + "});", + "var jsonData = pm.response.json();", + "if (jsonData.id !== undefined) pm.collectionVariables.set('bulkUserId', jsonData.id);" + ] + } + } + ] }, { "name": "Bulk Delete Users", @@ -1575,9 +3043,27 @@ ], "body": { "mode": "raw", - "raw": "{\n \"ids\": [\n 1\n ]\n}" + "raw": "{\n \"ids\": [\n {{bulkUserId}}\n ]\n}", + "options": { + "raw": { + "language": "json" + } + } } - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 204', function () {", + " pm.response.to.have.status(204);", + "});" + ] + } + } + ] } ] }, @@ -1593,9 +3079,27 @@ { "key": "Content-Type", "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" } ] - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] }, { "name": "List Stores", @@ -1613,13 +3117,26 @@ "type": "text" } ] - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] }, { "name": "Get Store", "request": { "method": "GET", - "url": "{{baseUrl}}/api/v1/stores/1", + "url": "{{baseUrl}}/api/v1/stores/{{storeId}}", "header": [ { "key": "Content-Type", @@ -1631,7 +3148,20 @@ "type": "text" } ] - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] }, { "name": "Create Store", @@ -1651,15 +3181,35 @@ ], "body": { "mode": "raw", - "raw": "{\n \"storeName\": \"New Pet Shop\",\n \"address\": \"123 Main Street\",\n \"phone\": \"555-111-2222\",\n \"email\": \"newstore@petshop.com\"\n}" + "raw": "{\n \"storeName\": \"Postman Store {{$timestamp}}\",\n \"address\": \"100 Postman Ave\",\n \"phone\": \"555-200-3000\",\n \"email\": \"postman.store.{{$guid}}@example.com\"\n}", + "options": { + "raw": { + "language": "json" + } + } } - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 201', function () {", + " pm.response.to.have.status(201);", + "});", + "var jsonData = pm.response.json();", + "if (jsonData.storeId !== undefined) pm.collectionVariables.set('storeId', jsonData.storeId);" + ] + } + } + ] }, { "name": "Update Store", "request": { "method": "PUT", - "url": "{{baseUrl}}/api/v1/stores/1", + "url": "{{baseUrl}}/api/v1/stores/{{storeId}}", "header": [ { "key": "Content-Type", @@ -1673,15 +3223,33 @@ ], "body": { "mode": "raw", - "raw": "{\n \"storeName\": \"Updated Pet Shop\",\n \"address\": \"456 Oak Avenue\",\n \"phone\": \"555-333-4444\",\n \"email\": \"updated@petshop.com\"\n}" + "raw": "{\n \"storeName\": \"Postman Store Updated\",\n \"address\": \"101 Postman Ave\",\n \"phone\": \"555-200-3001\",\n \"email\": \"postman.store.updated@example.com\"\n}", + "options": { + "raw": { + "language": "json" + } + } } - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] }, { "name": "Delete Store", "request": { "method": "DELETE", - "url": "{{baseUrl}}/api/v1/stores/1", + "url": "{{baseUrl}}/api/v1/stores/{{storeId}}", "header": [ { "key": "Content-Type", @@ -1693,7 +3261,62 @@ "type": "text" } ] - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 204', function () {", + " pm.response.to.have.status(204);", + "});" + ] + } + } + ] + }, + { + "name": "Create Store For Bulk Delete", + "request": { + "method": "POST", + "url": "{{baseUrl}}/api/v1/stores", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"storeName\": \"Postman Store {{$timestamp}}\",\n \"address\": \"100 Postman Ave\",\n \"phone\": \"555-200-3000\",\n \"email\": \"postman.store.{{$guid}}@example.com\"\n}", + "options": { + "raw": { + "language": "json" + } + } + } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 201', function () {", + " pm.response.to.have.status(201);", + "});", + "var jsonData = pm.response.json();", + "if (jsonData.storeId !== undefined) pm.collectionVariables.set('bulkStoreId', jsonData.storeId);" + ] + } + } + ] }, { "name": "Bulk Delete Stores", @@ -1713,9 +3336,22 @@ ], "body": { "mode": "raw", - "raw": "{\n \"ids\": [\n 1\n ]\n}" + "raw": "{\n \"ids\": [\n {{bulkStoreId}}\n ]\n}" } - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 204', function () {", + " pm.response.to.have.status(204);", + "});" + ] + } + } + ] } ] }, @@ -1738,7 +3374,20 @@ "type": "text" } ] - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] }, { "name": "Get Inventory Item", @@ -1756,7 +3405,20 @@ "type": "text" } ] - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] }, { "name": "Create Inventory", @@ -1776,15 +3438,35 @@ ], "body": { "mode": "raw", - "raw": "{\n \"prodId\": 1,\n \"quantity\": 100\n}" + "raw": "{\n \"prodId\": {{productId}},\n \"quantity\": 10\n}", + "options": { + "raw": { + "language": "json" + } + } } - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 201', function () {", + " pm.response.to.have.status(201);", + "});", + "var jsonData = pm.response.json();", + "if (jsonData.inventoryId !== undefined) pm.collectionVariables.set('inventoryId', jsonData.inventoryId);" + ] + } + } + ] }, { "name": "Update Inventory", "request": { "method": "PUT", - "url": "{{baseUrl}}/api/v1/inventory/1", + "url": "{{baseUrl}}/api/v1/inventory/{{inventoryId}}", "header": [ { "key": "Content-Type", @@ -1798,15 +3480,33 @@ ], "body": { "mode": "raw", - "raw": "{\n \"prodId\": 1,\n \"quantity\": 150\n}" + "raw": "{\n \"prodId\": {{productId}},\n \"quantity\": 12\n}", + "options": { + "raw": { + "language": "json" + } + } } - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] }, { "name": "Delete Inventory", "request": { "method": "DELETE", - "url": "{{baseUrl}}/api/v1/inventory/1", + "url": "{{baseUrl}}/api/v1/inventory/{{inventoryId}}", "header": [ { "key": "Content-Type", @@ -1818,7 +3518,62 @@ "type": "text" } ] - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 204', function () {", + " pm.response.to.have.status(204);", + "});" + ] + } + } + ] + }, + { + "name": "Create Inventory For Bulk Delete", + "request": { + "method": "POST", + "url": "{{baseUrl}}/api/v1/inventory", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"prodId\": {{productId}},\n \"quantity\": 10\n}", + "options": { + "raw": { + "language": "json" + } + } + } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 201', function () {", + " pm.response.to.have.status(201);", + "});", + "var jsonData = pm.response.json();", + "if (jsonData.inventoryId !== undefined) pm.collectionVariables.set('bulkInventoryId', jsonData.inventoryId);" + ] + } + } + ] }, { "name": "Bulk Delete Inventory", @@ -1838,9 +3593,22 @@ ], "body": { "mode": "raw", - "raw": "{\n \"ids\": [\n 1\n ]\n}" + "raw": "{\n \"ids\": [\n {{bulkInventoryId}}\n ]\n}" } - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 204', function () {", + " pm.response.to.have.status(204);", + "});" + ] + } + } + ] } ] }, @@ -1863,13 +3631,26 @@ "type": "text" } ] - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] }, { "name": "Get Supplier", "request": { "method": "GET", - "url": "{{baseUrl}}/api/v1/suppliers/1", + "url": "{{baseUrl}}/api/v1/suppliers/{{supplierId}}", "header": [ { "key": "Content-Type", @@ -1881,7 +3662,20 @@ "type": "text" } ] - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] }, { "name": "Create Supplier", @@ -1901,15 +3695,35 @@ ], "body": { "mode": "raw", - "raw": "{\n \"supCompany\": \"New Supplier Inc\",\n \"supContactFirstName\": \"John\",\n \"supContactLastName\": \"Smith\",\n \"supEmail\": \"john@newsupplier.com\",\n \"supPhone\": \"555-555-5555\"\n}" + "raw": "{\n \"supCompany\": \"Postman Supplier {{$timestamp}}\",\n \"supContactFirstName\": \"Post\",\n \"supContactLastName\": \"Man\",\n \"supEmail\": \"postman.supplier.{{$guid}}@example.com\",\n \"supPhone\": \"555-300-4000\"\n}", + "options": { + "raw": { + "language": "json" + } + } } - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 201', function () {", + " pm.response.to.have.status(201);", + "});", + "var jsonData = pm.response.json();", + "if (jsonData.supId !== undefined) pm.collectionVariables.set('supplierId', jsonData.supId);" + ] + } + } + ] }, { "name": "Update Supplier", "request": { "method": "PUT", - "url": "{{baseUrl}}/api/v1/suppliers/1", + "url": "{{baseUrl}}/api/v1/suppliers/{{supplierId}}", "header": [ { "key": "Content-Type", @@ -1923,15 +3737,33 @@ ], "body": { "mode": "raw", - "raw": "{\n \"supCompany\": \"Updated Supplier Co\",\n \"supContactFirstName\": \"Jane\",\n \"supContactLastName\": \"Doe\",\n \"supEmail\": \"jane@updatedsupplier.com\",\n \"supPhone\": \"555-666-7777\"\n}" + "raw": "{\n \"supCompany\": \"Postman Supplier Updated {{$timestamp}}\",\n \"supContactFirstName\": \"Post\",\n \"supContactLastName\": \"Man\",\n \"supEmail\": \"postman.supplier.updated.{{$timestamp}}@example.com\",\n \"supPhone\": \"555-300-4001\"\n}", + "options": { + "raw": { + "language": "json" + } + } } - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] }, { "name": "Delete Supplier", "request": { "method": "DELETE", - "url": "{{baseUrl}}/api/v1/suppliers/1", + "url": "{{baseUrl}}/api/v1/suppliers/{{supplierId}}", "header": [ { "key": "Content-Type", @@ -1939,11 +3771,24 @@ }, { "key": "Authorization", - "value": "Bearer {{adminToken}}", + "value": "Bearer {{staffToken}}", "type": "text" } ] - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 403', function () {", + " pm.response.to.have.status(403);", + "});" + ] + } + } + ] }, { "name": "Bulk Delete Suppliers", @@ -1957,7 +3802,7 @@ }, { "key": "Authorization", - "value": "Bearer {{adminToken}}", + "value": "Bearer {{staffToken}}", "type": "text" } ], @@ -1965,7 +3810,20 @@ "mode": "raw", "raw": "{\n \"ids\": [\n 1\n ]\n}" } - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 403', function () {", + " pm.response.to.have.status(403);", + "});" + ] + } + } + ] }, { "name": "Get Suppliers Dropdown", @@ -1983,7 +3841,20 @@ "type": "text" } ] - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] } ] }, @@ -2006,7 +3877,20 @@ "type": "text" } ] - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] }, { "name": "Get Purchase Order", @@ -2024,7 +3908,20 @@ "type": "text" } ] - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] } ] }, @@ -2047,7 +3944,20 @@ "type": "text" } ] - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] }, { "name": "Get Product Supplier", @@ -2065,7 +3975,20 @@ "type": "text" } ] - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] }, { "name": "Create Product Supplier", @@ -2085,15 +4008,33 @@ ], "body": { "mode": "raw", - "raw": "{\n \"productId\": 1,\n \"supplierId\": 2,\n \"cost\": 35.00\n}" + "raw": "{\n \"productId\": {{productId}},\n \"supplierId\": {{supplierId}},\n \"cost\": 7.50\n}", + "options": { + "raw": { + "language": "json" + } + } } - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 201', function () {", + " pm.response.to.have.status(201);", + "});" + ] + } + } + ] }, { "name": "Update Product Supplier", "request": { "method": "PUT", - "url": "{{baseUrl}}/api/v1/product-suppliers/1/1", + "url": "{{baseUrl}}/api/v1/product-suppliers/{{productId}}/{{supplierId}}", "header": [ { "key": "Content-Type", @@ -2107,15 +4048,33 @@ ], "body": { "mode": "raw", - "raw": "{\n \"productId\": 1,\n \"supplierId\": 2,\n \"cost\": 40.00\n}" + "raw": "{\n \"productId\": {{productId}},\n \"supplierId\": {{supplierId}},\n \"cost\": 7.75\n}", + "options": { + "raw": { + "language": "json" + } + } } - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] }, { "name": "Delete Product Supplier", "request": { "method": "DELETE", - "url": "{{baseUrl}}/api/v1/product-suppliers/1/1", + "url": "{{baseUrl}}/api/v1/product-suppliers/{{productId}}/{{supplierId}}", "header": [ { "key": "Content-Type", @@ -2127,7 +4086,60 @@ "type": "text" } ] - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 204', function () {", + " pm.response.to.have.status(204);", + "});" + ] + } + } + ] + }, + { + "name": "Create Product Supplier For Bulk Delete", + "request": { + "method": "POST", + "url": "{{baseUrl}}/api/v1/product-suppliers", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"productId\": {{productId}},\n \"supplierId\": {{supplierId}},\n \"cost\": 7.50\n}", + "options": { + "raw": { + "language": "json" + } + } + } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 201', function () {", + " pm.response.to.have.status(201);", + "});" + ] + } + } + ] }, { "name": "Bulk Delete Product Suppliers", @@ -2147,9 +4159,22 @@ ], "body": { "mode": "raw", - "raw": "{\n \"keys\": [\n {\"productId\": 1, \"supplierId\": 2},\n {\"productId\": 3, \"supplierId\": 4}\n ]\n}" + "raw": "{\n \"keys\": [\n {\"productId\": {{productId}}, \"supplierId\": {{supplierId}}}\n ]\n}" } - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 204', function () {", + " pm.response.to.have.status(204);", + "});" + ] + } + } + ] } ] }, @@ -2172,9 +4197,22 @@ "type": "text" } ] - } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] } ] } ] -} \ No newline at end of file +} From bd3575ef26dbeae2d1653c80cf89a7715d67fecf Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Tue, 10 Mar 2026 18:41:28 -0600 Subject: [PATCH 55/84] Add avatar fixture --- petshop-api.postman_collection.json | 2 +- postman/avatar.png | Bin 0 -> 68 bytes 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 postman/avatar.png diff --git a/petshop-api.postman_collection.json b/petshop-api.postman_collection.json index b0b9d60d..192c834c 100644 --- a/petshop-api.postman_collection.json +++ b/petshop-api.postman_collection.json @@ -88,7 +88,7 @@ }, { "key": "avatarFile", - "value": "/tmp/petshop-avatar.png" + "value": "postman/avatar.png" }, { "key": "bulkPetId", diff --git a/postman/avatar.png b/postman/avatar.png new file mode 100644 index 0000000000000000000000000000000000000000..05feaba19f63b5a6304be53b6a35e71a29b7e1e9 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcwN$fBwreFf%hTyr1># QJW!ay)78&qol`;+0FWIH{{R30 literal 0 HcmV?d00001 From 1a2f551c06a23df95c2197aaf4e8a95077299e2f Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Tue, 10 Mar 2026 18:49:51 -0600 Subject: [PATCH 56/84] Add auth store info --- .../backend/controller/AuthController.java | 31 +++++++++++++++++-- .../backend/dto/auth/UserInfoResponse.java | 30 ++++++++++++++++-- 2 files changed, 55 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/petshop/backend/controller/AuthController.java b/src/main/java/com/petshop/backend/controller/AuthController.java index b5bc1900..bf907257 100644 --- a/src/main/java/com/petshop/backend/controller/AuthController.java +++ b/src/main/java/com/petshop/backend/controller/AuthController.java @@ -7,7 +7,10 @@ import com.petshop.backend.dto.auth.ProfileUpdateRequest; import com.petshop.backend.dto.auth.RegisterRequest; import com.petshop.backend.dto.auth.RegisterResponse; import com.petshop.backend.dto.auth.UserInfoResponse; +import com.petshop.backend.entity.EmployeeStore; import com.petshop.backend.entity.User; +import com.petshop.backend.repository.EmployeeRepository; +import com.petshop.backend.repository.EmployeeStoreRepository; import com.petshop.backend.repository.UserRepository; import com.petshop.backend.security.JwtUtil; import com.petshop.backend.service.UserBusinessLinkageService; @@ -46,13 +49,17 @@ public class AuthController { private final JwtUtil jwtUtil; private final PasswordEncoder passwordEncoder; private final UserBusinessLinkageService userBusinessLinkageService; + private final EmployeeRepository employeeRepository; + private final EmployeeStoreRepository employeeStoreRepository; - public AuthController(AuthenticationManager authenticationManager, UserRepository userRepository, JwtUtil jwtUtil, PasswordEncoder passwordEncoder, UserBusinessLinkageService userBusinessLinkageService) { + public AuthController(AuthenticationManager authenticationManager, UserRepository userRepository, JwtUtil jwtUtil, PasswordEncoder passwordEncoder, UserBusinessLinkageService userBusinessLinkageService, EmployeeRepository employeeRepository, EmployeeStoreRepository employeeStoreRepository) { this.authenticationManager = authenticationManager; this.userRepository = userRepository; this.jwtUtil = jwtUtil; this.passwordEncoder = passwordEncoder; this.userBusinessLinkageService = userBusinessLinkageService; + this.employeeRepository = employeeRepository; + this.employeeStoreRepository = employeeStoreRepository; } @PostMapping("/register") @@ -149,13 +156,17 @@ public class AuthController { User user = userRepository.findByUsername(username) .orElseThrow(() -> new UsernameNotFoundException("User not found")); + EmployeeStore employeeStore = resolveEmployeeStore(user); + return ResponseEntity.ok(new UserInfoResponse( user.getId(), user.getUsername(), user.getEmail(), user.getFullName(), user.getAvatarUrl(), - user.getRole().name() + user.getRole().name(), + employeeStore != null ? employeeStore.getStore().getStoreId() : null, + employeeStore != null ? employeeStore.getStore().getStoreName() : null )); } @@ -195,16 +206,30 @@ public class AuthController { User updatedUser = userRepository.save(user); + EmployeeStore employeeStore = resolveEmployeeStore(updatedUser); + return ResponseEntity.ok(new UserInfoResponse( updatedUser.getId(), updatedUser.getUsername(), updatedUser.getEmail(), updatedUser.getFullName(), updatedUser.getAvatarUrl(), - updatedUser.getRole().name() + updatedUser.getRole().name(), + employeeStore != null ? employeeStore.getStore().getStoreId() : null, + employeeStore != null ? employeeStore.getStore().getStoreName() : null )); } + private EmployeeStore resolveEmployeeStore(User user) { + if (user.getRole() == User.Role.CUSTOMER) { + return null; + } + + return employeeRepository.findByUserId(user.getId()) + .flatMap(employee -> employeeStoreRepository.findByEmployeeEmployeeId(employee.getEmployeeId())) + .orElse(null); + } + @PostMapping("/me/avatar") public ResponseEntity uploadAvatar(@RequestParam("avatar") MultipartFile file) { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); diff --git a/src/main/java/com/petshop/backend/dto/auth/UserInfoResponse.java b/src/main/java/com/petshop/backend/dto/auth/UserInfoResponse.java index 7ce89f8a..1f15daf8 100644 --- a/src/main/java/com/petshop/backend/dto/auth/UserInfoResponse.java +++ b/src/main/java/com/petshop/backend/dto/auth/UserInfoResponse.java @@ -9,17 +9,21 @@ public class UserInfoResponse { private String fullName; private String avatarUrl; private String role; + private Long storeId; + private String storeName; public UserInfoResponse() { } - public UserInfoResponse(Long id, String username, String email, String fullName, String avatarUrl, String role) { + public UserInfoResponse(Long id, String username, String email, String fullName, String avatarUrl, String role, Long storeId, String storeName) { this.id = id; this.username = username; this.email = email; this.fullName = fullName; this.avatarUrl = avatarUrl; this.role = role; + this.storeId = storeId; + this.storeName = storeName; } public Long getId() { @@ -70,6 +74,22 @@ public class UserInfoResponse { this.role = role; } + public Long getStoreId() { + return storeId; + } + + public void setStoreId(Long storeId) { + this.storeId = storeId; + } + + public String getStoreName() { + return storeName; + } + + public void setStoreName(String storeName) { + this.storeName = storeName; + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -80,12 +100,14 @@ public class UserInfoResponse { Objects.equals(email, that.email) && Objects.equals(fullName, that.fullName) && Objects.equals(avatarUrl, that.avatarUrl) && - Objects.equals(role, that.role); + Objects.equals(role, that.role) && + Objects.equals(storeId, that.storeId) && + Objects.equals(storeName, that.storeName); } @Override public int hashCode() { - return Objects.hash(id, username, email, fullName, avatarUrl, role); + return Objects.hash(id, username, email, fullName, avatarUrl, role, storeId, storeName); } @Override @@ -97,6 +119,8 @@ public class UserInfoResponse { ", fullName='" + fullName + '\'' + ", avatarUrl='" + avatarUrl + '\'' + ", role='" + role + '\'' + + ", storeId=" + storeId + + ", storeName='" + storeName + '\'' + '}'; } } From 84c5f3c7b1976835a85c9c50b9dacadbbceb3cc1 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Tue, 10 Mar 2026 18:56:18 -0600 Subject: [PATCH 57/84] Add chat websocket flow --- .../WebSocketAuthChannelInterceptor.java | 140 ++++++++++++++++++ .../backend/config/WebSocketConfig.java | 15 ++ .../backend/controller/ChatController.java | 8 +- .../controller/ChatWebSocketController.java | 39 +++++ .../backend/security/SecurityConfig.java | 1 + .../backend/service/ChatRealtimeService.java | 75 ++++++++++ .../petshop/backend/service/ChatService.java | 51 ++++--- 7 files changed, 310 insertions(+), 19 deletions(-) create mode 100644 src/main/java/com/petshop/backend/config/WebSocketAuthChannelInterceptor.java create mode 100644 src/main/java/com/petshop/backend/controller/ChatWebSocketController.java create mode 100644 src/main/java/com/petshop/backend/service/ChatRealtimeService.java diff --git a/src/main/java/com/petshop/backend/config/WebSocketAuthChannelInterceptor.java b/src/main/java/com/petshop/backend/config/WebSocketAuthChannelInterceptor.java new file mode 100644 index 00000000..2b467025 --- /dev/null +++ b/src/main/java/com/petshop/backend/config/WebSocketAuthChannelInterceptor.java @@ -0,0 +1,140 @@ +package com.petshop.backend.config; + +import com.petshop.backend.entity.User; +import com.petshop.backend.repository.UserRepository; +import com.petshop.backend.security.JwtUtil; +import com.petshop.backend.service.ChatService; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessageChannel; +import org.springframework.messaging.simp.stomp.StompCommand; +import org.springframework.messaging.simp.stomp.StompHeaderAccessor; +import org.springframework.messaging.support.ChannelInterceptor; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.stereotype.Component; + +import java.security.Principal; +import java.util.Collections; +import java.util.List; + +@Component +public class WebSocketAuthChannelInterceptor implements ChannelInterceptor { + + private final JwtUtil jwtUtil; + private final UserRepository userRepository; + private final ChatService chatService; + + public WebSocketAuthChannelInterceptor(JwtUtil jwtUtil, UserRepository userRepository, ChatService chatService) { + this.jwtUtil = jwtUtil; + this.userRepository = userRepository; + this.chatService = chatService; + } + + @Override + public Message preSend(Message message, MessageChannel channel) { + StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message); + StompCommand command = accessor.getCommand(); + + if (command == null) { + return message; + } + + if (StompCommand.CONNECT.equals(command)) { + String tokenHeader = firstHeader(accessor, "Authorization"); + String token = extractToken(tokenHeader != null ? tokenHeader : firstHeader(accessor, "token")); + if (token == null || token.isBlank()) { + throw new IllegalArgumentException("Missing websocket token"); + } + + String username = jwtUtil.extractUsername(token); + User user = userRepository.findByUsername(username) + .orElseThrow(() -> new IllegalArgumentException("User not found")); + if (user.getActive() == null || !user.getActive()) { + throw new IllegalArgumentException("User account is inactive"); + } + + UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken( + user.getUsername(), + null, + Collections.singletonList(new SimpleGrantedAuthority("ROLE_" + user.getRole().name())) + ); + accessor.setUser(authentication); + return message; + } + + Principal principal = accessor.getUser(); + if (principal == null) { + throw new IllegalArgumentException("Unauthenticated websocket session"); + } + + User user = userRepository.findByUsername(principal.getName()) + .orElseThrow(() -> new IllegalArgumentException("User not found")); + + if (StompCommand.SUBSCRIBE.equals(command)) { + authorizeSubscription(accessor.getDestination(), user); + } else if (StompCommand.SEND.equals(command)) { + authorizeSend(accessor.getDestination(), user); + } + + return message; + } + + private void authorizeSubscription(String destination, User user) { + if (destination == null || destination.startsWith("/user/queue/")) { + return; + } + + if ("/topic/chat/conversations".equals(destination)) { + if (user.getRole() == User.Role.CUSTOMER) { + throw new IllegalArgumentException("Customers cannot subscribe to staff conversation feed"); + } + return; + } + + Long conversationId = extractConversationId(destination, "/topic/chat/conversations/"); + if (conversationId != null && chatService.hasConversationAccess(conversationId, user.getId(), user.getRole())) { + return; + } + + throw new IllegalArgumentException("Not authorized to subscribe to destination"); + } + + private void authorizeSend(String destination, User user) { + Long conversationId = extractConversationId(destination, "/app/chat/conversations/"); + if (conversationId != null && destination.endsWith("/messages") && chatService.hasConversationAccess(conversationId, user.getId(), user.getRole())) { + return; + } + + throw new IllegalArgumentException("Not authorized to send to destination"); + } + + private Long extractConversationId(String destination, String prefix) { + if (destination == null || !destination.startsWith(prefix)) { + return null; + } + + String suffix = destination.substring(prefix.length()); + String[] parts = suffix.split("/"); + if (parts.length == 0 || parts[0].isBlank()) { + return null; + } + + try { + return Long.parseLong(parts[0]); + } catch (NumberFormatException ex) { + return null; + } + } + + private String firstHeader(StompHeaderAccessor accessor, String name) { + List values = accessor.getNativeHeader(name); + return values == null || values.isEmpty() ? null : values.get(0); + } + + private String extractToken(String rawValue) { + if (rawValue == null || rawValue.isBlank()) { + return null; + } + return rawValue.startsWith("Bearer ") ? rawValue.substring(7) : rawValue; + } +} diff --git a/src/main/java/com/petshop/backend/config/WebSocketConfig.java b/src/main/java/com/petshop/backend/config/WebSocketConfig.java index 84944c25..27526bec 100644 --- a/src/main/java/com/petshop/backend/config/WebSocketConfig.java +++ b/src/main/java/com/petshop/backend/config/WebSocketConfig.java @@ -1,6 +1,7 @@ package com.petshop.backend.config; import org.springframework.context.annotation.Configuration; +import org.springframework.messaging.simp.config.ChannelRegistration; import org.springframework.messaging.simp.config.MessageBrokerRegistry; import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; import org.springframework.web.socket.config.annotation.StompEndpointRegistry; @@ -10,15 +11,29 @@ import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerCo @EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { + private final WebSocketAuthChannelInterceptor webSocketAuthChannelInterceptor; + + public WebSocketConfig(WebSocketAuthChannelInterceptor webSocketAuthChannelInterceptor) { + this.webSocketAuthChannelInterceptor = webSocketAuthChannelInterceptor; + } + @Override public void configureMessageBroker(MessageBrokerRegistry config) { config.enableSimpleBroker("/topic", "/queue"); config.setApplicationDestinationPrefixes("/app"); + config.setUserDestinationPrefix("/user"); + } + + @Override + public void configureClientInboundChannel(ChannelRegistration registration) { + registration.interceptors(webSocketAuthChannelInterceptor); } @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/ws/chat") + .setAllowedOriginPatterns("*"); + registry.addEndpoint("/ws/chat-sockjs") .setAllowedOriginPatterns("*") .withSockJS(); } diff --git a/src/main/java/com/petshop/backend/controller/ChatController.java b/src/main/java/com/petshop/backend/controller/ChatController.java index 5ffb1444..35541f65 100644 --- a/src/main/java/com/petshop/backend/controller/ChatController.java +++ b/src/main/java/com/petshop/backend/controller/ChatController.java @@ -7,6 +7,7 @@ import com.petshop.backend.dto.chat.MessageResponse; import com.petshop.backend.entity.User; import com.petshop.backend.repository.CustomerRepository; import com.petshop.backend.repository.UserRepository; +import com.petshop.backend.service.ChatRealtimeService; import com.petshop.backend.service.ChatService; import jakarta.validation.Valid; import org.springframework.http.HttpStatus; @@ -24,11 +25,13 @@ import java.util.List; public class ChatController { private final ChatService chatService; + private final ChatRealtimeService chatRealtimeService; private final UserRepository userRepository; private final CustomerRepository customerRepository; - public ChatController(ChatService chatService, UserRepository userRepository, CustomerRepository customerRepository) { + public ChatController(ChatService chatService, ChatRealtimeService chatRealtimeService, UserRepository userRepository, CustomerRepository customerRepository) { this.chatService = chatService; + this.chatRealtimeService = chatRealtimeService; this.userRepository = userRepository; this.customerRepository = customerRepository; } @@ -44,6 +47,7 @@ public class ChatController { public ResponseEntity createConversation(@Valid @RequestBody ConversationRequest request) { User user = getCurrentUser(); ConversationResponse response = chatService.createConversation(user.getId(), request); + chatRealtimeService.publishNewConversation(response); return ResponseEntity.status(HttpStatus.CREATED).body(response); } @@ -70,6 +74,8 @@ public class ChatController { @Valid @RequestBody MessageRequest request) { User user = getCurrentUser(); MessageResponse message = chatService.sendMessage(id, user.getId(), user.getRole(), request); + chatRealtimeService.publishMessage(id, message); + chatRealtimeService.publishConversationUpdate(id); return ResponseEntity.status(HttpStatus.CREATED).body(message); } diff --git a/src/main/java/com/petshop/backend/controller/ChatWebSocketController.java b/src/main/java/com/petshop/backend/controller/ChatWebSocketController.java new file mode 100644 index 00000000..011268e6 --- /dev/null +++ b/src/main/java/com/petshop/backend/controller/ChatWebSocketController.java @@ -0,0 +1,39 @@ +package com.petshop.backend.controller; + +import com.petshop.backend.dto.chat.MessageRequest; +import com.petshop.backend.dto.chat.MessageResponse; +import com.petshop.backend.entity.User; +import com.petshop.backend.repository.UserRepository; +import com.petshop.backend.service.ChatRealtimeService; +import com.petshop.backend.service.ChatService; +import jakarta.validation.Valid; +import org.springframework.messaging.handler.annotation.DestinationVariable; +import org.springframework.messaging.handler.annotation.MessageMapping; +import org.springframework.messaging.handler.annotation.Payload; +import org.springframework.messaging.simp.annotation.SendToUser; +import org.springframework.security.core.Authentication; +import org.springframework.stereotype.Controller; + +@Controller +public class ChatWebSocketController { + + private final ChatService chatService; + private final ChatRealtimeService chatRealtimeService; + private final UserRepository userRepository; + + public ChatWebSocketController(ChatService chatService, ChatRealtimeService chatRealtimeService, UserRepository userRepository) { + this.chatService = chatService; + this.chatRealtimeService = chatRealtimeService; + this.userRepository = userRepository; + } + + @MessageMapping("/chat/conversations/{id}/messages") + @SendToUser("/queue/chat/errors") + public void sendMessage(@DestinationVariable Long id, @Valid @Payload MessageRequest request, Authentication authentication) { + User user = userRepository.findByUsername(authentication.getName()) + .orElseThrow(() -> new IllegalArgumentException("User not found")); + MessageResponse message = chatService.sendMessage(id, user.getId(), user.getRole(), request); + chatRealtimeService.publishMessage(id, message); + chatRealtimeService.publishConversationUpdate(id); + } +} diff --git a/src/main/java/com/petshop/backend/security/SecurityConfig.java b/src/main/java/com/petshop/backend/security/SecurityConfig.java index 9841d9fd..38811256 100644 --- a/src/main/java/com/petshop/backend/security/SecurityConfig.java +++ b/src/main/java/com/petshop/backend/security/SecurityConfig.java @@ -38,6 +38,7 @@ public class SecurityConfig { .authorizeHttpRequests(auth -> auth .requestMatchers("/api/v1/auth/login", "/api/v1/auth/register").permitAll() .requestMatchers("/api/v1/health").permitAll() + .requestMatchers("/ws/chat/**", "/ws/chat-sockjs/**").permitAll() .requestMatchers("/swagger-ui/**", "/v3/api-docs/**", "/swagger-ui.html").permitAll() .requestMatchers(HttpMethod.GET, "/api/v1/pets/**").permitAll() .requestMatchers(HttpMethod.GET, "/api/v1/products/**").permitAll() diff --git a/src/main/java/com/petshop/backend/service/ChatRealtimeService.java b/src/main/java/com/petshop/backend/service/ChatRealtimeService.java new file mode 100644 index 00000000..9b835823 --- /dev/null +++ b/src/main/java/com/petshop/backend/service/ChatRealtimeService.java @@ -0,0 +1,75 @@ +package com.petshop.backend.service; + +import com.petshop.backend.dto.chat.ConversationResponse; +import com.petshop.backend.dto.chat.MessageResponse; +import com.petshop.backend.entity.Conversation; +import com.petshop.backend.entity.Customer; +import com.petshop.backend.entity.User; +import com.petshop.backend.exception.ResourceNotFoundException; +import com.petshop.backend.repository.ConversationRepository; +import com.petshop.backend.repository.CustomerRepository; +import com.petshop.backend.repository.MessageRepository; +import com.petshop.backend.repository.UserRepository; +import org.springframework.messaging.simp.SimpMessagingTemplate; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +public class ChatRealtimeService { + + private final SimpMessagingTemplate messagingTemplate; + private final ConversationRepository conversationRepository; + private final MessageRepository messageRepository; + private final CustomerRepository customerRepository; + private final UserRepository userRepository; + + public ChatRealtimeService(SimpMessagingTemplate messagingTemplate, ConversationRepository conversationRepository, MessageRepository messageRepository, CustomerRepository customerRepository, UserRepository userRepository) { + this.messagingTemplate = messagingTemplate; + this.conversationRepository = conversationRepository; + this.messageRepository = messageRepository; + this.customerRepository = customerRepository; + this.userRepository = userRepository; + } + + public void publishNewConversation(ConversationResponse conversation) { + messagingTemplate.convertAndSend("/topic/chat/conversations", conversation); + sendConversationToCustomerQueue(conversation); + } + + public void publishMessage(Long conversationId, MessageResponse message) { + messagingTemplate.convertAndSend("/topic/chat/conversations/" + conversationId, message); + } + + public void publishConversationUpdate(Long conversationId) { + Conversation conversation = conversationRepository.findById(conversationId) + .orElseThrow(() -> new ResourceNotFoundException("Conversation not found")); + List messages = messageRepository.findByConversationIdOrderByTimestampAsc(conversationId); + String lastMessage = messages.isEmpty() ? "" : messages.get(messages.size() - 1).getContent(); + ConversationResponse response = ConversationResponse.fromEntity(conversation, lastMessage); + + messagingTemplate.convertAndSend("/topic/chat/conversations", response); + sendConversationToCustomerQueue(response); + sendConversationToStaffQueue(response); + } + + private void sendConversationToCustomerQueue(ConversationResponse conversation) { + Customer customer = customerRepository.findById(conversation.getCustomerId()) + .orElseThrow(() -> new ResourceNotFoundException("Customer not found")); + if (customer.getUserId() == null) { + return; + } + User customerUser = userRepository.findById(customer.getUserId()) + .orElseThrow(() -> new ResourceNotFoundException("User not found")); + messagingTemplate.convertAndSendToUser(customerUser.getUsername(), "/queue/chat/conversations", conversation); + } + + private void sendConversationToStaffQueue(ConversationResponse conversation) { + if (conversation.getStaffId() == null) { + return; + } + User staffUser = userRepository.findById(conversation.getStaffId()) + .orElseThrow(() -> new ResourceNotFoundException("User not found")); + messagingTemplate.convertAndSendToUser(staffUser.getUsername(), "/queue/chat/conversations", conversation); + } +} diff --git a/src/main/java/com/petshop/backend/service/ChatService.java b/src/main/java/com/petshop/backend/service/ChatService.java index af5b6332..ac240eb0 100644 --- a/src/main/java/com/petshop/backend/service/ChatService.java +++ b/src/main/java/com/petshop/backend/service/ChatService.java @@ -94,14 +94,11 @@ public class ChatService { Conversation conversation = conversationRepository.findById(conversationId) .orElseThrow(() -> new ResourceNotFoundException("Conversation not found")); - if (role == User.Role.CUSTOMER) { - Customer customer = customerRepository.findByUserId(userId) - .orElseThrow(() -> new ResourceNotFoundException("Customer record not found for user")); - if (!conversation.getCustomerId().equals(customer.getCustomerId())) { + if (!hasConversationAccess(conversation, userId, role)) { + if (role == User.Role.CUSTOMER) { throw new AccessDeniedException("You can only view your own conversations"); } - } else if (role == User.Role.STAFF) { - if (conversation.getStaffId() != null && !conversation.getStaffId().equals(userId)) { + if (role == User.Role.STAFF) { throw new AccessDeniedException("You can only view conversations assigned to you or unassigned conversations"); } } @@ -117,14 +114,11 @@ public class ChatService { Conversation conversation = conversationRepository.findById(conversationId) .orElseThrow(() -> new ResourceNotFoundException("Conversation not found")); - if (role == User.Role.CUSTOMER) { - Customer customer = customerRepository.findByUserId(userId) - .orElseThrow(() -> new ResourceNotFoundException("Customer record not found for user")); - if (!conversation.getCustomerId().equals(customer.getCustomerId())) { + if (!hasConversationAccess(conversation, userId, role)) { + if (role == User.Role.CUSTOMER) { throw new AccessDeniedException("You can only send messages to your own conversations"); } - } else if (role == User.Role.STAFF) { - if (conversation.getStaffId() != null && !conversation.getStaffId().equals(userId)) { + if (role == User.Role.STAFF) { throw new AccessDeniedException("You can only reply to conversations assigned to you or unassigned conversations"); } } @@ -148,14 +142,11 @@ public class ChatService { Conversation conversation = conversationRepository.findById(conversationId) .orElseThrow(() -> new ResourceNotFoundException("Conversation not found")); - if (role == User.Role.CUSTOMER) { - Customer customer = customerRepository.findByUserId(userId) - .orElseThrow(() -> new ResourceNotFoundException("Customer record not found for user")); - if (!conversation.getCustomerId().equals(customer.getCustomerId())) { + if (!hasConversationAccess(conversation, userId, role)) { + if (role == User.Role.CUSTOMER) { throw new AccessDeniedException("You can only view messages from your own conversations"); } - } else if (role == User.Role.STAFF) { - if (conversation.getStaffId() != null && !conversation.getStaffId().equals(userId)) { + if (role == User.Role.STAFF) { throw new AccessDeniedException("You can only view messages from conversations assigned to you or unassigned conversations"); } } @@ -165,4 +156,28 @@ public class ChatService { .map(MessageResponse::fromEntity) .collect(Collectors.toList()); } + + public boolean hasConversationAccess(Long conversationId, Long userId, User.Role role) { + Conversation conversation = conversationRepository.findById(conversationId) + .orElseThrow(() -> new ResourceNotFoundException("Conversation not found")); + return hasConversationAccess(conversation, userId, role); + } + + private boolean hasConversationAccess(Conversation conversation, Long userId, User.Role role) { + if (role == User.Role.ADMIN) { + return true; + } + + if (role == User.Role.CUSTOMER) { + Customer customer = customerRepository.findByUserId(userId) + .orElseThrow(() -> new ResourceNotFoundException("Customer record not found for user")); + return conversation.getCustomerId().equals(customer.getCustomerId()); + } + + if (role == User.Role.STAFF) { + return conversation.getStaffId() == null || conversation.getStaffId().equals(userId); + } + + return false; + } } From 2e401a544fc44fa8f355b67a0738a1c77e941604 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Tue, 10 Mar 2026 20:04:26 -0600 Subject: [PATCH 58/84] Fix chat auth --- .../WebSocketAuthChannelInterceptor.java | 12 +++++++ .../controller/ChatWebSocketController.java | 35 ++++++++++++++++--- 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/petshop/backend/config/WebSocketAuthChannelInterceptor.java b/src/main/java/com/petshop/backend/config/WebSocketAuthChannelInterceptor.java index 2b467025..6f01c384 100644 --- a/src/main/java/com/petshop/backend/config/WebSocketAuthChannelInterceptor.java +++ b/src/main/java/com/petshop/backend/config/WebSocketAuthChannelInterceptor.java @@ -59,10 +59,22 @@ public class WebSocketAuthChannelInterceptor implements ChannelInterceptor { Collections.singletonList(new SimpleGrantedAuthority("ROLE_" + user.getRole().name())) ); accessor.setUser(authentication); + accessor.getSessionAttributes().put("user", authentication); + return message; + } + + if (StompCommand.DISCONNECT.equals(command) || StompCommand.UNSUBSCRIBE.equals(command)) { return message; } Principal principal = accessor.getUser(); + if (principal == null && accessor.getSessionAttributes() != null) { + Object sessionUser = accessor.getSessionAttributes().get("user"); + if (sessionUser instanceof Principal storedPrincipal) { + accessor.setUser(storedPrincipal); + principal = storedPrincipal; + } + } if (principal == null) { throw new IllegalArgumentException("Unauthenticated websocket session"); } diff --git a/src/main/java/com/petshop/backend/controller/ChatWebSocketController.java b/src/main/java/com/petshop/backend/controller/ChatWebSocketController.java index 011268e6..bb30bd44 100644 --- a/src/main/java/com/petshop/backend/controller/ChatWebSocketController.java +++ b/src/main/java/com/petshop/backend/controller/ChatWebSocketController.java @@ -4,36 +4,61 @@ import com.petshop.backend.dto.chat.MessageRequest; import com.petshop.backend.dto.chat.MessageResponse; import com.petshop.backend.entity.User; import com.petshop.backend.repository.UserRepository; +import com.petshop.backend.security.JwtUtil; import com.petshop.backend.service.ChatRealtimeService; import com.petshop.backend.service.ChatService; import jakarta.validation.Valid; import org.springframework.messaging.handler.annotation.DestinationVariable; import org.springframework.messaging.handler.annotation.MessageMapping; import org.springframework.messaging.handler.annotation.Payload; +import org.springframework.messaging.simp.SimpMessageHeaderAccessor; import org.springframework.messaging.simp.annotation.SendToUser; -import org.springframework.security.core.Authentication; import org.springframework.stereotype.Controller; +import java.security.Principal; + @Controller public class ChatWebSocketController { private final ChatService chatService; private final ChatRealtimeService chatRealtimeService; private final UserRepository userRepository; + private final JwtUtil jwtUtil; - public ChatWebSocketController(ChatService chatService, ChatRealtimeService chatRealtimeService, UserRepository userRepository) { + public ChatWebSocketController(ChatService chatService, ChatRealtimeService chatRealtimeService, UserRepository userRepository, JwtUtil jwtUtil) { this.chatService = chatService; this.chatRealtimeService = chatRealtimeService; this.userRepository = userRepository; + this.jwtUtil = jwtUtil; } @MessageMapping("/chat/conversations/{id}/messages") @SendToUser("/queue/chat/errors") - public void sendMessage(@DestinationVariable Long id, @Valid @Payload MessageRequest request, Authentication authentication) { - User user = userRepository.findByUsername(authentication.getName()) - .orElseThrow(() -> new IllegalArgumentException("User not found")); + public void sendMessage(@DestinationVariable Long id, @Valid @Payload MessageRequest request, SimpMessageHeaderAccessor headerAccessor) { + User user = resolveUser(headerAccessor); MessageResponse message = chatService.sendMessage(id, user.getId(), user.getRole(), request); chatRealtimeService.publishMessage(id, message); chatRealtimeService.publishConversationUpdate(id); } + + private User resolveUser(SimpMessageHeaderAccessor headerAccessor) { + Principal principal = headerAccessor.getUser(); + if (principal != null) { + return userRepository.findByUsername(principal.getName()) + .orElseThrow(() -> new IllegalArgumentException("User not found")); + } + + String tokenHeader = headerAccessor.getFirstNativeHeader("Authorization"); + if (tokenHeader == null || tokenHeader.isBlank()) { + tokenHeader = headerAccessor.getFirstNativeHeader("token"); + } + if (tokenHeader == null || tokenHeader.isBlank()) { + throw new IllegalArgumentException("User not authenticated"); + } + + String token = tokenHeader.startsWith("Bearer ") ? tokenHeader.substring(7) : tokenHeader; + String username = jwtUtil.extractUsername(token); + return userRepository.findByUsername(username) + .orElseThrow(() -> new IllegalArgumentException("User not found")); + } } From c18ef16ef6b7461a9cc1fbc0fa7aeb62e8eb0ae6 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Tue, 10 Mar 2026 20:52:00 -0600 Subject: [PATCH 59/84] Add chat takeover --- .../backend/controller/ChatController.java | 9 ++++++ .../dto/chat/ConversationResponse.java | 24 +++++++++++++- .../petshop/backend/entity/Conversation.java | 31 ++++++++++++++++++- .../petshop/backend/service/ChatService.java | 25 +++++++++++++++ .../V4__conversation_mode_and_takeover.sql | 9 ++++++ 5 files changed, 96 insertions(+), 2 deletions(-) create mode 100644 src/main/resources/db/migration/V4__conversation_mode_and_takeover.sql diff --git a/src/main/java/com/petshop/backend/controller/ChatController.java b/src/main/java/com/petshop/backend/controller/ChatController.java index 35541f65..4392b25d 100644 --- a/src/main/java/com/petshop/backend/controller/ChatController.java +++ b/src/main/java/com/petshop/backend/controller/ChatController.java @@ -86,4 +86,13 @@ public class ChatController { List messages = chatService.getMessages(id, user.getId(), user.getRole()); return ResponseEntity.ok(messages); } + + @PostMapping("/conversations/{id}/request-human") + @PreAuthorize("hasRole('CUSTOMER')") + public ResponseEntity requestHumanTakeover(@PathVariable Long id) { + User user = getCurrentUser(); + ConversationResponse conversation = chatService.requestHumanTakeover(id, user.getId(), user.getRole()); + chatRealtimeService.publishConversationUpdate(id); + return ResponseEntity.ok(conversation); + } } diff --git a/src/main/java/com/petshop/backend/dto/chat/ConversationResponse.java b/src/main/java/com/petshop/backend/dto/chat/ConversationResponse.java index d9dbb1f8..86078ab2 100644 --- a/src/main/java/com/petshop/backend/dto/chat/ConversationResponse.java +++ b/src/main/java/com/petshop/backend/dto/chat/ConversationResponse.java @@ -9,19 +9,23 @@ public class ConversationResponse { private Long customerId; private Long staffId; private String status; + private String mode; private String lastMessage; + private LocalDateTime humanRequestedAt; private LocalDateTime createdAt; private LocalDateTime updatedAt; public ConversationResponse() { } - public ConversationResponse(Long id, Long customerId, Long staffId, String status, String lastMessage, LocalDateTime createdAt, LocalDateTime updatedAt) { + public ConversationResponse(Long id, Long customerId, Long staffId, String status, String mode, String lastMessage, LocalDateTime humanRequestedAt, LocalDateTime createdAt, LocalDateTime updatedAt) { this.id = id; this.customerId = customerId; this.staffId = staffId; this.status = status; + this.mode = mode; this.lastMessage = lastMessage; + this.humanRequestedAt = humanRequestedAt; this.createdAt = createdAt; this.updatedAt = updatedAt; } @@ -32,7 +36,9 @@ public class ConversationResponse { response.setCustomerId(conversation.getCustomerId()); response.setStaffId(conversation.getStaffId()); response.setStatus(conversation.getStatus().name()); + response.setMode(conversation.getMode().name()); response.setLastMessage(lastMessage); + response.setHumanRequestedAt(conversation.getHumanRequestedAt()); response.setCreatedAt(conversation.getCreatedAt()); response.setUpdatedAt(conversation.getUpdatedAt()); return response; @@ -70,6 +76,14 @@ public class ConversationResponse { this.status = status; } + public String getMode() { + return mode; + } + + public void setMode(String mode) { + this.mode = mode; + } + public String getLastMessage() { return lastMessage; } @@ -78,6 +92,14 @@ public class ConversationResponse { this.lastMessage = lastMessage; } + public LocalDateTime getHumanRequestedAt() { + return humanRequestedAt; + } + + public void setHumanRequestedAt(LocalDateTime humanRequestedAt) { + this.humanRequestedAt = humanRequestedAt; + } + public LocalDateTime getCreatedAt() { return createdAt; } diff --git a/src/main/java/com/petshop/backend/entity/Conversation.java b/src/main/java/com/petshop/backend/entity/Conversation.java index 0a907710..080228eb 100644 --- a/src/main/java/com/petshop/backend/entity/Conversation.java +++ b/src/main/java/com/petshop/backend/entity/Conversation.java @@ -24,6 +24,13 @@ public class Conversation { @Column(length = 20, nullable = false, columnDefinition = "VARCHAR(20)") private ConversationStatus status = ConversationStatus.OPEN; + @Enumerated(EnumType.STRING) + @Column(length = 20, nullable = false, columnDefinition = "VARCHAR(20)") + private ConversationMode mode = ConversationMode.AUTOMATED; + + @Column + private LocalDateTime humanRequestedAt; + @CreationTimestamp @Column(name = "created_at", nullable = false, updatable = false) private LocalDateTime createdAt; @@ -36,14 +43,20 @@ public class Conversation { OPEN, CLOSED } + public enum ConversationMode { + AUTOMATED, HUMAN + } + public Conversation() { } - public Conversation(Long id, Long customerId, Long staffId, ConversationStatus status, LocalDateTime createdAt, LocalDateTime updatedAt) { + public Conversation(Long id, Long customerId, Long staffId, ConversationStatus status, ConversationMode mode, LocalDateTime humanRequestedAt, LocalDateTime createdAt, LocalDateTime updatedAt) { this.id = id; this.customerId = customerId; this.staffId = staffId; this.status = status; + this.mode = mode; + this.humanRequestedAt = humanRequestedAt; this.createdAt = createdAt; this.updatedAt = updatedAt; } @@ -80,6 +93,22 @@ public class Conversation { this.status = status; } + public ConversationMode getMode() { + return mode; + } + + public void setMode(ConversationMode mode) { + this.mode = mode; + } + + public LocalDateTime getHumanRequestedAt() { + return humanRequestedAt; + } + + public void setHumanRequestedAt(LocalDateTime humanRequestedAt) { + this.humanRequestedAt = humanRequestedAt; + } + public LocalDateTime getCreatedAt() { return createdAt; } diff --git a/src/main/java/com/petshop/backend/service/ChatService.java b/src/main/java/com/petshop/backend/service/ChatService.java index ac240eb0..f66cbdb5 100644 --- a/src/main/java/com/petshop/backend/service/ChatService.java +++ b/src/main/java/com/petshop/backend/service/ChatService.java @@ -17,6 +17,7 @@ import org.springframework.security.access.AccessDeniedException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.time.LocalDateTime; import java.util.List; import java.util.stream.Collectors; @@ -53,6 +54,7 @@ public class ChatService { Conversation conversation = new Conversation(); conversation.setCustomerId(customer.getCustomerId()); conversation.setStatus(Conversation.ConversationStatus.OPEN); + conversation.setMode(Conversation.ConversationMode.AUTOMATED); conversation = conversationRepository.save(conversation); Message message = new Message(); @@ -132,12 +134,35 @@ public class ChatService { if (role == User.Role.STAFF && conversation.getStaffId() == null) { conversation.setStaffId(userId); + } + + if (role == User.Role.STAFF) { + conversation.setMode(Conversation.ConversationMode.HUMAN); conversationRepository.save(conversation); } return MessageResponse.fromEntity(message); } + @Transactional + public ConversationResponse requestHumanTakeover(Long conversationId, Long userId, User.Role role) { + Conversation conversation = conversationRepository.findById(conversationId) + .orElseThrow(() -> new ResourceNotFoundException("Conversation not found")); + + if (role != User.Role.CUSTOMER || !hasConversationAccess(conversation, userId, role)) { + throw new AccessDeniedException("You can only request human takeover for your own conversations"); + } + + if (conversation.getHumanRequestedAt() == null) { + conversation.setHumanRequestedAt(LocalDateTime.now()); + } + conversationRepository.save(conversation); + + List messages = messageRepository.findByConversationIdOrderByTimestampAsc(conversationId); + String lastMessage = messages.isEmpty() ? "" : messages.get(messages.size() - 1).getContent(); + return ConversationResponse.fromEntity(conversation, lastMessage); + } + public List getMessages(Long conversationId, Long userId, User.Role role) { Conversation conversation = conversationRepository.findById(conversationId) .orElseThrow(() -> new ResourceNotFoundException("Conversation not found")); diff --git a/src/main/resources/db/migration/V4__conversation_mode_and_takeover.sql b/src/main/resources/db/migration/V4__conversation_mode_and_takeover.sql new file mode 100644 index 00000000..271c0e72 --- /dev/null +++ b/src/main/resources/db/migration/V4__conversation_mode_and_takeover.sql @@ -0,0 +1,9 @@ +ALTER TABLE conversation + ADD COLUMN mode VARCHAR(20) NOT NULL DEFAULT 'AUTOMATED' AFTER status, + ADD COLUMN humanRequestedAt TIMESTAMP NULL AFTER mode; + +UPDATE conversation +SET mode = CASE + WHEN staffId IS NULL THEN 'AUTOMATED' + ELSE 'HUMAN' +END; From 8cb80d8ada72a6523a65d0fc9166be67ab4061c7 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Tue, 10 Mar 2026 21:11:49 -0600 Subject: [PATCH 60/84] Sync dev stack --- pom.xml | 18 ++ .../petshop/backend/DevStackApplication.java | 154 ++++++++++++++++++ 2 files changed, 172 insertions(+) create mode 100644 src/main/java/com/petshop/backend/DevStackApplication.java diff --git a/pom.xml b/pom.xml index 4357996d..9a3e6414 100644 --- a/pom.xml +++ b/pom.xml @@ -117,6 +117,24 @@ exec-maven-plugin 3.1.0 + + docker-up + + exec + + + docker + + compose + -f + docker-compose.dev.yml + up + -d + --wait + db + + + docker-down diff --git a/src/main/java/com/petshop/backend/DevStackApplication.java b/src/main/java/com/petshop/backend/DevStackApplication.java new file mode 100644 index 00000000..a049b7ee --- /dev/null +++ b/src/main/java/com/petshop/backend/DevStackApplication.java @@ -0,0 +1,154 @@ +package com.petshop.backend; + +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.event.ContextClosedEvent; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +public class DevStackApplication { + + private static final Duration WATCH_INTERVAL = Duration.ofSeconds(2); + + public static void main(String[] args) { + DevStackController controller = new DevStackController(); + ConfigurableApplicationContext context = null; + CountDownLatch shutdownLatch = new CountDownLatch(1); + + try { + controller.startDatabase(); + context = new SpringApplicationBuilder(BackendApplication.class).run(args); + ConfigurableApplicationContext appContext = context; + context.addApplicationListener(event -> { + if (event instanceof ContextClosedEvent) { + shutdownLatch.countDown(); + } + }); + controller.watchDatabase(appContext); + shutdownLatch.await(); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + throw new IllegalStateException("Dev stack interrupted", ex); + } finally { + controller.stopWatching(); + if (context != null && context.isActive()) { + context.close(); + } + controller.stopDatabase(); + } + } + + private static final class DevStackController { + private final Path projectDir = Paths.get("").toAbsolutePath(); + private final AtomicBoolean shuttingDown = new AtomicBoolean(false); + private ScheduledExecutorService scheduler; + + private void startDatabase() { + runCommand(composeCommand("up", "-d", "--wait", "db")); + } + + private void stopDatabase() { + if (!shuttingDown.compareAndSet(false, true)) { + return; + } + stopWatching(); + runCommand(composeCommand("stop", "db")); + } + + private void watchDatabase(ConfigurableApplicationContext context) { + scheduler = Executors.newSingleThreadScheduledExecutor(); + scheduler.scheduleWithFixedDelay(() -> { + if (shuttingDown.get()) { + return; + } + if (!isDatabaseRunning()) { + shuttingDown.set(true); + if (context.isActive()) { + context.close(); + } + } + }, WATCH_INTERVAL.toSeconds(), WATCH_INTERVAL.toSeconds(), TimeUnit.SECONDS); + } + + private void stopWatching() { + if (scheduler != null) { + scheduler.shutdownNow(); + scheduler = null; + } + } + + private boolean isDatabaseRunning() { + CommandResult result = runCommand(composeCommand("ps", "--status", "running", "--services", "db"), false); + return result.exitCode == 0 && result.output.lines() + .map(String::trim) + .anyMatch("db"::equals); + } + + private List composeCommand(String... args) { + List command = new ArrayList<>(); + command.add(resolveDockerExecutable()); + command.add("compose"); + command.add("-f"); + command.add("docker-compose.dev.yml"); + for (String arg : args) { + command.add(arg); + } + return command; + } + + private String resolveDockerExecutable() { + String os = System.getProperty("os.name", "").toLowerCase(Locale.ROOT); + return os.contains("win") ? "docker.exe" : "docker"; + } + + private void runCommand(List command) { + CommandResult result = runCommand(command, true); + if (result.exitCode != 0) { + throw new IllegalStateException(result.output.isBlank() ? "Command failed: " + String.join(" ", command) : result.output); + } + } + + private CommandResult runCommand(List command, boolean printOutput) { + ProcessBuilder builder = new ProcessBuilder(command); + builder.directory(projectDir.toFile()); + builder.redirectErrorStream(true); + + try { + Process process = builder.start(); + StringBuilder output = new StringBuilder(); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) { + String line; + while ((line = reader.readLine()) != null) { + output.append(line).append(System.lineSeparator()); + if (printOutput) { + System.out.println(line); + } + } + } + int exitCode = process.waitFor(); + return new CommandResult(exitCode, output.toString().trim()); + } catch (IOException ex) { + throw new IllegalStateException("Unable to run docker command", ex); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + throw new IllegalStateException("Docker command interrupted", ex); + } + } + } + + private record CommandResult(int exitCode, String output) { + } +} From 26ba6e36950253a6bc3222f66154cb4825be3e2f Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Wed, 11 Mar 2026 08:38:48 -0600 Subject: [PATCH 61/84] Update chat requests --- petshop-api.postman_collection.json | 161 ++++++++++++++++++++++++++-- 1 file changed, 154 insertions(+), 7 deletions(-) diff --git a/petshop-api.postman_collection.json b/petshop-api.postman_collection.json index 192c834c..c8fa07ca 100644 --- a/petshop-api.postman_collection.json +++ b/petshop-api.postman_collection.json @@ -2363,7 +2363,12 @@ ], "body": { "mode": "raw", - "raw": "{\n \"message\": \"I need help\"\n}" + "raw": "{\n \"message\": \"I need help\"\n}", + "options": { + "raw": { + "language": "json" + } + } } }, "event": [ @@ -2376,14 +2381,46 @@ " pm.response.to.have.status(201);", "});", "var jsonData = pm.response.json();", - "if (jsonData.id !== undefined) pm.collectionVariables.set('conversationId', jsonData.id);" + "pm.expect(jsonData.id).to.exist;", + "pm.collectionVariables.set('conversationId', jsonData.id);" ] } } ] }, { - "name": "List Conversations", + "name": "Customer List Conversations", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/chat/conversations", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{customerToken}}", + "type": "text" + } + ] + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});" + ] + } + } + ] + }, + { + "name": "Staff List Conversations", "request": { "method": "GET", "url": "{{baseUrl}}/api/v1/chat/conversations", @@ -2445,7 +2482,80 @@ ] }, { - "name": "Send Message", + "name": "Customer Request Human", + "request": { + "method": "POST", + "url": "{{baseUrl}}/api/v1/chat/conversations/{{conversationId}}/request-human", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{customerToken}}", + "type": "text" + } + ] + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});", + "var jsonData = pm.response.json();", + "pm.expect(jsonData.mode).to.eql('AUTOMATED');" + ] + } + } + ] + }, + { + "name": "Customer Send Message", + "request": { + "method": "POST", + "url": "{{baseUrl}}/api/v1/chat/conversations/{{conversationId}}/messages", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{customerToken}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"content\": \"Customer follow-up from Postman\"\n}", + "options": { + "raw": { + "language": "json" + } + } + } + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 201', function () {", + " pm.response.to.have.status(201);", + "});" + ] + } + } + ] + }, + { + "name": "Staff Send Message", "request": { "method": "POST", "url": "{{baseUrl}}/api/v1/chat/conversations/{{conversationId}}/messages", @@ -2478,14 +2588,16 @@ "exec": [ "pm.test('Status code is 201', function () {", " pm.response.to.have.status(201);", - "});" + "});", + "var jsonData = pm.response.json();", + "pm.expect(jsonData.senderRole).to.eql('STAFF');" ] } } ] }, { - "name": "Get Messages", + "name": "Customer Get Messages", "request": { "method": "GET", "url": "{{baseUrl}}/api/v1/chat/conversations/{{conversationId}}/messages", @@ -2509,7 +2621,42 @@ "exec": [ "pm.test('Status code is 200', function () {", " pm.response.to.have.status(200);", - "});" + "});", + "var jsonData = pm.response.json();", + "pm.expect(jsonData.length).to.be.above(0);" + ] + } + } + ] + }, + { + "name": "Staff Get Messages", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/chat/conversations/{{conversationId}}/messages", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{staffToken}}", + "type": "text" + } + ] + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});", + "var jsonData = pm.response.json();", + "pm.expect(jsonData.length).to.be.above(0);" ] } } From 2b5ea87e8806b4eae9c9b6524acfa81e32da199e Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Wed, 11 Mar 2026 08:40:58 -0600 Subject: [PATCH 62/84] Fix chat assertion --- petshop-api.postman_collection.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/petshop-api.postman_collection.json b/petshop-api.postman_collection.json index c8fa07ca..6ecec217 100644 --- a/petshop-api.postman_collection.json +++ b/petshop-api.postman_collection.json @@ -2590,7 +2590,8 @@ " pm.response.to.have.status(201);", "});", "var jsonData = pm.response.json();", - "pm.expect(jsonData.senderRole).to.eql('STAFF');" + "pm.expect(jsonData.senderId).to.exist;", + "pm.expect(jsonData.content).to.eql('Reply from Postman');" ] } } From d2b26dc1133504d8d60e90046a3b66e888f81856 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Wed, 11 Mar 2026 09:26:15 -0600 Subject: [PATCH 63/84] Upgrade to JDK 25 --- pom.xml | 9 ++++----- .../com/petshop/backend/security/SecurityConfig.java | 3 +-- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/pom.xml b/pom.xml index 9a3e6414..9e6252d0 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ org.springframework.boot spring-boot-starter-parent - 3.2.2 + 4.0.3 @@ -19,7 +19,7 @@ Spring Boot backend for PetShop desktop application - 17 + 25 @@ -87,7 +87,7 @@ org.springdoc springdoc-openapi-starter-webmvc-ui - 2.3.0 + 3.0.1 @@ -104,8 +104,7 @@ org.apache.maven.plugins maven-compiler-plugin - 17 - 17 + 25 diff --git a/src/main/java/com/petshop/backend/security/SecurityConfig.java b/src/main/java/com/petshop/backend/security/SecurityConfig.java index 38811256..696a2f88 100644 --- a/src/main/java/com/petshop/backend/security/SecurityConfig.java +++ b/src/main/java/com/petshop/backend/security/SecurityConfig.java @@ -56,8 +56,7 @@ public class SecurityConfig { @Bean public AuthenticationProvider authenticationProvider() { - DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(); - authProvider.setUserDetailsService(userDetailsService); + DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(userDetailsService); authProvider.setPasswordEncoder(passwordEncoder()); return authProvider; } From a3fa7f32edf6935f35dd52a775a65caef81d454e Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Wed, 11 Mar 2026 14:11:06 -0600 Subject: [PATCH 64/84] Fix backend warnings --- .../com/petshop/backend/BackendApplication.java | 2 ++ .../backend/security/SecurityConfig.java | 9 ++++----- .../backend/service/AnalyticsService.java | 2 ++ .../backend/service/AppointmentService.java | 3 +++ .../petshop/backend/service/SaleService.java | 2 ++ src/main/resources/application.yml | 3 ++- .../backend/config/RunConfigValidationTest.java | 17 ++++++++--------- 7 files changed, 23 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/petshop/backend/BackendApplication.java b/src/main/java/com/petshop/backend/BackendApplication.java index 05cede40..b2df1cc4 100644 --- a/src/main/java/com/petshop/backend/BackendApplication.java +++ b/src/main/java/com/petshop/backend/BackendApplication.java @@ -2,8 +2,10 @@ package com.petshop.backend; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.web.config.EnableSpringDataWebSupport; @SpringBootApplication +@EnableSpringDataWebSupport(pageSerializationMode = EnableSpringDataWebSupport.PageSerializationMode.VIA_DTO) public class BackendApplication { public static void main(String[] args) { SpringApplication.run(BackendApplication.class, args); diff --git a/src/main/java/com/petshop/backend/security/SecurityConfig.java b/src/main/java/com/petshop/backend/security/SecurityConfig.java index 696a2f88..0a893c18 100644 --- a/src/main/java/com/petshop/backend/security/SecurityConfig.java +++ b/src/main/java/com/petshop/backend/security/SecurityConfig.java @@ -4,7 +4,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.authentication.ProviderManager; import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; @@ -48,14 +48,13 @@ public class SecurityConfig { .anyRequest().authenticated() ) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) - .authenticationProvider(authenticationProvider()) + .authenticationProvider(daoAuthenticationProvider()) .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); } - @Bean - public AuthenticationProvider authenticationProvider() { + private DaoAuthenticationProvider daoAuthenticationProvider() { DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(userDetailsService); authProvider.setPasswordEncoder(passwordEncoder()); return authProvider; @@ -63,7 +62,7 @@ public class SecurityConfig { @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception { - return config.getAuthenticationManager(); + return new ProviderManager(daoAuthenticationProvider()); } @Bean diff --git a/src/main/java/com/petshop/backend/service/AnalyticsService.java b/src/main/java/com/petshop/backend/service/AnalyticsService.java index 18c9c9b8..32e6a5a7 100644 --- a/src/main/java/com/petshop/backend/service/AnalyticsService.java +++ b/src/main/java/com/petshop/backend/service/AnalyticsService.java @@ -8,6 +8,7 @@ import com.petshop.backend.repository.InventoryRepository; import com.petshop.backend.repository.ProductRepository; import com.petshop.backend.repository.SaleRepository; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; import java.time.LocalDate; @@ -30,6 +31,7 @@ public class AnalyticsService { this.productRepository = productRepository; } + @Transactional(readOnly = true) public DashboardResponse getDashboardData(int days, int top) { LocalDateTime startDate = LocalDateTime.now().minusDays(days); diff --git a/src/main/java/com/petshop/backend/service/AppointmentService.java b/src/main/java/com/petshop/backend/service/AppointmentService.java index d2fa5eaf..9abcf831 100644 --- a/src/main/java/com/petshop/backend/service/AppointmentService.java +++ b/src/main/java/com/petshop/backend/service/AppointmentService.java @@ -58,6 +58,7 @@ public class AppointmentService { this.employeeStoreRepository = employeeStoreRepository; } + @Transactional(readOnly = true) public Page getAllAppointments(String query, Pageable pageable, Long customerId) { Page appointments; @@ -78,6 +79,7 @@ public class AppointmentService { return appointments.map(this::mapToResponse); } + @Transactional(readOnly = true) public AppointmentResponse getAppointmentById(Long id, Long customerId) { Appointment appointment = appointmentRepository.findById(id) .orElseThrow(() -> new ResourceNotFoundException("Appointment not found with id: " + id)); @@ -166,6 +168,7 @@ public class AppointmentService { appointmentRepository.deleteAllById(request.getIds()); } + @Transactional(readOnly = true) public List checkAvailability(Long storeId, Long serviceId, LocalDate date) { storeRepository.findById(storeId) .orElseThrow(() -> new ResourceNotFoundException("Store not found with id: " + storeId)); diff --git a/src/main/java/com/petshop/backend/service/SaleService.java b/src/main/java/com/petshop/backend/service/SaleService.java index 55024b0c..b426dc38 100644 --- a/src/main/java/com/petshop/backend/service/SaleService.java +++ b/src/main/java/com/petshop/backend/service/SaleService.java @@ -40,6 +40,7 @@ public class SaleService { this.customerRepository = customerRepository; } + @Transactional(readOnly = true) public Page getAllSales(String query, Pageable pageable) { Page sales; if (query != null && !query.trim().isEmpty()) { @@ -50,6 +51,7 @@ public class SaleService { return sales.map(this::mapToResponse); } + @Transactional(readOnly = true) public SaleResponse getSaleById(Long id) { Sale sale = saleRepository.findById(id) .orElseThrow(() -> new ResourceNotFoundException("Sale not found with id: " + id)); diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 6338af51..81f7ed51 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -27,7 +27,7 @@ spring: properties: hibernate: format_sql: true - dialect: org.hibernate.dialect.MySQLDialect + open-in-view: false flyway: enabled: true @@ -53,3 +53,4 @@ logging: level: com.petshop: ${LOG_LEVEL:INFO} org.springframework.security: ${LOG_LEVEL_SECURITY:WARN} + org.springdoc.core.events.SpringDocAppInitializer: ERROR diff --git a/src/test/java/com/petshop/backend/config/RunConfigValidationTest.java b/src/test/java/com/petshop/backend/config/RunConfigValidationTest.java index 88851bae..6c1c4a85 100644 --- a/src/test/java/com/petshop/backend/config/RunConfigValidationTest.java +++ b/src/test/java/com/petshop/backend/config/RunConfigValidationTest.java @@ -64,18 +64,17 @@ class RunConfigValidationTest { } @Test - void databaseSchemaFileExists() { - File schemaFile = new File("src/main/resources/schema.sql"); - assertTrue(schemaFile.exists(), "schema.sql should exist in src/main/resources/"); + void flywayBaselineMigrationExists() { + File migrationFile = new File("src/main/resources/db/migration/V1__baseline_schema.sql"); + assertTrue(migrationFile.exists(), "Flyway baseline migration should exist in src/main/resources/db/migration/"); } @Test - void schemaContainsActiveColumn() throws Exception { - File schemaFile = new File("src/main/resources/schema.sql"); - String schemaContent = new String(java.nio.file.Files.readAllBytes(schemaFile.toPath())); + void flywayBaselineContainsActiveColumn() throws Exception { + File migrationFile = new File("src/main/resources/db/migration/V1__baseline_schema.sql"); + String migrationContent = new String(java.nio.file.Files.readAllBytes(migrationFile.toPath())); - assertTrue(schemaContent.contains("active BOOLEAN") || schemaContent.contains("active boolean"), - "schema.sql should contain 'active' column definition in users table. " + - "If you see 'Unknown column active' error, run 'Reset Database Only' configuration."); + assertTrue(migrationContent.contains("active BOOLEAN") || migrationContent.contains("active boolean"), + "Baseline migration should contain the users.active column definition."); } } From 29fd106e0cb90875c0e02c8d1f942de00975a6dd Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Wed, 11 Mar 2026 15:38:42 -0600 Subject: [PATCH 65/84] Fix backend run config --- pom.xml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pom.xml b/pom.xml index 9e6252d0..980c753b 100644 --- a/pom.xml +++ b/pom.xml @@ -116,6 +116,16 @@ exec-maven-plugin 3.1.0 + + dev-stack + + java + + + com.petshop.backend.DevStackApplication + runtime + + docker-up From 389b5685e32c35d94cc61823639f39ab873c2bda Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Wed, 11 Mar 2026 15:54:40 -0600 Subject: [PATCH 66/84] Run flyway explicitly --- .../petshop/backend/BackendApplication.java | 7 +++- .../petshop/backend/DevStackApplication.java | 5 ++- .../config/FlywayContextInitializer.java | 40 +++++++++++++++++++ 3 files changed, 49 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/petshop/backend/config/FlywayContextInitializer.java diff --git a/src/main/java/com/petshop/backend/BackendApplication.java b/src/main/java/com/petshop/backend/BackendApplication.java index b2df1cc4..2a9718c3 100644 --- a/src/main/java/com/petshop/backend/BackendApplication.java +++ b/src/main/java/com/petshop/backend/BackendApplication.java @@ -1,6 +1,7 @@ package com.petshop.backend; -import org.springframework.boot.SpringApplication; +import com.petshop.backend.config.FlywayContextInitializer; +import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.data.web.config.EnableSpringDataWebSupport; @@ -8,6 +9,8 @@ import org.springframework.data.web.config.EnableSpringDataWebSupport; @EnableSpringDataWebSupport(pageSerializationMode = EnableSpringDataWebSupport.PageSerializationMode.VIA_DTO) public class BackendApplication { public static void main(String[] args) { - SpringApplication.run(BackendApplication.class, args); + new SpringApplicationBuilder(BackendApplication.class) + .initializers(new FlywayContextInitializer()) + .run(args); } } diff --git a/src/main/java/com/petshop/backend/DevStackApplication.java b/src/main/java/com/petshop/backend/DevStackApplication.java index a049b7ee..76b76de4 100644 --- a/src/main/java/com/petshop/backend/DevStackApplication.java +++ b/src/main/java/com/petshop/backend/DevStackApplication.java @@ -1,5 +1,6 @@ package com.petshop.backend; +import com.petshop.backend.config.FlywayContextInitializer; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.event.ContextClosedEvent; @@ -30,7 +31,9 @@ public class DevStackApplication { try { controller.startDatabase(); - context = new SpringApplicationBuilder(BackendApplication.class).run(args); + context = new SpringApplicationBuilder(BackendApplication.class) + .initializers(new FlywayContextInitializer()) + .run(args); ConfigurableApplicationContext appContext = context; context.addApplicationListener(event -> { if (event instanceof ContextClosedEvent) { diff --git a/src/main/java/com/petshop/backend/config/FlywayContextInitializer.java b/src/main/java/com/petshop/backend/config/FlywayContextInitializer.java new file mode 100644 index 00000000..3fb28d10 --- /dev/null +++ b/src/main/java/com/petshop/backend/config/FlywayContextInitializer.java @@ -0,0 +1,40 @@ +package com.petshop.backend.config; + +import org.flywaydb.core.Flyway; +import org.flywaydb.core.api.MigrationVersion; +import org.springframework.context.ApplicationContextInitializer; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.core.env.ConfigurableEnvironment; + +import java.util.Arrays; + +public class FlywayContextInitializer implements ApplicationContextInitializer { + + @Override + public void initialize(ConfigurableApplicationContext applicationContext) { + ConfigurableEnvironment environment = applicationContext.getEnvironment(); + + String url = environment.getProperty("spring.datasource.url"); + String username = environment.getProperty("spring.datasource.username"); + String password = environment.getProperty("spring.datasource.password"); + + if (url == null || username == null || password == null) { + throw new IllegalStateException("Datasource properties are required before startup"); + } + + String[] locations = Arrays.stream(environment + .getProperty("spring.flyway.locations", "classpath:db/migration") + .split(",")) + .map(String::trim) + .filter(location -> !location.isEmpty()) + .toArray(String[]::new); + + Flyway.configure() + .dataSource(url, username, password) + .locations(locations) + .baselineOnMigrate(environment.getProperty("spring.flyway.baseline-on-migrate", Boolean.class, false)) + .baselineVersion(MigrationVersion.fromVersion(environment.getProperty("spring.flyway.baseline-version", "1"))) + .load() + .migrate(); + } +} From 52889b90b3c0875530c314cda7abf851f3a14b01 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Thu, 12 Mar 2026 17:36:16 -0600 Subject: [PATCH 67/84] Add token version --- .../java/com/petshop/backend/entity/User.java | 15 +++++++++++- .../db/migration/V5__user_token_version.sql | 2 ++ .../config/RunConfigValidationTest.java | 15 ++++++++++++ .../backend/entity/UserEntityTest.java | 24 +++++++++++++++++++ 4 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 src/main/resources/db/migration/V5__user_token_version.sql create mode 100644 src/test/java/com/petshop/backend/entity/UserEntityTest.java diff --git a/src/main/java/com/petshop/backend/entity/User.java b/src/main/java/com/petshop/backend/entity/User.java index 7a2cf43a..54bd202e 100644 --- a/src/main/java/com/petshop/backend/entity/User.java +++ b/src/main/java/com/petshop/backend/entity/User.java @@ -37,6 +37,9 @@ public class User { @Column(nullable = false) private Boolean active = true; + @Column(nullable = false) + private Integer tokenVersion = 0; + @CreationTimestamp @Column(name = "created_at", updatable = false) private LocalDateTime createdAt; @@ -52,7 +55,7 @@ public class User { public User() { } - public User(Long id, String username, String password, String email, String fullName, String avatarUrl, Role role, Boolean active, LocalDateTime createdAt, LocalDateTime updatedAt) { + public User(Long id, String username, String password, String email, String fullName, String avatarUrl, Role role, Boolean active, Integer tokenVersion, LocalDateTime createdAt, LocalDateTime updatedAt) { this.id = id; this.username = username; this.password = password; @@ -61,6 +64,7 @@ public class User { this.avatarUrl = avatarUrl; this.role = role; this.active = active; + this.tokenVersion = tokenVersion; this.createdAt = createdAt; this.updatedAt = updatedAt; } @@ -129,6 +133,14 @@ public class User { this.active = active; } + public Integer getTokenVersion() { + return tokenVersion; + } + + public void setTokenVersion(Integer tokenVersion) { + this.tokenVersion = tokenVersion; + } + public LocalDateTime getCreatedAt() { return createdAt; } @@ -169,6 +181,7 @@ public class User { ", avatarUrl='" + avatarUrl + '\'' + ", role=" + role + ", active=" + active + + ", tokenVersion=" + tokenVersion + ", createdAt=" + createdAt + ", updatedAt=" + updatedAt + '}'; diff --git a/src/main/resources/db/migration/V5__user_token_version.sql b/src/main/resources/db/migration/V5__user_token_version.sql new file mode 100644 index 00000000..455c4bc8 --- /dev/null +++ b/src/main/resources/db/migration/V5__user_token_version.sql @@ -0,0 +1,2 @@ +ALTER TABLE users + ADD COLUMN tokenVersion INT NOT NULL DEFAULT 0 AFTER active; diff --git a/src/test/java/com/petshop/backend/config/RunConfigValidationTest.java b/src/test/java/com/petshop/backend/config/RunConfigValidationTest.java index 6c1c4a85..58af8c4a 100644 --- a/src/test/java/com/petshop/backend/config/RunConfigValidationTest.java +++ b/src/test/java/com/petshop/backend/config/RunConfigValidationTest.java @@ -77,4 +77,19 @@ class RunConfigValidationTest { assertTrue(migrationContent.contains("active BOOLEAN") || migrationContent.contains("active boolean"), "Baseline migration should contain the users.active column definition."); } + + @Test + void userTokenVersionMigrationExists() { + File migrationFile = new File("src/main/resources/db/migration/V5__user_token_version.sql"); + assertTrue(migrationFile.exists(), "User tokenVersion migration should exist."); + } + + @Test + void userTokenVersionMigrationAddsColumn() throws Exception { + File migrationFile = new File("src/main/resources/db/migration/V5__user_token_version.sql"); + String migrationContent = new String(java.nio.file.Files.readAllBytes(migrationFile.toPath())); + + assertTrue(migrationContent.contains("ADD COLUMN tokenVersion INT NOT NULL DEFAULT 0"), + "User tokenVersion migration should add the tokenVersion column with default 0."); + } } diff --git a/src/test/java/com/petshop/backend/entity/UserEntityTest.java b/src/test/java/com/petshop/backend/entity/UserEntityTest.java new file mode 100644 index 00000000..57330bc8 --- /dev/null +++ b/src/test/java/com/petshop/backend/entity/UserEntityTest.java @@ -0,0 +1,24 @@ +package com.petshop.backend.entity; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class UserEntityTest { + + @Test + void tokenVersionDefaultsToZero() { + User user = new User(); + + assertEquals(0, user.getTokenVersion()); + } + + @Test + void tokenVersionCanBeUpdated() { + User user = new User(); + + user.setTokenVersion(3); + + assertEquals(3, user.getTokenVersion()); + } +} From 8fdd28cbbd92114ffdb6c459e70d7f050ab5d9e8 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Thu, 12 Mar 2026 17:44:39 -0600 Subject: [PATCH 68/84] Use claims auth --- .../backend/controller/AuthController.java | 17 +-- .../backend/security/AppPrincipal.java | 51 +++++++ .../security/JwtAuthenticationFilter.java | 62 +++++---- .../com/petshop/backend/security/JwtUtil.java | 35 ++++- .../security/JwtAuthenticationFilterTest.java | 129 ++++++++++++++++++ .../petshop/backend/security/JwtUtilTest.java | 59 ++++++++ 6 files changed, 304 insertions(+), 49 deletions(-) create mode 100644 src/main/java/com/petshop/backend/security/AppPrincipal.java create mode 100644 src/test/java/com/petshop/backend/security/JwtAuthenticationFilterTest.java create mode 100644 src/test/java/com/petshop/backend/security/JwtUtilTest.java diff --git a/src/main/java/com/petshop/backend/controller/AuthController.java b/src/main/java/com/petshop/backend/controller/AuthController.java index bf907257..0aca7d24 100644 --- a/src/main/java/com/petshop/backend/controller/AuthController.java +++ b/src/main/java/com/petshop/backend/controller/AuthController.java @@ -24,7 +24,6 @@ import org.springframework.security.authentication.InternalAuthenticationService import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.web.bind.annotation.*; @@ -89,13 +88,7 @@ public class AuthController { // Create or link customer record userBusinessLinkageService.ensureLinkedCustomer(savedUser); - UserDetails userDetails = new org.springframework.security.core.userdetails.User( - savedUser.getUsername(), - savedUser.getPassword(), - java.util.Collections.emptyList() - ); - - String token = jwtUtil.generateToken(userDetails); + String token = jwtUtil.generateToken(savedUser); return ResponseEntity.status(HttpStatus.CREATED).body(new RegisterResponse( savedUser.getId(), @@ -116,13 +109,7 @@ public class AuthController { User user = userRepository.findByUsername(request.getUsername()) .orElseThrow(() -> new UsernameNotFoundException("User not found")); - UserDetails userDetails = new org.springframework.security.core.userdetails.User( - user.getUsername(), - user.getPassword(), - java.util.Collections.emptyList() - ); - - String token = jwtUtil.generateToken(userDetails); + String token = jwtUtil.generateToken(user); return ResponseEntity.ok(new LoginResponse( token, diff --git a/src/main/java/com/petshop/backend/security/AppPrincipal.java b/src/main/java/com/petshop/backend/security/AppPrincipal.java new file mode 100644 index 00000000..30ceca66 --- /dev/null +++ b/src/main/java/com/petshop/backend/security/AppPrincipal.java @@ -0,0 +1,51 @@ +package com.petshop.backend.security; + +import com.petshop.backend.entity.User; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; + +import java.security.Principal; +import java.util.Collection; +import java.util.List; + +public class AppPrincipal implements Principal { + + private final Long userId; + private final String username; + private final User.Role role; + private final Integer tokenVersion; + private final List authorities; + + public AppPrincipal(Long userId, String username, User.Role role, Integer tokenVersion) { + this.userId = userId; + this.username = username; + this.role = role; + this.tokenVersion = tokenVersion; + this.authorities = List.of(new SimpleGrantedAuthority("ROLE_" + role.name())); + } + + public Long getUserId() { + return userId; + } + + @Override + public String getName() { + return username; + } + + public String getUsername() { + return username; + } + + public User.Role getRole() { + return role; + } + + public Integer getTokenVersion() { + return tokenVersion; + } + + public Collection getAuthorities() { + return authorities; + } +} diff --git a/src/main/java/com/petshop/backend/security/JwtAuthenticationFilter.java b/src/main/java/com/petshop/backend/security/JwtAuthenticationFilter.java index d79c88f6..8d311f74 100644 --- a/src/main/java/com/petshop/backend/security/JwtAuthenticationFilter.java +++ b/src/main/java/com/petshop/backend/security/JwtAuthenticationFilter.java @@ -1,15 +1,14 @@ package com.petshop.backend.security; +import com.petshop.backend.entity.User; +import com.petshop.backend.repository.UserRepository; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.lang.NonNull; -import org.springframework.security.authentication.DisabledException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; @@ -21,11 +20,11 @@ import java.time.LocalDateTime; public class JwtAuthenticationFilter extends OncePerRequestFilter { private final JwtUtil jwtUtil; - private final UserDetailsService userDetailsService; + private final UserRepository userRepository; - public JwtAuthenticationFilter(JwtUtil jwtUtil, UserDetailsService userDetailsService) { + public JwtAuthenticationFilter(JwtUtil jwtUtil, UserRepository userRepository) { this.jwtUtil = jwtUtil; - this.userDetailsService = userDetailsService; + this.userRepository = userRepository; } @Override @@ -36,38 +35,47 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { ) throws ServletException, IOException { final String authHeader = request.getHeader("Authorization"); final String jwt; - final String username; - if (authHeader == null || !authHeader.startsWith("Bearer ")) { filterChain.doFilter(request, response); return; } jwt = authHeader.substring(7); - username = jwtUtil.extractUsername(jwt); + Long userId = jwtUtil.extractUserId(jwt); - if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { - UserDetails userDetails; - try { - userDetails = userDetailsService.loadUserByUsername(username); - } catch (DisabledException ex) { - response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); - response.setContentType("application/json"); - response.getWriter().write( - "{\"status\":401,\"message\":\"" + ex.getMessage() + "\",\"timestamp\":\"" + LocalDateTime.now() + "\"}" - ); + if (userId != null && SecurityContextHolder.getContext().getAuthentication() == null) { + User user = userRepository.findById(userId).orElse(null); + if (user == null || user.getActive() == null || !user.getActive()) { + writeUnauthorized(response, "User account is inactive"); return; } - if (jwtUtil.validateToken(jwt, userDetails)) { - UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken( - userDetails, - null, - userDetails.getAuthorities() - ); - authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); - SecurityContextHolder.getContext().setAuthentication(authToken); + if (!jwtUtil.validateToken(jwt, user)) { + writeUnauthorized(response, "Invalid or expired token"); + return; } + + AppPrincipal principal = new AppPrincipal( + user.getId(), + user.getUsername(), + user.getRole(), + user.getTokenVersion() + ); + UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken( + principal, + null, + principal.getAuthorities() + ); + authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + SecurityContextHolder.getContext().setAuthentication(authToken); } filterChain.doFilter(request, response); } + + private void writeUnauthorized(HttpServletResponse response, String message) throws IOException { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.setContentType("application/json"); + response.getWriter().write( + "{\"status\":401,\"message\":\"" + message + "\",\"timestamp\":\"" + LocalDateTime.now() + "\"}" + ); + } } diff --git a/src/main/java/com/petshop/backend/security/JwtUtil.java b/src/main/java/com/petshop/backend/security/JwtUtil.java index 9381369b..3d4541bc 100644 --- a/src/main/java/com/petshop/backend/security/JwtUtil.java +++ b/src/main/java/com/petshop/backend/security/JwtUtil.java @@ -1,10 +1,10 @@ package com.petshop.backend.security; +import com.petshop.backend.entity.User; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.security.Keys; import org.springframework.beans.factory.annotation.Value; -import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Component; import javax.crypto.SecretKey; @@ -28,7 +28,20 @@ public class JwtUtil { } public String extractUsername(String token) { - return extractClaim(token, Claims::getSubject); + return extractAllClaims(token).get("username", String.class); + } + + public Long extractUserId(String token) { + return Long.parseLong(extractClaim(token, Claims::getSubject)); + } + + public String extractRole(String token) { + return extractAllClaims(token).get("role", String.class); + } + + public Integer extractTokenVersion(String token) { + Number tokenVersion = extractAllClaims(token).get("tokenVersion", Number.class); + return tokenVersion == null ? null : tokenVersion.intValue(); } public Date extractExpiration(String token) { @@ -52,9 +65,12 @@ public class JwtUtil { return extractExpiration(token).before(new Date()); } - public String generateToken(UserDetails userDetails) { + public String generateToken(User user) { Map claims = new HashMap<>(); - return createToken(claims, userDetails.getUsername()); + claims.put("username", user.getUsername()); + claims.put("role", user.getRole().name()); + claims.put("tokenVersion", user.getTokenVersion()); + return createToken(claims, user.getId().toString()); } private String createToken(Map claims, String subject) { @@ -67,8 +83,13 @@ public class JwtUtil { .compact(); } - public Boolean validateToken(String token, UserDetails userDetails) { - final String username = extractUsername(token); - return (username.equals(userDetails.getUsername()) && !isTokenExpired(token)); + public Boolean validateToken(String token, User user) { + Long userId = extractUserId(token); + String role = extractRole(token); + Integer tokenVersion = extractTokenVersion(token); + return user.getId().equals(userId) + && user.getRole().name().equals(role) + && user.getTokenVersion().equals(tokenVersion) + && !isTokenExpired(token); } } diff --git a/src/test/java/com/petshop/backend/security/JwtAuthenticationFilterTest.java b/src/test/java/com/petshop/backend/security/JwtAuthenticationFilterTest.java new file mode 100644 index 00000000..fa8b429c --- /dev/null +++ b/src/test/java/com/petshop/backend/security/JwtAuthenticationFilterTest.java @@ -0,0 +1,129 @@ +package com.petshop.backend.security; + +import com.petshop.backend.entity.User; +import com.petshop.backend.repository.UserRepository; +import jakarta.servlet.FilterChain; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.test.util.ReflectionTestUtils; + +import java.lang.reflect.Proxy; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicBoolean; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class JwtAuthenticationFilterTest { + + private JwtUtil jwtUtil; + + @BeforeEach + void setUp() { + jwtUtil = new JwtUtil(); + ReflectionTestUtils.setField(jwtUtil, "secret", "change_me_please_make_this_at_least_32_characters_long_for_security"); + ReflectionTestUtils.setField(jwtUtil, "expiration", 86400000L); + SecurityContextHolder.clearContext(); + } + + @AfterEach + void tearDown() { + SecurityContextHolder.clearContext(); + } + + @Test + void validTokenBuildsAppPrincipalAuthentication() throws Exception { + User user = buildUser(); + String token = jwtUtil.generateToken(user); + AtomicBoolean chainCalled = new AtomicBoolean(false); + JwtAuthenticationFilter filter = new JwtAuthenticationFilter(jwtUtil, userRepositoryFor(user)); + + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addHeader("Authorization", "Bearer " + token); + MockHttpServletResponse response = new MockHttpServletResponse(); + FilterChain chain = (req, res) -> chainCalled.set(true); + + filter.doFilter(request, response, chain); + + Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); + assertInstanceOf(AppPrincipal.class, principal); + assertEquals("staff-user", ((AppPrincipal) principal).getUsername()); + assertEquals(User.Role.STAFF, ((AppPrincipal) principal).getRole()); + assertTrue(chainCalled.get()); + } + + @Test + void inactiveUserReturnsUnauthorized() throws Exception { + User user = buildUser(); + user.setActive(false); + String token = jwtUtil.generateToken(user); + JwtAuthenticationFilter filter = new JwtAuthenticationFilter(jwtUtil, userRepositoryFor(user)); + + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addHeader("Authorization", "Bearer " + token); + MockHttpServletResponse response = new MockHttpServletResponse(); + + filter.doFilter(request, response, (req, res) -> { + }); + + assertEquals(401, response.getStatus()); + assertNull(SecurityContextHolder.getContext().getAuthentication()); + } + + @Test + void tokenVersionMismatchReturnsUnauthorized() throws Exception { + User user = buildUser(); + String token = jwtUtil.generateToken(user); + user.setTokenVersion(4); + JwtAuthenticationFilter filter = new JwtAuthenticationFilter(jwtUtil, userRepositoryFor(user)); + + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addHeader("Authorization", "Bearer " + token); + MockHttpServletResponse response = new MockHttpServletResponse(); + + filter.doFilter(request, response, (req, res) -> { + }); + + assertEquals(401, response.getStatus()); + assertNull(SecurityContextHolder.getContext().getAuthentication()); + } + + private UserRepository userRepositoryFor(User user) { + return (UserRepository) Proxy.newProxyInstance( + UserRepository.class.getClassLoader(), + new Class[]{UserRepository.class}, + (proxy, method, args) -> { + if ("findById".equals(method.getName())) { + return user.getId().equals(args[0]) ? Optional.of(user) : Optional.empty(); + } + if ("equals".equals(method.getName())) { + return proxy == args[0]; + } + if ("hashCode".equals(method.getName())) { + return System.identityHashCode(proxy); + } + if ("toString".equals(method.getName())) { + return "UserRepositoryProxy"; + } + throw new UnsupportedOperationException(method.getName()); + } + ); + } + + private User buildUser() { + User user = new User(); + user.setId(42L); + user.setUsername("staff-user"); + user.setPassword("encoded"); + user.setRole(User.Role.STAFF); + user.setActive(true); + user.setTokenVersion(3); + return user; + } +} diff --git a/src/test/java/com/petshop/backend/security/JwtUtilTest.java b/src/test/java/com/petshop/backend/security/JwtUtilTest.java new file mode 100644 index 00000000..14f9ba3e --- /dev/null +++ b/src/test/java/com/petshop/backend/security/JwtUtilTest.java @@ -0,0 +1,59 @@ +package com.petshop.backend.security; + +import com.petshop.backend.entity.User; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.test.util.ReflectionTestUtils; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class JwtUtilTest { + + private JwtUtil jwtUtil; + + @BeforeEach + void setUp() { + jwtUtil = new JwtUtil(); + ReflectionTestUtils.setField(jwtUtil, "secret", "change_me_please_make_this_at_least_32_characters_long_for_security"); + ReflectionTestUtils.setField(jwtUtil, "expiration", 86400000L); + } + + @Test + void generatedTokenContainsIdentityClaims() { + User user = buildUser(); + + String token = jwtUtil.generateToken(user); + + assertEquals(42L, jwtUtil.extractUserId(token)); + assertEquals("staff-user", jwtUtil.extractUsername(token)); + assertEquals("STAFF", jwtUtil.extractRole(token)); + assertEquals(7, jwtUtil.extractTokenVersion(token)); + assertTrue(jwtUtil.validateToken(token, user)); + } + + @Test + void validateTokenRejectsChangedRoleOrTokenVersion() { + User user = buildUser(); + String token = jwtUtil.generateToken(user); + + user.setRole(User.Role.ADMIN); + assertFalse(jwtUtil.validateToken(token, user)); + + user.setRole(User.Role.STAFF); + user.setTokenVersion(8); + assertFalse(jwtUtil.validateToken(token, user)); + } + + private User buildUser() { + User user = new User(); + user.setId(42L); + user.setUsername("staff-user"); + user.setPassword("encoded"); + user.setRole(User.Role.STAFF); + user.setActive(true); + user.setTokenVersion(7); + return user; + } +} From 9dcb4865fa0eec62e97eeb806065f928c1c1a75f Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Thu, 12 Mar 2026 18:16:20 -0600 Subject: [PATCH 69/84] Invalidate auth tokens --- .../backend/controller/AuthController.java | 48 ++++++++----------- .../backend/controller/ChatController.java | 11 +++-- .../petshop/backend/service/UserService.java | 9 ++++ .../backend/util/AuthenticationHelper.java | 26 +++++++++- 4 files changed, 61 insertions(+), 33 deletions(-) diff --git a/src/main/java/com/petshop/backend/controller/AuthController.java b/src/main/java/com/petshop/backend/controller/AuthController.java index 0aca7d24..717ca7fa 100644 --- a/src/main/java/com/petshop/backend/controller/AuthController.java +++ b/src/main/java/com/petshop/backend/controller/AuthController.java @@ -14,6 +14,7 @@ import com.petshop.backend.repository.EmployeeStoreRepository; import com.petshop.backend.repository.UserRepository; import com.petshop.backend.security.JwtUtil; import com.petshop.backend.service.UserBusinessLinkageService; +import com.petshop.backend.util.AuthenticationHelper; import jakarta.validation.Valid; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -22,8 +23,6 @@ import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.DisabledException; import org.springframework.security.authentication.InternalAuthenticationServiceException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.web.bind.annotation.*; @@ -137,11 +136,7 @@ public class AuthController { @GetMapping("/me") public ResponseEntity getCurrentUser() { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - String username = authentication.getName(); - - User user = userRepository.findByUsername(username) - .orElseThrow(() -> new UsernameNotFoundException("User not found")); + User user = getAuthenticatedUser(); EmployeeStore employeeStore = resolveEmployeeStore(user); @@ -159,11 +154,8 @@ public class AuthController { @PutMapping("/me") public ResponseEntity updateProfile(@Valid @RequestBody ProfileUpdateRequest request) { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - String username = authentication.getName(); - - User user = userRepository.findByUsername(username) - .orElseThrow(() -> new UsernameNotFoundException("User not found")); + User user = getAuthenticatedUser(); + boolean invalidateToken = false; if (request.getUsername() != null && !request.getUsername().equals(user.getUsername())) { if (userRepository.findByUsername(request.getUsername()).isPresent()) { @@ -172,6 +164,7 @@ public class AuthController { return ResponseEntity.status(HttpStatus.CONFLICT).body(error); } user.setUsername(request.getUsername()); + invalidateToken = true; } if (request.getEmail() != null && !request.getEmail().equals(user.getEmail())) { @@ -189,6 +182,11 @@ public class AuthController { if (request.getPassword() != null && !request.getPassword().isEmpty()) { user.setPassword(passwordEncoder.encode(request.getPassword())); + invalidateToken = true; + } + + if (invalidateToken) { + user.setTokenVersion(user.getTokenVersion() + 1); } User updatedUser = userRepository.save(user); @@ -219,11 +217,7 @@ public class AuthController { @PostMapping("/me/avatar") public ResponseEntity uploadAvatar(@RequestParam("avatar") MultipartFile file) { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - String username = authentication.getName(); - - User user = userRepository.findByUsername(username) - .orElseThrow(() -> new UsernameNotFoundException("User not found")); + User user = getAuthenticatedUser(); if (file.isEmpty()) { Map error = new HashMap<>(); @@ -275,11 +269,7 @@ public class AuthController { @GetMapping("/me/avatar") public ResponseEntity getAvatar() { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - String username = authentication.getName(); - - User user = userRepository.findByUsername(username) - .orElseThrow(() -> new UsernameNotFoundException("User not found")); + User user = getAuthenticatedUser(); if (user.getAvatarUrl() == null || user.getAvatarUrl().isEmpty()) { Map error = new HashMap<>(); @@ -294,11 +284,7 @@ public class AuthController { @DeleteMapping("/me/avatar") public ResponseEntity deleteAvatar() { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - String username = authentication.getName(); - - User user = userRepository.findByUsername(username) - .orElseThrow(() -> new UsernameNotFoundException("User not found")); + User user = getAuthenticatedUser(); if (user.getAvatarUrl() != null && !user.getAvatarUrl().isEmpty()) { try { @@ -322,4 +308,12 @@ public class AuthController { response.put("note", "Token remains valid until expiration. Clear token from client storage."); return ResponseEntity.ok(response); } + + private User getAuthenticatedUser() { + try { + return AuthenticationHelper.getAuthenticatedUser(userRepository); + } catch (RuntimeException ex) { + throw new UsernameNotFoundException(ex.getMessage(), ex); + } + } } diff --git a/src/main/java/com/petshop/backend/controller/ChatController.java b/src/main/java/com/petshop/backend/controller/ChatController.java index 4392b25d..8cfb5df3 100644 --- a/src/main/java/com/petshop/backend/controller/ChatController.java +++ b/src/main/java/com/petshop/backend/controller/ChatController.java @@ -9,12 +9,11 @@ import com.petshop.backend.repository.CustomerRepository; import com.petshop.backend.repository.UserRepository; import com.petshop.backend.service.ChatRealtimeService; import com.petshop.backend.service.ChatService; +import com.petshop.backend.util.AuthenticationHelper; import jakarta.validation.Valid; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.web.bind.annotation.*; @@ -37,9 +36,11 @@ public class ChatController { } private User getCurrentUser() { - UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); - return userRepository.findByUsername(userDetails.getUsername()) - .orElseThrow(() -> new UsernameNotFoundException("User not found")); + try { + return AuthenticationHelper.getAuthenticatedUser(userRepository); + } catch (RuntimeException ex) { + throw new UsernameNotFoundException(ex.getMessage(), ex); + } } @PostMapping("/conversations") diff --git a/src/main/java/com/petshop/backend/service/UserService.java b/src/main/java/com/petshop/backend/service/UserService.java index 3e8d36a1..ee705a8a 100644 --- a/src/main/java/com/petshop/backend/service/UserService.java +++ b/src/main/java/com/petshop/backend/service/UserService.java @@ -68,14 +68,23 @@ public class UserService { User user = userRepository.findById(id) .orElseThrow(() -> new ResourceNotFoundException("User not found with id: " + id)); + boolean invalidateToken = + !user.getUsername().equals(request.getUsername()) + || user.getRole() != request.getRole() + || !user.getActive().equals(request.getActive() != null ? request.getActive() : true); + user.setUsername(request.getUsername()); if (request.getPassword() != null && !request.getPassword().trim().isEmpty()) { user.setPassword(passwordEncoder.encode(request.getPassword())); + invalidateToken = true; } user.setFullName(request.getFullName()); user.setEmail(request.getEmail()); user.setRole(request.getRole()); user.setActive(request.getActive() != null ? request.getActive() : true); + if (invalidateToken) { + user.setTokenVersion(user.getTokenVersion() + 1); + } user = userRepository.save(user); return mapToResponse(user); diff --git a/src/main/java/com/petshop/backend/util/AuthenticationHelper.java b/src/main/java/com/petshop/backend/util/AuthenticationHelper.java index 26468202..b1ab33a1 100644 --- a/src/main/java/com/petshop/backend/util/AuthenticationHelper.java +++ b/src/main/java/com/petshop/backend/util/AuthenticationHelper.java @@ -6,6 +6,7 @@ import com.petshop.backend.entity.User; import com.petshop.backend.repository.CustomerRepository; import com.petshop.backend.repository.EmployeeRepository; import com.petshop.backend.repository.UserRepository; +import com.petshop.backend.security.AppPrincipal; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; @@ -13,11 +14,34 @@ import org.springframework.stereotype.Component; @Component public class AuthenticationHelper { - public static User getAuthenticatedUser(UserRepository userRepository) { + public static Authentication getAuthentication() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication == null || !authentication.isAuthenticated()) { throw new RuntimeException("No authenticated user found"); } + return authentication; + } + + public static AppPrincipal getAuthenticatedPrincipal() { + Object principal = getAuthentication().getPrincipal(); + if (principal instanceof AppPrincipal appPrincipal) { + return appPrincipal; + } + throw new RuntimeException("Authenticated principal is not supported"); + } + + public static Long getAuthenticatedUserId() { + return getAuthenticatedPrincipal().getUserId(); + } + + public static User getAuthenticatedUser(UserRepository userRepository) { + Authentication authentication = getAuthentication(); + Object principal = authentication.getPrincipal(); + + if (principal instanceof AppPrincipal appPrincipal) { + return userRepository.findById(appPrincipal.getUserId()) + .orElseThrow(() -> new RuntimeException("User not found: " + appPrincipal.getUserId())); + } String username = authentication.getName(); return userRepository.findByUsername(username) From 79e8eb08b87b53a8d9efec10d1713cdd40938b04 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Thu, 12 Mar 2026 18:27:02 -0600 Subject: [PATCH 70/84] Align websocket auth --- .../WebSocketAuthChannelInterceptor.java | 79 +++++++++++++------ .../controller/ChatWebSocketController.java | 20 +++-- 2 files changed, 72 insertions(+), 27 deletions(-) diff --git a/src/main/java/com/petshop/backend/config/WebSocketAuthChannelInterceptor.java b/src/main/java/com/petshop/backend/config/WebSocketAuthChannelInterceptor.java index 6f01c384..b62dfe34 100644 --- a/src/main/java/com/petshop/backend/config/WebSocketAuthChannelInterceptor.java +++ b/src/main/java/com/petshop/backend/config/WebSocketAuthChannelInterceptor.java @@ -2,6 +2,7 @@ package com.petshop.backend.config; import com.petshop.backend.entity.User; import com.petshop.backend.repository.UserRepository; +import com.petshop.backend.security.AppPrincipal; import com.petshop.backend.security.JwtUtil; import com.petshop.backend.service.ChatService; import org.springframework.messaging.Message; @@ -10,11 +11,9 @@ import org.springframework.messaging.simp.stomp.StompCommand; import org.springframework.messaging.simp.stomp.StompHeaderAccessor; import org.springframework.messaging.support.ChannelInterceptor; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.stereotype.Component; import java.security.Principal; -import java.util.Collections; import java.util.List; @Component @@ -46,41 +45,42 @@ public class WebSocketAuthChannelInterceptor implements ChannelInterceptor { throw new IllegalArgumentException("Missing websocket token"); } - String username = jwtUtil.extractUsername(token); - User user = userRepository.findByUsername(username) - .orElseThrow(() -> new IllegalArgumentException("User not found")); + Long userId = jwtUtil.extractUserId(token); + User user = userId == null ? null : userRepository.findById(userId).orElse(null); + if (user == null) { + throw new IllegalArgumentException("User not found"); + } if (user.getActive() == null || !user.getActive()) { throw new IllegalArgumentException("User account is inactive"); } + if (!jwtUtil.validateToken(token, user)) { + throw new IllegalArgumentException("Invalid websocket token"); + } - UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken( + AppPrincipal principal = new AppPrincipal( + user.getId(), user.getUsername(), + user.getRole(), + user.getTokenVersion() + ); + UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken( + principal, null, - Collections.singletonList(new SimpleGrantedAuthority("ROLE_" + user.getRole().name())) + principal.getAuthorities() ); accessor.setUser(authentication); accessor.getSessionAttributes().put("user", authentication); return message; } - if (StompCommand.DISCONNECT.equals(command) || StompCommand.UNSUBSCRIBE.equals(command)) { - return message; - } - - Principal principal = accessor.getUser(); - if (principal == null && accessor.getSessionAttributes() != null) { - Object sessionUser = accessor.getSessionAttributes().get("user"); - if (sessionUser instanceof Principal storedPrincipal) { - accessor.setUser(storedPrincipal); - principal = storedPrincipal; - } - } - if (principal == null) { + User user = resolveUser(accessor.getUser(), accessor); + if (user == null) { throw new IllegalArgumentException("Unauthenticated websocket session"); } - User user = userRepository.findByUsername(principal.getName()) - .orElseThrow(() -> new IllegalArgumentException("User not found")); + if (StompCommand.DISCONNECT.equals(command) || StompCommand.UNSUBSCRIBE.equals(command)) { + return message; + } if (StompCommand.SUBSCRIBE.equals(command)) { authorizeSubscription(accessor.getDestination(), user); @@ -91,6 +91,41 @@ public class WebSocketAuthChannelInterceptor implements ChannelInterceptor { return message; } + private User resolveUser(Principal principal, StompHeaderAccessor accessor) { + Principal currentPrincipal = principal; + if (currentPrincipal == null && accessor.getSessionAttributes() != null) { + Object sessionUser = accessor.getSessionAttributes().get("user"); + if (sessionUser instanceof Principal storedPrincipal) { + accessor.setUser(storedPrincipal); + currentPrincipal = storedPrincipal; + } + } + + if (currentPrincipal instanceof UsernamePasswordAuthenticationToken authenticationToken + && authenticationToken.getPrincipal() instanceof AppPrincipal appPrincipal) { + return userRepository.findById(appPrincipal.getUserId()) + .orElseThrow(() -> new IllegalArgumentException("User not found")); + } + + if (currentPrincipal instanceof AppPrincipal appPrincipal) { + return userRepository.findById(appPrincipal.getUserId()) + .orElseThrow(() -> new IllegalArgumentException("User not found")); + } + + String tokenHeader = firstHeader(accessor, "Authorization"); + String token = extractToken(tokenHeader != null ? tokenHeader : firstHeader(accessor, "token")); + if (token == null || token.isBlank()) { + return null; + } + + Long userId = jwtUtil.extractUserId(token); + User user = userId == null ? null : userRepository.findById(userId).orElse(null); + if (user == null || user.getActive() == null || !user.getActive() || !jwtUtil.validateToken(token, user)) { + throw new IllegalArgumentException("User not found"); + } + return user; + } + private void authorizeSubscription(String destination, User user) { if (destination == null || destination.startsWith("/user/queue/")) { return; diff --git a/src/main/java/com/petshop/backend/controller/ChatWebSocketController.java b/src/main/java/com/petshop/backend/controller/ChatWebSocketController.java index bb30bd44..d7f1e3b6 100644 --- a/src/main/java/com/petshop/backend/controller/ChatWebSocketController.java +++ b/src/main/java/com/petshop/backend/controller/ChatWebSocketController.java @@ -4,6 +4,7 @@ import com.petshop.backend.dto.chat.MessageRequest; import com.petshop.backend.dto.chat.MessageResponse; import com.petshop.backend.entity.User; import com.petshop.backend.repository.UserRepository; +import com.petshop.backend.security.AppPrincipal; import com.petshop.backend.security.JwtUtil; import com.petshop.backend.service.ChatRealtimeService; import com.petshop.backend.service.ChatService; @@ -43,8 +44,14 @@ public class ChatWebSocketController { private User resolveUser(SimpMessageHeaderAccessor headerAccessor) { Principal principal = headerAccessor.getUser(); - if (principal != null) { - return userRepository.findByUsername(principal.getName()) + if (principal instanceof org.springframework.security.authentication.UsernamePasswordAuthenticationToken authenticationToken + && authenticationToken.getPrincipal() instanceof AppPrincipal appPrincipal) { + return userRepository.findById(appPrincipal.getUserId()) + .orElseThrow(() -> new IllegalArgumentException("User not found")); + } + + if (principal instanceof AppPrincipal appPrincipal) { + return userRepository.findById(appPrincipal.getUserId()) .orElseThrow(() -> new IllegalArgumentException("User not found")); } @@ -57,8 +64,11 @@ public class ChatWebSocketController { } String token = tokenHeader.startsWith("Bearer ") ? tokenHeader.substring(7) : tokenHeader; - String username = jwtUtil.extractUsername(token); - return userRepository.findByUsername(username) - .orElseThrow(() -> new IllegalArgumentException("User not found")); + Long userId = jwtUtil.extractUserId(token); + User user = userId == null ? null : userRepository.findById(userId).orElse(null); + if (user == null || user.getActive() == null || !user.getActive() || !jwtUtil.validateToken(token, user)) { + throw new IllegalArgumentException("User not found"); + } + return user; } } From 8368ff2359b8f0c77e344c52bf0b38d70e128201 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Fri, 13 Mar 2026 10:05:32 -0600 Subject: [PATCH 71/84] Harden run configs --- pom.xml | 10 + .../petshop/backend/DevStackApplication.java | 215 +++++++++--------- .../petshop/backend/DockerComposeSupport.java | 164 +++++++++++++ .../backend/ResetDatabaseApplication.java | 10 + 4 files changed, 296 insertions(+), 103 deletions(-) create mode 100644 src/main/java/com/petshop/backend/DockerComposeSupport.java create mode 100644 src/main/java/com/petshop/backend/ResetDatabaseApplication.java diff --git a/pom.xml b/pom.xml index 980c753b..1d3a79ac 100644 --- a/pom.xml +++ b/pom.xml @@ -126,6 +126,16 @@ runtime + + reset-db + + java + + + com.petshop.backend.ResetDatabaseApplication + runtime + + docker-up diff --git a/src/main/java/com/petshop/backend/DevStackApplication.java b/src/main/java/com/petshop/backend/DevStackApplication.java index 76b76de4..f60e9957 100644 --- a/src/main/java/com/petshop/backend/DevStackApplication.java +++ b/src/main/java/com/petshop/backend/DevStackApplication.java @@ -2,17 +2,12 @@ package com.petshop.backend; import com.petshop.backend.config.FlywayContextInitializer; import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.web.server.PortInUseException; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.event.ContextClosedEvent; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.nio.file.Path; -import java.nio.file.Paths; +import java.net.BindException; import java.time.Duration; -import java.util.ArrayList; -import java.util.List; import java.util.Locale; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executors; @@ -25,133 +20,147 @@ public class DevStackApplication { private static final Duration WATCH_INTERVAL = Duration.ofSeconds(2); public static void main(String[] args) { - DevStackController controller = new DevStackController(); + DockerComposeSupport docker = new DockerComposeSupport(); ConfigurableApplicationContext context = null; CountDownLatch shutdownLatch = new CountDownLatch(1); + ScheduledExecutorService scheduler = null; + AtomicBoolean shuttingDown = new AtomicBoolean(false); try { - controller.startDatabase(); + validateJavaVersion(); + docker.ensureDockerAvailable(); + docker.startDatabase(); context = new SpringApplicationBuilder(BackendApplication.class) .initializers(new FlywayContextInitializer()) .run(args); - ConfigurableApplicationContext appContext = context; context.addApplicationListener(event -> { if (event instanceof ContextClosedEvent) { shutdownLatch.countDown(); } }); - controller.watchDatabase(appContext); + scheduler = startDatabaseWatch(docker, context, shuttingDown); shutdownLatch.await(); + } catch (RuntimeException ex) { + throw new IllegalStateException(describeStartupFailure(ex), ex); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); throw new IllegalStateException("Dev stack interrupted", ex); } finally { - controller.stopWatching(); + if (scheduler != null) { + scheduler.shutdownNow(); + } if (context != null && context.isActive()) { context.close(); } - controller.stopDatabase(); + if (!shuttingDown.get()) { + docker.stopDatabase(); + } } } - private static final class DevStackController { - private final Path projectDir = Paths.get("").toAbsolutePath(); - private final AtomicBoolean shuttingDown = new AtomicBoolean(false); - private ScheduledExecutorService scheduler; - - private void startDatabase() { - runCommand(composeCommand("up", "-d", "--wait", "db")); + private static void validateJavaVersion() { + int feature = Runtime.version().feature(); + if (feature < 25) { + throw new IllegalStateException("JDK 25 or newer is required. IntelliJ is currently using Java " + Runtime.version() + "."); } + } - private void stopDatabase() { - if (!shuttingDown.compareAndSet(false, true)) { + private static ScheduledExecutorService startDatabaseWatch(DockerComposeSupport docker, ConfigurableApplicationContext context, AtomicBoolean shuttingDown) { + ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); + scheduler.scheduleWithFixedDelay(() -> { + if (shuttingDown.get()) { return; } - stopWatching(); - runCommand(composeCommand("stop", "db")); - } - - private void watchDatabase(ConfigurableApplicationContext context) { - scheduler = Executors.newSingleThreadScheduledExecutor(); - scheduler.scheduleWithFixedDelay(() -> { - if (shuttingDown.get()) { - return; + if (!docker.isDatabaseRunning()) { + shuttingDown.set(true); + if (context.isActive()) { + context.close(); } - if (!isDatabaseRunning()) { - shuttingDown.set(true); - if (context.isActive()) { - context.close(); - } + } + }, WATCH_INTERVAL.toSeconds(), WATCH_INTERVAL.toSeconds(), TimeUnit.SECONDS); + return scheduler; + } + + private static String describeStartupFailure(RuntimeException ex) { + Throwable cause = deepestCause(ex); + String message = cause.getMessage(); + String lowerMessage = ""; + if (message != null) { + lowerMessage = message.toLowerCase(Locale.ROOT); + } + + switch (classifyStartupFailure(cause, lowerMessage)) { + case SERVER_PORT_IN_USE: + return "Backend startup failed because port 8080 is already in use. Stop the other process or set SERVER_PORT to a different value."; + case DOCKER_NOT_RUNNING: + return "Backend startup failed because Docker is not available. Start Docker Desktop and try again."; + case DOCKER_NOT_INSTALLED: + return "Backend startup failed because Docker is not installed or not on PATH. Install Docker Desktop and reopen IntelliJ."; + case DATABASE_PORT_IN_USE: + return "Backend startup failed because port 3306 is already in use. Stop the conflicting MySQL service or container, then run Reset Database and try again."; + case STALE_SCHEMA: + return "Backend startup failed because the database schema is stale or incomplete. Run Reset Database and then start Pet Shop Application again."; + case FLYWAY_MIGRATION_FAILED: + return "Backend startup failed during Flyway migration. Run Reset Database and check the database logs if the problem persists."; + case DATASOURCE_CONFIG_MISSING: + return "Backend startup failed because datasource configuration was not loaded. Reload the Maven project and rerun Pet Shop Application."; + case DATABASE_UNREACHABLE: + return "Backend startup failed because it could not connect to MySQL. Make sure Docker Desktop is running, the database container is healthy, and port 3306 is reachable."; + case UNKNOWN: + if (message == null || message.isBlank()) { + return "Backend startup failed: " + ex.getClass().getSimpleName(); } - }, WATCH_INTERVAL.toSeconds(), WATCH_INTERVAL.toSeconds(), TimeUnit.SECONDS); - } - - private void stopWatching() { - if (scheduler != null) { - scheduler.shutdownNow(); - scheduler = null; - } - } - - private boolean isDatabaseRunning() { - CommandResult result = runCommand(composeCommand("ps", "--status", "running", "--services", "db"), false); - return result.exitCode == 0 && result.output.lines() - .map(String::trim) - .anyMatch("db"::equals); - } - - private List composeCommand(String... args) { - List command = new ArrayList<>(); - command.add(resolveDockerExecutable()); - command.add("compose"); - command.add("-f"); - command.add("docker-compose.dev.yml"); - for (String arg : args) { - command.add(arg); - } - return command; - } - - private String resolveDockerExecutable() { - String os = System.getProperty("os.name", "").toLowerCase(Locale.ROOT); - return os.contains("win") ? "docker.exe" : "docker"; - } - - private void runCommand(List command) { - CommandResult result = runCommand(command, true); - if (result.exitCode != 0) { - throw new IllegalStateException(result.output.isBlank() ? "Command failed: " + String.join(" ", command) : result.output); - } - } - - private CommandResult runCommand(List command, boolean printOutput) { - ProcessBuilder builder = new ProcessBuilder(command); - builder.directory(projectDir.toFile()); - builder.redirectErrorStream(true); - - try { - Process process = builder.start(); - StringBuilder output = new StringBuilder(); - try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) { - String line; - while ((line = reader.readLine()) != null) { - output.append(line).append(System.lineSeparator()); - if (printOutput) { - System.out.println(line); - } - } - } - int exitCode = process.waitFor(); - return new CommandResult(exitCode, output.toString().trim()); - } catch (IOException ex) { - throw new IllegalStateException("Unable to run docker command", ex); - } catch (InterruptedException ex) { - Thread.currentThread().interrupt(); - throw new IllegalStateException("Docker command interrupted", ex); - } + return "Backend startup failed: " + message; + default: + throw new IllegalStateException("Unhandled startup failure classification"); } } - private record CommandResult(int exitCode, String output) { + private static StartupFailure classifyStartupFailure(Throwable cause, String lowerMessage) { + if (cause instanceof PortInUseException || cause instanceof BindException || lowerMessage.contains("port 8080") && lowerMessage.contains("already in use")) { + return StartupFailure.SERVER_PORT_IN_USE; + } + if (lowerMessage.contains("docker desktop") || lowerMessage.contains("docker daemon") || lowerMessage.contains("docker engine") || lowerMessage.contains("cannot connect to the docker daemon")) { + return StartupFailure.DOCKER_NOT_RUNNING; + } + if (lowerMessage.contains("docker executable") || lowerMessage.contains("no such file or directory") && lowerMessage.contains("docker")) { + return StartupFailure.DOCKER_NOT_INSTALLED; + } + if (lowerMessage.contains("port is already allocated") || lowerMessage.contains("address already in use") && lowerMessage.contains("3306")) { + return StartupFailure.DATABASE_PORT_IN_USE; + } + if (lowerMessage.contains("schema validation: missing table")) { + return StartupFailure.STALE_SCHEMA; + } + if (lowerMessage.contains("flyway") || lowerMessage.contains("migration")) { + return StartupFailure.FLYWAY_MIGRATION_FAILED; + } + if (lowerMessage.contains("datasource properties are required")) { + return StartupFailure.DATASOURCE_CONFIG_MISSING; + } + if (lowerMessage.contains("access denied for user") || lowerMessage.contains("communications link failure") || lowerMessage.contains("connection refused") || lowerMessage.contains("unable to determine dialect without jdbc metadata")) { + return StartupFailure.DATABASE_UNREACHABLE; + } + return StartupFailure.UNKNOWN; + } + + private static Throwable deepestCause(Throwable throwable) { + Throwable current = throwable; + while (current.getCause() != null && current.getCause() != current) { + current = current.getCause(); + } + return current; + } + + private enum StartupFailure { + SERVER_PORT_IN_USE, + DOCKER_NOT_RUNNING, + DOCKER_NOT_INSTALLED, + DATABASE_PORT_IN_USE, + STALE_SCHEMA, + FLYWAY_MIGRATION_FAILED, + DATASOURCE_CONFIG_MISSING, + DATABASE_UNREACHABLE, + UNKNOWN } } diff --git a/src/main/java/com/petshop/backend/DockerComposeSupport.java b/src/main/java/com/petshop/backend/DockerComposeSupport.java new file mode 100644 index 00000000..2a1f4396 --- /dev/null +++ b/src/main/java/com/petshop/backend/DockerComposeSupport.java @@ -0,0 +1,164 @@ +package com.petshop.backend; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +final class DockerComposeSupport { + + private final Path projectDir = Paths.get("").toAbsolutePath(); + + void ensureDockerAvailable() { + CommandResult versionResult = runCommand(List.of(resolveDockerExecutable(), "version"), false); + if (versionResult.exitCode != 0) { + throw new IllegalStateException(describeDockerFailure(versionResult.output)); + } + } + + void startDatabase() { + runCommand(composeCommand("up", "-d", "--wait", "db")); + } + + void stopDatabase() { + runCommand(composeCommand("stop", "db")); + } + + void resetDatabase() { + runCommand(composeCommand("down", "-v", "--remove-orphans")); + String volumeName = getDatabaseVolumeName(); + if (volumeExists(volumeName)) { + runCommand(List.of(resolveDockerExecutable(), "volume", "rm", "-f", volumeName)); + } + } + + boolean isDatabaseRunning() { + CommandResult result = runCommand(composeCommand("ps", "--status", "running", "--services", "db"), false); + if (result.exitCode != 0) { + return false; + } + return result.output.lines() + .map(String::trim) + .anyMatch("db"::equals); + } + + private boolean volumeExists(String volumeName) { + CommandResult result = runCommand(List.of(resolveDockerExecutable(), "volume", "ls", "--format", "{{.Name}}"), false); + if (result.exitCode != 0) { + return false; + } + return result.output.lines() + .map(String::trim) + .anyMatch(volumeName::equals); + } + + private String getDatabaseVolumeName() { + return projectDir.getFileName().toString() + "_db_data"; + } + + private List composeCommand(String... args) { + List command = new ArrayList<>(); + command.add(resolveDockerExecutable()); + command.add("compose"); + command.add("-f"); + command.add("docker-compose.dev.yml"); + for (String arg : args) { + command.add(arg); + } + return command; + } + + private String resolveDockerExecutable() { + String os = System.getProperty("os.name", "").toLowerCase(Locale.ROOT); + if (os.contains("win")) { + return "docker.exe"; + } + return "docker"; + } + + private void runCommand(List command) { + CommandResult result = runCommand(command, true); + if (result.exitCode != 0) { + throw new IllegalStateException(describeCommandFailure(command, result.output)); + } + } + + private CommandResult runCommand(List command, boolean printOutput) { + ProcessBuilder builder = new ProcessBuilder(command); + builder.directory(projectDir.toFile()); + builder.redirectErrorStream(true); + + try { + Process process = builder.start(); + StringBuilder output = new StringBuilder(); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) { + String line; + while ((line = reader.readLine()) != null) { + output.append(line).append(System.lineSeparator()); + if (printOutput) { + System.out.println(line); + } + } + } + int exitCode = process.waitFor(); + return new CommandResult(exitCode, output.toString().trim()); + } catch (IOException ex) { + String executable = command.isEmpty() ? "docker" : command.getFirst(); + throw new IllegalStateException("Unable to run " + executable + ". Install Docker Desktop and make sure it is available on PATH.", ex); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + throw new IllegalStateException("Docker command interrupted", ex); + } + } + + private String describeDockerFailure(String output) { + String lowerOutput = ""; + if (output != null) { + lowerOutput = output.toLowerCase(Locale.ROOT); + } + if (lowerOutput.contains("docker desktop") || lowerOutput.contains("docker daemon") || lowerOutput.contains("docker engine") || lowerOutput.contains("cannot connect")) { + return "Docker Desktop is not running. Start Docker Desktop and rerun the command."; + } + if (output == null || output.isBlank()) { + return "Docker is unavailable. Start Docker Desktop and rerun the command."; + } + return output; + } + + private String describeCommandFailure(List command, String output) { + String renderedCommand = String.join(" ", command); + String lowerOutput = ""; + if (output != null) { + lowerOutput = output.toLowerCase(Locale.ROOT); + } + if (renderedCommand.contains(" up ") || renderedCommand.endsWith(" up -d --wait db")) { + if (lowerOutput.contains("port is already allocated") || lowerOutput.contains("address already in use")) { + return "Database startup failed because port 3306 is already in use. Stop the conflicting MySQL service or container, then run Reset Database."; + } + if (lowerOutput.contains("docker desktop") || lowerOutput.contains("docker daemon") || lowerOutput.contains("docker engine")) { + return "Database startup failed because Docker Desktop is not running."; + } + if (output == null || output.isBlank()) { + return "Database startup failed while bringing up the Docker MySQL container."; + } + return output; + } + if (renderedCommand.contains(" volume rm ")) { + if (output == null || output.isBlank()) { + return "Database reset failed while removing the Docker volume."; + } + return output; + } + if (output == null || output.isBlank()) { + return "Command failed: " + renderedCommand; + } + return output; + } + + private record CommandResult(int exitCode, String output) { + } +} diff --git a/src/main/java/com/petshop/backend/ResetDatabaseApplication.java b/src/main/java/com/petshop/backend/ResetDatabaseApplication.java new file mode 100644 index 00000000..056ec36e --- /dev/null +++ b/src/main/java/com/petshop/backend/ResetDatabaseApplication.java @@ -0,0 +1,10 @@ +package com.petshop.backend; + +public class ResetDatabaseApplication { + + public static void main(String[] args) { + DockerComposeSupport docker = new DockerComposeSupport(); + docker.ensureDockerAvailable(); + docker.resetDatabase(); + } +} From 3cabc7b8ffa384e39bb51d2c3cb973cef8761b82 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Fri, 13 Mar 2026 10:25:50 -0600 Subject: [PATCH 72/84] Add startup checks --- pom.xml | 21 ++++++++++++++++ .../petshop/backend/BackendApplication.java | 1 + .../petshop/backend/DevStackApplication.java | 1 + .../backend/RuntimeClasspathValidator.java | 24 +++++++++++++++++++ 4 files changed, 47 insertions(+) create mode 100644 src/main/java/com/petshop/backend/RuntimeClasspathValidator.java diff --git a/pom.xml b/pom.xml index 1d3a79ac..de496351 100644 --- a/pom.xml +++ b/pom.xml @@ -107,6 +107,27 @@ 25 + + org.apache.maven.plugins + maven-enforcer-plugin + 3.5.0 + + + require-java-25 + + enforce + + + + + [25,) + JDK 25 or newer is required. Configure IntelliJ and Maven to use JDK 25 before running the backend. + + + + + + org.springframework.boot spring-boot-maven-plugin diff --git a/src/main/java/com/petshop/backend/BackendApplication.java b/src/main/java/com/petshop/backend/BackendApplication.java index 2a9718c3..87268e9a 100644 --- a/src/main/java/com/petshop/backend/BackendApplication.java +++ b/src/main/java/com/petshop/backend/BackendApplication.java @@ -9,6 +9,7 @@ import org.springframework.data.web.config.EnableSpringDataWebSupport; @EnableSpringDataWebSupport(pageSerializationMode = EnableSpringDataWebSupport.PageSerializationMode.VIA_DTO) public class BackendApplication { public static void main(String[] args) { + RuntimeClasspathValidator.validate(); new SpringApplicationBuilder(BackendApplication.class) .initializers(new FlywayContextInitializer()) .run(args); diff --git a/src/main/java/com/petshop/backend/DevStackApplication.java b/src/main/java/com/petshop/backend/DevStackApplication.java index f60e9957..4fd47ffc 100644 --- a/src/main/java/com/petshop/backend/DevStackApplication.java +++ b/src/main/java/com/petshop/backend/DevStackApplication.java @@ -28,6 +28,7 @@ public class DevStackApplication { try { validateJavaVersion(); + RuntimeClasspathValidator.validate(); docker.ensureDockerAvailable(); docker.startDatabase(); context = new SpringApplicationBuilder(BackendApplication.class) diff --git a/src/main/java/com/petshop/backend/RuntimeClasspathValidator.java b/src/main/java/com/petshop/backend/RuntimeClasspathValidator.java new file mode 100644 index 00000000..ad18d198 --- /dev/null +++ b/src/main/java/com/petshop/backend/RuntimeClasspathValidator.java @@ -0,0 +1,24 @@ +package com.petshop.backend; + +import java.net.URL; + +final class RuntimeClasspathValidator { + + private RuntimeClasspathValidator() { + } + + static void validate() { + if (!resourceExists("application.yml")) { + throw new IllegalStateException("Backend resources are missing from the runtime classpath. Reimport the Maven project in IntelliJ and run the shared Maven run configuration."); + } + if (!resourceExists("db/migration/V1__baseline_schema.sql")) { + throw new IllegalStateException("Flyway migration files are missing from the runtime classpath. Reimport the Maven project in IntelliJ and run the shared Maven run configuration."); + } + } + + private static boolean resourceExists(String path) { + ClassLoader classLoader = RuntimeClasspathValidator.class.getClassLoader(); + URL resource = classLoader.getResource(path); + return resource != null; + } +} From 885d9689784ed617da091c5a4dbd2f9f1fbe06be Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Fri, 13 Mar 2026 14:13:31 -0600 Subject: [PATCH 73/84] Find docker on windows --- .../java/com/petshop/backend/DockerComposeSupport.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/main/java/com/petshop/backend/DockerComposeSupport.java b/src/main/java/com/petshop/backend/DockerComposeSupport.java index 2a1f4396..904ad24f 100644 --- a/src/main/java/com/petshop/backend/DockerComposeSupport.java +++ b/src/main/java/com/petshop/backend/DockerComposeSupport.java @@ -3,6 +3,7 @@ package com.petshop.backend; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; +import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; @@ -75,6 +76,14 @@ final class DockerComposeSupport { private String resolveDockerExecutable() { String os = System.getProperty("os.name", "").toLowerCase(Locale.ROOT); if (os.contains("win")) { + Path dockerPath = Paths.get("C:", "Program Files", "Docker", "Docker", "resources", "bin", "docker.exe"); + if (Files.isRegularFile(dockerPath)) { + return dockerPath.toString(); + } + Path dockerPathAlt = Paths.get("C:", "Program Files", "Docker", "Docker", "resources", "docker.exe"); + if (Files.isRegularFile(dockerPathAlt)) { + return dockerPathAlt.toString(); + } return "docker.exe"; } return "docker"; From 462e37a4431604c8ce8dc3901c343711989c8633 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Fri, 13 Mar 2026 14:16:21 -0600 Subject: [PATCH 74/84] Improve docker errors --- src/main/java/com/petshop/backend/DockerComposeSupport.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/petshop/backend/DockerComposeSupport.java b/src/main/java/com/petshop/backend/DockerComposeSupport.java index 904ad24f..1ff43a77 100644 --- a/src/main/java/com/petshop/backend/DockerComposeSupport.java +++ b/src/main/java/com/petshop/backend/DockerComposeSupport.java @@ -129,7 +129,7 @@ final class DockerComposeSupport { if (output != null) { lowerOutput = output.toLowerCase(Locale.ROOT); } - if (lowerOutput.contains("docker desktop") || lowerOutput.contains("docker daemon") || lowerOutput.contains("docker engine") || lowerOutput.contains("cannot connect")) { + if (lowerOutput.contains("docker desktop") || lowerOutput.contains("docker daemon") || lowerOutput.contains("docker engine") || lowerOutput.contains("cannot connect") || lowerOutput.contains("failed to connect") || lowerOutput.contains("docker_api") || lowerOutput.contains("docker api") || lowerOutput.contains("docker_engine") || lowerOutput.contains("pipe/docker_engine") || lowerOutput.contains("npipe")) { return "Docker Desktop is not running. Start Docker Desktop and rerun the command."; } if (output == null || output.isBlank()) { From 1e381d59c6e51f53474fe2116d9519bb808cab73 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Fri, 13 Mar 2026 14:18:34 -0600 Subject: [PATCH 75/84] Keep startup errors --- .../com/petshop/backend/DevStackApplication.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/petshop/backend/DevStackApplication.java b/src/main/java/com/petshop/backend/DevStackApplication.java index 4fd47ffc..38846aaa 100644 --- a/src/main/java/com/petshop/backend/DevStackApplication.java +++ b/src/main/java/com/petshop/backend/DevStackApplication.java @@ -25,6 +25,7 @@ public class DevStackApplication { CountDownLatch shutdownLatch = new CountDownLatch(1); ScheduledExecutorService scheduler = null; AtomicBoolean shuttingDown = new AtomicBoolean(false); + RuntimeException startupFailure = null; try { validateJavaVersion(); @@ -42,6 +43,7 @@ public class DevStackApplication { scheduler = startDatabaseWatch(docker, context, shuttingDown); shutdownLatch.await(); } catch (RuntimeException ex) { + startupFailure = ex; throw new IllegalStateException(describeStartupFailure(ex), ex); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); @@ -54,7 +56,15 @@ public class DevStackApplication { context.close(); } if (!shuttingDown.get()) { - docker.stopDatabase(); + try { + docker.stopDatabase(); + } catch (RuntimeException stopFailure) { + if (startupFailure != null) { + System.err.println(stopFailure.getMessage()); + } else { + throw stopFailure; + } + } } } } From bc9bd09d245b4c8d1866691e200dcbbac9b61e90 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Fri, 13 Mar 2026 14:18:34 -0600 Subject: [PATCH 76/84] Keep original startup error message --- .../com/petshop/backend/DevStackApplication.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/petshop/backend/DevStackApplication.java b/src/main/java/com/petshop/backend/DevStackApplication.java index 4fd47ffc..38846aaa 100644 --- a/src/main/java/com/petshop/backend/DevStackApplication.java +++ b/src/main/java/com/petshop/backend/DevStackApplication.java @@ -25,6 +25,7 @@ public class DevStackApplication { CountDownLatch shutdownLatch = new CountDownLatch(1); ScheduledExecutorService scheduler = null; AtomicBoolean shuttingDown = new AtomicBoolean(false); + RuntimeException startupFailure = null; try { validateJavaVersion(); @@ -42,6 +43,7 @@ public class DevStackApplication { scheduler = startDatabaseWatch(docker, context, shuttingDown); shutdownLatch.await(); } catch (RuntimeException ex) { + startupFailure = ex; throw new IllegalStateException(describeStartupFailure(ex), ex); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); @@ -54,7 +56,15 @@ public class DevStackApplication { context.close(); } if (!shuttingDown.get()) { - docker.stopDatabase(); + try { + docker.stopDatabase(); + } catch (RuntimeException stopFailure) { + if (startupFailure != null) { + System.err.println(stopFailure.getMessage()); + } else { + throw stopFailure; + } + } } } } From efc9836c11b38eaa6f36913a26e1a095ac8bcefa Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Sat, 14 Mar 2026 20:12:48 -0600 Subject: [PATCH 77/84] add user phone --- .../backend/config/DataInitializer.java | 15 ++++ .../backend/controller/AuthController.java | 34 +++++++++ .../dto/auth/ProfileUpdateRequest.java | 15 +++- .../backend/dto/auth/RegisterRequest.java | 18 ++++- .../backend/dto/auth/RegisterResponse.java | 16 +++- .../backend/dto/auth/UserInfoResponse.java | 16 +++- .../petshop/backend/dto/user/UserRequest.java | 15 +++- .../backend/dto/user/UserResponse.java | 17 ++++- .../java/com/petshop/backend/entity/User.java | 15 +++- .../backend/repository/UserRepository.java | 6 +- .../service/UserBusinessLinkageService.java | 74 +++++++++++++------ .../petshop/backend/service/UserService.java | 39 ++++++++-- .../resources/db/migration/V6__user_phone.sql | 8 ++ 13 files changed, 245 insertions(+), 43 deletions(-) create mode 100644 src/main/resources/db/migration/V6__user_phone.sql diff --git a/src/main/java/com/petshop/backend/config/DataInitializer.java b/src/main/java/com/petshop/backend/config/DataInitializer.java index cff00f6c..4a8c7470 100644 --- a/src/main/java/com/petshop/backend/config/DataInitializer.java +++ b/src/main/java/com/petshop/backend/config/DataInitializer.java @@ -35,6 +35,7 @@ public class DataInitializer implements CommandLineRunner { admin.setPassword(passwordEncoder.encode("admin123")); admin.setEmail("admin@petshop.com"); admin.setFullName("Admin User"); + admin.setPhone("000-000-1000"); admin.setRole(User.Role.ADMIN); admin.setActive(true); admin = userRepository.save(admin); @@ -51,6 +52,10 @@ public class DataInitializer implements CommandLineRunner { admin.setEmail("admin@petshop.com"); updated = true; } + if (admin.getPhone() == null || admin.getPhone().isEmpty()) { + admin.setPhone("000-000-1000"); + updated = true; + } if (admin.getActive() == null) { admin.setActive(true); updated = true; @@ -75,6 +80,7 @@ public class DataInitializer implements CommandLineRunner { staff.setPassword(passwordEncoder.encode("staff123")); staff.setEmail("staff@petshop.com"); staff.setFullName("Staff User"); + staff.setPhone("000-000-1001"); staff.setRole(User.Role.STAFF); staff.setActive(true); staff = userRepository.save(staff); @@ -91,6 +97,10 @@ public class DataInitializer implements CommandLineRunner { staff.setEmail("staff@petshop.com"); updated = true; } + if (staff.getPhone() == null || staff.getPhone().isEmpty()) { + staff.setPhone("000-000-1001"); + updated = true; + } if (staff.getActive() == null) { staff.setActive(true); updated = true; @@ -115,6 +125,7 @@ public class DataInitializer implements CommandLineRunner { customer.setPassword(passwordEncoder.encode("customer123")); customer.setEmail("customer@petshop.com"); customer.setFullName("Test Customer"); + customer.setPhone("000-000-1002"); customer.setRole(User.Role.CUSTOMER); customer.setActive(true); customer = userRepository.save(customer); @@ -131,6 +142,10 @@ public class DataInitializer implements CommandLineRunner { customer.setEmail("customer@petshop.com"); updated = true; } + if (customer.getPhone() == null || customer.getPhone().isEmpty()) { + customer.setPhone("000-000-1002"); + updated = true; + } if (customer.getActive() == null) { customer.setActive(true); updated = true; diff --git a/src/main/java/com/petshop/backend/controller/AuthController.java b/src/main/java/com/petshop/backend/controller/AuthController.java index 717ca7fa..2bd2b47d 100644 --- a/src/main/java/com/petshop/backend/controller/AuthController.java +++ b/src/main/java/com/petshop/backend/controller/AuthController.java @@ -74,11 +74,19 @@ public class AuthController { return ResponseEntity.status(HttpStatus.CONFLICT).body(error); } + String phone = trimToNull(request.getPhone()); + if (phone != null && userRepository.findByPhone(phone).isPresent()) { + Map error = new HashMap<>(); + error.put("message", "Phone already exists"); + return ResponseEntity.status(HttpStatus.CONFLICT).body(error); + } + User user = new User(); user.setUsername(request.getUsername()); user.setPassword(passwordEncoder.encode(request.getPassword())); user.setEmail(request.getEmail()); user.setFullName(request.getFullName()); + user.setPhone(phone); user.setRole(User.Role.CUSTOMER); user.setActive(true); @@ -93,6 +101,7 @@ public class AuthController { savedUser.getId(), savedUser.getUsername(), savedUser.getEmail(), + savedUser.getPhone(), savedUser.getRole().name(), token )); @@ -145,6 +154,7 @@ public class AuthController { user.getUsername(), user.getEmail(), user.getFullName(), + user.getPhone(), user.getAvatarUrl(), user.getRole().name(), employeeStore != null ? employeeStore.getStore().getStoreId() : null, @@ -180,6 +190,20 @@ public class AuthController { user.setFullName(request.getFullName()); } + if (request.getPhone() != null) { + String phone = trimToNull(request.getPhone()); + if (!java.util.Objects.equals(phone, user.getPhone())) { + if (phone != null && userRepository.findByPhone(phone) + .filter(existing -> !existing.getId().equals(user.getId())) + .isPresent()) { + Map error = new HashMap<>(); + error.put("message", "Phone already exists"); + return ResponseEntity.status(HttpStatus.CONFLICT).body(error); + } + user.setPhone(phone); + } + } + if (request.getPassword() != null && !request.getPassword().isEmpty()) { user.setPassword(passwordEncoder.encode(request.getPassword())); invalidateToken = true; @@ -190,6 +214,7 @@ public class AuthController { } User updatedUser = userRepository.save(user); + userBusinessLinkageService.syncLinkedRecords(updatedUser); EmployeeStore employeeStore = resolveEmployeeStore(updatedUser); @@ -198,6 +223,7 @@ public class AuthController { updatedUser.getUsername(), updatedUser.getEmail(), updatedUser.getFullName(), + updatedUser.getPhone(), updatedUser.getAvatarUrl(), updatedUser.getRole().name(), employeeStore != null ? employeeStore.getStore().getStoreId() : null, @@ -215,6 +241,14 @@ public class AuthController { .orElse(null); } + private String trimToNull(String value) { + if (value == null) { + return null; + } + String trimmed = value.trim(); + return trimmed.isEmpty() ? null : trimmed; + } + @PostMapping("/me/avatar") public ResponseEntity uploadAvatar(@RequestParam("avatar") MultipartFile file) { User user = getAuthenticatedUser(); diff --git a/src/main/java/com/petshop/backend/dto/auth/ProfileUpdateRequest.java b/src/main/java/com/petshop/backend/dto/auth/ProfileUpdateRequest.java index ae7d6270..58959678 100644 --- a/src/main/java/com/petshop/backend/dto/auth/ProfileUpdateRequest.java +++ b/src/main/java/com/petshop/backend/dto/auth/ProfileUpdateRequest.java @@ -14,6 +14,9 @@ public class ProfileUpdateRequest { @Size(max = 100, message = "Full name must not exceed 100 characters") private String fullName; + @Size(max = 20, message = "Phone must not exceed 20 characters") + private String phone; + @Size(min = 6, message = "Password must be at least 6 characters") private String password; @@ -41,6 +44,14 @@ public class ProfileUpdateRequest { this.fullName = fullName; } + public String getPhone() { + return phone; + } + + public void setPhone(String phone) { + this.phone = phone; + } + public String getPassword() { return password; } @@ -57,12 +68,13 @@ public class ProfileUpdateRequest { return Objects.equals(username, that.username) && Objects.equals(email, that.email) && Objects.equals(fullName, that.fullName) && + Objects.equals(phone, that.phone) && Objects.equals(password, that.password); } @Override public int hashCode() { - return Objects.hash(username, email, fullName, password); + return Objects.hash(username, email, fullName, phone, password); } @Override @@ -71,6 +83,7 @@ public class ProfileUpdateRequest { "username='" + username + '\'' + ", email='" + email + '\'' + ", fullName='" + fullName + '\'' + + ", phone='" + phone + '\'' + ", password='" + password + '\'' + '}'; } diff --git a/src/main/java/com/petshop/backend/dto/auth/RegisterRequest.java b/src/main/java/com/petshop/backend/dto/auth/RegisterRequest.java index 07775bad..2791746c 100644 --- a/src/main/java/com/petshop/backend/dto/auth/RegisterRequest.java +++ b/src/main/java/com/petshop/backend/dto/auth/RegisterRequest.java @@ -22,6 +22,10 @@ public class RegisterRequest { @Size(max = 100, message = "Full name must not exceed 100 characters") private String fullName; + @NotBlank(message = "Phone is required") + @Size(max = 20, message = "Phone must not exceed 20 characters") + private String phone; + public String getUsername() { return username; } @@ -54,6 +58,14 @@ public class RegisterRequest { this.fullName = fullName; } + public String getPhone() { + return phone; + } + + public void setPhone(String phone) { + this.phone = phone; + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -62,12 +74,13 @@ public class RegisterRequest { return Objects.equals(username, that.username) && Objects.equals(password, that.password) && Objects.equals(email, that.email) && - Objects.equals(fullName, that.fullName); + Objects.equals(fullName, that.fullName) && + Objects.equals(phone, that.phone); } @Override public int hashCode() { - return Objects.hash(username, password, email, fullName); + return Objects.hash(username, password, email, fullName, phone); } @Override @@ -77,6 +90,7 @@ public class RegisterRequest { ", password='" + password + '\'' + ", email='" + email + '\'' + ", fullName='" + fullName + '\'' + + ", phone='" + phone + '\'' + '}'; } } diff --git a/src/main/java/com/petshop/backend/dto/auth/RegisterResponse.java b/src/main/java/com/petshop/backend/dto/auth/RegisterResponse.java index b31370cb..7e016985 100644 --- a/src/main/java/com/petshop/backend/dto/auth/RegisterResponse.java +++ b/src/main/java/com/petshop/backend/dto/auth/RegisterResponse.java @@ -6,16 +6,18 @@ public class RegisterResponse { private Long id; private String username; private String email; + private String phone; private String role; private String token; public RegisterResponse() { } - public RegisterResponse(Long id, String username, String email, String role, String token) { + public RegisterResponse(Long id, String username, String email, String phone, String role, String token) { this.id = id; this.username = username; this.email = email; + this.phone = phone; this.role = role; this.token = token; } @@ -44,6 +46,14 @@ public class RegisterResponse { this.email = email; } + public String getPhone() { + return phone; + } + + public void setPhone(String phone) { + this.phone = phone; + } + public String getRole() { return role; } @@ -68,13 +78,14 @@ public class RegisterResponse { return Objects.equals(id, that.id) && Objects.equals(username, that.username) && Objects.equals(email, that.email) && + Objects.equals(phone, that.phone) && Objects.equals(role, that.role) && Objects.equals(token, that.token); } @Override public int hashCode() { - return Objects.hash(id, username, email, role, token); + return Objects.hash(id, username, email, phone, role, token); } @Override @@ -83,6 +94,7 @@ public class RegisterResponse { "id=" + id + ", username='" + username + '\'' + ", email='" + email + '\'' + + ", phone='" + phone + '\'' + ", role='" + role + '\'' + ", token='" + token + '\'' + '}'; diff --git a/src/main/java/com/petshop/backend/dto/auth/UserInfoResponse.java b/src/main/java/com/petshop/backend/dto/auth/UserInfoResponse.java index 1f15daf8..ba714a49 100644 --- a/src/main/java/com/petshop/backend/dto/auth/UserInfoResponse.java +++ b/src/main/java/com/petshop/backend/dto/auth/UserInfoResponse.java @@ -7,6 +7,7 @@ public class UserInfoResponse { private String username; private String email; private String fullName; + private String phone; private String avatarUrl; private String role; private Long storeId; @@ -15,11 +16,12 @@ public class UserInfoResponse { public UserInfoResponse() { } - public UserInfoResponse(Long id, String username, String email, String fullName, String avatarUrl, String role, Long storeId, String storeName) { + public UserInfoResponse(Long id, String username, String email, String fullName, String phone, String avatarUrl, String role, Long storeId, String storeName) { this.id = id; this.username = username; this.email = email; this.fullName = fullName; + this.phone = phone; this.avatarUrl = avatarUrl; this.role = role; this.storeId = storeId; @@ -58,6 +60,14 @@ public class UserInfoResponse { this.fullName = fullName; } + public String getPhone() { + return phone; + } + + public void setPhone(String phone) { + this.phone = phone; + } + public String getAvatarUrl() { return avatarUrl; } @@ -99,6 +109,7 @@ public class UserInfoResponse { Objects.equals(username, that.username) && Objects.equals(email, that.email) && Objects.equals(fullName, that.fullName) && + Objects.equals(phone, that.phone) && Objects.equals(avatarUrl, that.avatarUrl) && Objects.equals(role, that.role) && Objects.equals(storeId, that.storeId) && @@ -107,7 +118,7 @@ public class UserInfoResponse { @Override public int hashCode() { - return Objects.hash(id, username, email, fullName, avatarUrl, role, storeId, storeName); + return Objects.hash(id, username, email, fullName, phone, avatarUrl, role, storeId, storeName); } @Override @@ -117,6 +128,7 @@ public class UserInfoResponse { ", username='" + username + '\'' + ", email='" + email + '\'' + ", fullName='" + fullName + '\'' + + ", phone='" + phone + '\'' + ", avatarUrl='" + avatarUrl + '\'' + ", role='" + role + '\'' + ", storeId=" + storeId + diff --git a/src/main/java/com/petshop/backend/dto/user/UserRequest.java b/src/main/java/com/petshop/backend/dto/user/UserRequest.java index 72b1dbfb..09a9036d 100644 --- a/src/main/java/com/petshop/backend/dto/user/UserRequest.java +++ b/src/main/java/com/petshop/backend/dto/user/UserRequest.java @@ -21,6 +21,9 @@ public class UserRequest { @Email(message = "Invalid email format") private String email; + @Size(max = 20, message = "Phone must not exceed 20 characters") + private String phone; + @NotNull(message = "Role is required") private User.Role role; @@ -58,6 +61,14 @@ public class UserRequest { this.email = email; } + public String getPhone() { + return phone; + } + + public void setPhone(String phone) { + this.phone = phone; + } + public User.Role getRole() { return role; } @@ -83,13 +94,14 @@ public class UserRequest { Objects.equals(password, that.password) && Objects.equals(fullName, that.fullName) && Objects.equals(email, that.email) && + Objects.equals(phone, that.phone) && role == that.role && Objects.equals(active, that.active); } @Override public int hashCode() { - return Objects.hash(username, password, fullName, email, role, active); + return Objects.hash(username, password, fullName, email, phone, role, active); } @Override @@ -99,6 +111,7 @@ public class UserRequest { ", password='" + password + '\'' + ", fullName='" + fullName + '\'' + ", email='" + email + '\'' + + ", phone='" + phone + '\'' + ", role=" + role + ", active=" + active + '}'; diff --git a/src/main/java/com/petshop/backend/dto/user/UserResponse.java b/src/main/java/com/petshop/backend/dto/user/UserResponse.java index 8b383366..9d7167c2 100644 --- a/src/main/java/com/petshop/backend/dto/user/UserResponse.java +++ b/src/main/java/com/petshop/backend/dto/user/UserResponse.java @@ -8,6 +8,7 @@ public class UserResponse { private String username; private String fullName; private String email; + private String phone; private String role; private Boolean active; private LocalDateTime createdAt; @@ -16,11 +17,12 @@ public class UserResponse { public UserResponse() { } - public UserResponse(Long id, String username, String fullName, String email, String role, Boolean active, LocalDateTime createdAt, LocalDateTime updatedAt) { + public UserResponse(Long id, String username, String fullName, String email, String phone, String role, Boolean active, LocalDateTime createdAt, LocalDateTime updatedAt) { this.id = id; this.username = username; this.fullName = fullName; this.email = email; + this.phone = phone; this.role = role; this.active = active; this.createdAt = createdAt; @@ -59,6 +61,14 @@ public class UserResponse { this.email = email; } + public String getPhone() { + return phone; + } + + public void setPhone(String phone) { + this.phone = phone; + } + public String getRole() { return role; } @@ -96,12 +106,12 @@ public class UserResponse { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; UserResponse that = (UserResponse) o; - return Objects.equals(id, that.id) && Objects.equals(username, that.username) && Objects.equals(fullName, that.fullName) && Objects.equals(email, that.email) && Objects.equals(role, that.role) && Objects.equals(active, that.active) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt); + return Objects.equals(id, that.id) && Objects.equals(username, that.username) && Objects.equals(fullName, that.fullName) && Objects.equals(email, that.email) && Objects.equals(phone, that.phone) && Objects.equals(role, that.role) && Objects.equals(active, that.active) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt); } @Override public int hashCode() { - return Objects.hash(id, username, fullName, email, role, active, createdAt, updatedAt); + return Objects.hash(id, username, fullName, email, phone, role, active, createdAt, updatedAt); } @Override @@ -111,6 +121,7 @@ public class UserResponse { ", username='" + username + '\'' + ", fullName='" + fullName + '\'' + ", email='" + email + '\'' + + ", phone='" + phone + '\'' + ", role='" + role + '\'' + ", active=" + active + ", createdAt=" + createdAt + diff --git a/src/main/java/com/petshop/backend/entity/User.java b/src/main/java/com/petshop/backend/entity/User.java index 54bd202e..cdec2754 100644 --- a/src/main/java/com/petshop/backend/entity/User.java +++ b/src/main/java/com/petshop/backend/entity/User.java @@ -27,6 +27,9 @@ public class User { @Column(length = 100) private String fullName; + @Column(length = 20) + private String phone; + @Column(length = 255) private String avatarUrl; @@ -55,12 +58,13 @@ public class User { public User() { } - public User(Long id, String username, String password, String email, String fullName, String avatarUrl, Role role, Boolean active, Integer tokenVersion, LocalDateTime createdAt, LocalDateTime updatedAt) { + public User(Long id, String username, String password, String email, String fullName, String phone, String avatarUrl, Role role, Boolean active, Integer tokenVersion, LocalDateTime createdAt, LocalDateTime updatedAt) { this.id = id; this.username = username; this.password = password; this.email = email; this.fullName = fullName; + this.phone = phone; this.avatarUrl = avatarUrl; this.role = role; this.active = active; @@ -109,6 +113,14 @@ public class User { this.fullName = fullName; } + public String getPhone() { + return phone; + } + + public void setPhone(String phone) { + this.phone = phone; + } + public String getAvatarUrl() { return avatarUrl; } @@ -178,6 +190,7 @@ public class User { ", password='" + password + '\'' + ", email='" + email + '\'' + ", fullName='" + fullName + '\'' + + ", phone='" + phone + '\'' + ", avatarUrl='" + avatarUrl + '\'' + ", role=" + role + ", active=" + active + diff --git a/src/main/java/com/petshop/backend/repository/UserRepository.java b/src/main/java/com/petshop/backend/repository/UserRepository.java index 17e4356a..775c1b18 100644 --- a/src/main/java/com/petshop/backend/repository/UserRepository.java +++ b/src/main/java/com/petshop/backend/repository/UserRepository.java @@ -14,9 +14,13 @@ import java.util.Optional; public interface UserRepository extends JpaRepository { Optional findByUsername(String username); Optional findByEmail(String email); + Optional findByPhone(String phone); boolean existsByUsername(String username); @Query("SELECT u FROM User u WHERE " + - "LOWER(u.username) LIKE LOWER(CONCAT('%', :q, '%'))") + "LOWER(u.username) LIKE LOWER(CONCAT('%', :q, '%')) OR " + + "LOWER(COALESCE(u.fullName, '')) LIKE LOWER(CONCAT('%', :q, '%')) OR " + + "LOWER(COALESCE(u.email, '')) LIKE LOWER(CONCAT('%', :q, '%')) OR " + + "LOWER(COALESCE(u.phone, '')) LIKE LOWER(CONCAT('%', :q, '%'))") Page searchUsers(@Param("q") String query, Pageable pageable); } diff --git a/src/main/java/com/petshop/backend/service/UserBusinessLinkageService.java b/src/main/java/com/petshop/backend/service/UserBusinessLinkageService.java index 81b4738f..d0a18d3e 100644 --- a/src/main/java/com/petshop/backend/service/UserBusinessLinkageService.java +++ b/src/main/java/com/petshop/backend/service/UserBusinessLinkageService.java @@ -25,88 +25,116 @@ public class UserBusinessLinkageService { @Transactional public Employee ensureLinkedEmployee(User user) { - // Check if already linked if (user.getId() != null) { var existing = employeeRepository.findByUserId(user.getId()); if (existing.isPresent()) { - return existing.get(); + return syncEmployee(existing.get(), user); } } - // Check for email matches List emailMatches = employeeRepository.findAllByEmail(user.getEmail()); - // If exactly one match exists and has no userId, link it if (emailMatches.size() == 1) { Employee employee = emailMatches.get(0); if (employee.getUserId() == null) { employee.setUserId(user.getId()); - return employeeRepository.save(employee); + return syncEmployee(employee, user); } } - // Otherwise create a new linked Employee Employee newEmployee = new Employee(); newEmployee.setUserId(user.getId()); newEmployee.setEmail(user.getEmail()); - // Split fullName into firstName and lastName String[] nameParts = splitFullName(user.getFullName()); newEmployee.setFirstName(nameParts[0]); newEmployee.setLastName(nameParts[1]); - // Set required fields with deterministic values - newEmployee.setPhone("000-000-0000"); + newEmployee.setPhone(normalizePhone(user.getPhone(), "000-000-0000")); newEmployee.setIsActive(true); - // Map role based on user role if (user.getRole() == User.Role.ADMIN) { newEmployee.setRole("Manager"); } else if (user.getRole() == User.Role.STAFF) { newEmployee.setRole("Staff"); } else { - newEmployee.setRole("Staff"); // fallback + newEmployee.setRole("Staff"); } - return employeeRepository.save(newEmployee); + return syncEmployee(newEmployee, user); } @Transactional public Customer ensureLinkedCustomer(User user) { - // Check if already linked if (user.getId() != null) { var existing = customerRepository.findByUserId(user.getId()); if (existing.isPresent()) { - return existing.get(); + return syncCustomer(existing.get(), user); } } - // Check for email matches List emailMatches = customerRepository.findAllByEmail(user.getEmail()); - // If exactly one match exists and has no userId, link it if (emailMatches.size() == 1) { Customer customer = emailMatches.get(0); if (customer.getUserId() == null) { customer.setUserId(user.getId()); - return customerRepository.save(customer); + return syncCustomer(customer, user); } } - // Otherwise create a new linked Customer Customer newCustomer = new Customer(); newCustomer.setUserId(user.getId()); newCustomer.setEmail(user.getEmail()); - // Split fullName into firstName and lastName String[] nameParts = splitFullName(user.getFullName()); newCustomer.setFirstName(nameParts[0]); newCustomer.setLastName(nameParts[1]); - // Set required fields with deterministic values - newCustomer.setPhone("000-000-0001"); + newCustomer.setPhone(normalizePhone(user.getPhone(), "000-000-0001")); - return customerRepository.save(newCustomer); + return syncCustomer(newCustomer, user); + } + + @Transactional + public void syncLinkedRecords(User user) { + if (user.getRole() == User.Role.CUSTOMER) { + ensureLinkedCustomer(user); + return; + } + ensureLinkedEmployee(user); + } + + private Employee syncEmployee(Employee employee, User user) { + employee.setUserId(user.getId()); + employee.setEmail(user.getEmail()); + String[] nameParts = splitFullName(user.getFullName()); + employee.setFirstName(nameParts[0]); + employee.setLastName(nameParts[1]); + employee.setPhone(normalizePhone(user.getPhone(), employee.getPhone())); + if (user.getRole() == User.Role.ADMIN) { + employee.setRole("Manager"); + } else { + employee.setRole("Staff"); + } + return employeeRepository.save(employee); + } + + private Customer syncCustomer(Customer customer, User user) { + customer.setUserId(user.getId()); + customer.setEmail(user.getEmail()); + String[] nameParts = splitFullName(user.getFullName()); + customer.setFirstName(nameParts[0]); + customer.setLastName(nameParts[1]); + customer.setPhone(normalizePhone(user.getPhone(), customer.getPhone())); + return customerRepository.save(customer); + } + + private String normalizePhone(String phone, String fallback) { + if (phone == null || phone.trim().isEmpty()) { + return fallback; + } + return phone.trim(); } private String[] splitFullName(String fullName) { @@ -118,11 +146,9 @@ public class UserBusinessLinkageService { int spaceIndex = trimmed.indexOf(' '); if (spaceIndex == -1) { - // Single token return new String[]{trimmed, "User"}; } - // Multiple tokens String firstName = trimmed.substring(0, spaceIndex).trim(); String lastName = trimmed.substring(spaceIndex + 1).trim(); diff --git a/src/main/java/com/petshop/backend/service/UserService.java b/src/main/java/com/petshop/backend/service/UserService.java index ee705a8a..a54aecd3 100644 --- a/src/main/java/com/petshop/backend/service/UserService.java +++ b/src/main/java/com/petshop/backend/service/UserService.java @@ -11,6 +11,9 @@ import org.springframework.data.domain.Pageable; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.server.ResponseStatusException; + +import static org.springframework.http.HttpStatus.CONFLICT; @Service public class UserService { @@ -48,17 +51,15 @@ public class UserService { user.setPassword(passwordEncoder.encode(request.getPassword())); user.setFullName(request.getFullName()); user.setEmail(request.getEmail()); + user.setPhone(trimToNull(request.getPhone())); user.setRole(request.getRole()); user.setActive(request.getActive() != null ? request.getActive() : true); + validateUniquePhone(user.getPhone(), null); + user = userRepository.save(user); - // Create or link business entity based on role - if (user.getRole() == User.Role.STAFF || user.getRole() == User.Role.ADMIN) { - userBusinessLinkageService.ensureLinkedEmployee(user); - } else if (user.getRole() == User.Role.CUSTOMER) { - userBusinessLinkageService.ensureLinkedCustomer(user); - } + userBusinessLinkageService.syncLinkedRecords(user); return mapToResponse(user); } @@ -80,6 +81,11 @@ public class UserService { } user.setFullName(request.getFullName()); user.setEmail(request.getEmail()); + String phone = trimToNull(request.getPhone()); + if (!java.util.Objects.equals(user.getPhone(), phone)) { + validateUniquePhone(phone, user.getId()); + } + user.setPhone(phone); user.setRole(request.getRole()); user.setActive(request.getActive() != null ? request.getActive() : true); if (invalidateToken) { @@ -87,6 +93,7 @@ public class UserService { } user = userRepository.save(user); + userBusinessLinkageService.syncLinkedRecords(user); return mapToResponse(user); } @@ -109,10 +116,30 @@ public class UserService { response.setUsername(user.getUsername()); response.setFullName(user.getFullName()); response.setEmail(user.getEmail()); + response.setPhone(user.getPhone()); response.setRole(user.getRole().toString()); response.setActive(user.getActive()); response.setCreatedAt(user.getCreatedAt()); response.setUpdatedAt(user.getUpdatedAt()); return response; } + + private void validateUniquePhone(String phone, Long currentUserId) { + if (phone == null || phone.isBlank()) { + return; + } + userRepository.findByPhone(phone) + .filter(existing -> !existing.getId().equals(currentUserId)) + .ifPresent(existing -> { + throw new ResponseStatusException(CONFLICT, "Phone already exists"); + }); + } + + private String trimToNull(String value) { + if (value == null) { + return null; + } + String trimmed = value.trim(); + return trimmed.isEmpty() ? null : trimmed; + } } diff --git a/src/main/resources/db/migration/V6__user_phone.sql b/src/main/resources/db/migration/V6__user_phone.sql new file mode 100644 index 00000000..95478fdc --- /dev/null +++ b/src/main/resources/db/migration/V6__user_phone.sql @@ -0,0 +1,8 @@ +ALTER TABLE users + ADD COLUMN phone VARCHAR(20) NULL AFTER fullName; + +UPDATE users u +LEFT JOIN customer c ON c.user_id = u.id +LEFT JOIN employee e ON e.user_id = u.id +SET u.phone = COALESCE(NULLIF(c.phone, ''), NULLIF(e.phone, ''), u.phone) +WHERE u.phone IS NULL OR u.phone = ''; From 4fd2041fbbb0268a9a6758eb14c1ad1ba131e73c Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Sat, 14 Mar 2026 20:13:48 -0600 Subject: [PATCH 78/84] sync customer phone --- .../backend/service/CustomerService.java | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/petshop/backend/service/CustomerService.java b/src/main/java/com/petshop/backend/service/CustomerService.java index 47fa3c4c..888c7c82 100644 --- a/src/main/java/com/petshop/backend/service/CustomerService.java +++ b/src/main/java/com/petshop/backend/service/CustomerService.java @@ -6,6 +6,7 @@ import com.petshop.backend.dto.customer.CustomerResponse; import com.petshop.backend.entity.Customer; import com.petshop.backend.exception.ResourceNotFoundException; import com.petshop.backend.repository.CustomerRepository; +import com.petshop.backend.repository.UserRepository; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; @@ -15,9 +16,11 @@ import org.springframework.transaction.annotation.Transactional; public class CustomerService { private final CustomerRepository customerRepository; + private final UserRepository userRepository; - public CustomerService(CustomerRepository customerRepository) { + public CustomerService(CustomerRepository customerRepository, UserRepository userRepository) { this.customerRepository = customerRepository; + this.userRepository = userRepository; } public Page getAllCustomers(String query, Pageable pageable) { @@ -45,6 +48,7 @@ public class CustomerService { customer.setPhone(request.getPhone()); customer = customerRepository.save(customer); + syncLinkedUser(customer); return mapToResponse(customer); } @@ -59,6 +63,7 @@ public class CustomerService { customer.setPhone(request.getPhone()); customer = customerRepository.save(customer); + syncLinkedUser(customer); return mapToResponse(customer); } @@ -86,4 +91,16 @@ public class CustomerService { customer.getUpdatedAt() ); } + + private void syncLinkedUser(Customer customer) { + if (customer.getUserId() == null) { + return; + } + userRepository.findById(customer.getUserId()).ifPresent(user -> { + user.setEmail(customer.getEmail()); + user.setPhone(customer.getPhone()); + user.setFullName((customer.getFirstName() + " " + customer.getLastName()).trim()); + userRepository.save(user); + }); + } } From 4b4f4b087e5bc64aa6bd7292de605cda5b3b2f1f Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Sat, 14 Mar 2026 20:22:14 -0600 Subject: [PATCH 79/84] cut over employee phones --- .../controller/EmployeeController.java | 49 +++++ .../backend/dto/customer/CustomerRequest.java | 16 +- .../dto/customer/CustomerResponse.java | 17 +- .../backend/dto/employee/EmployeeRequest.java | 50 +++++ .../dto/employee/EmployeeResponse.java | 43 +++++ .../com/petshop/backend/entity/Customer.java | 15 +- .../com/petshop/backend/entity/Employee.java | 15 +- .../repository/CustomerRepository.java | 2 +- .../repository/EmployeeRepository.java | 14 ++ .../backend/service/CustomerService.java | 4 - .../backend/service/EmployeeService.java | 175 ++++++++++++++++++ .../service/UserBusinessLinkageService.java | 12 -- .../V7__employee_customer_phone_cutover.sql | 11 ++ 13 files changed, 350 insertions(+), 73 deletions(-) create mode 100644 src/main/java/com/petshop/backend/controller/EmployeeController.java create mode 100644 src/main/java/com/petshop/backend/dto/employee/EmployeeRequest.java create mode 100644 src/main/java/com/petshop/backend/dto/employee/EmployeeResponse.java create mode 100644 src/main/java/com/petshop/backend/service/EmployeeService.java create mode 100644 src/main/resources/db/migration/V7__employee_customer_phone_cutover.sql diff --git a/src/main/java/com/petshop/backend/controller/EmployeeController.java b/src/main/java/com/petshop/backend/controller/EmployeeController.java new file mode 100644 index 00000000..1c567623 --- /dev/null +++ b/src/main/java/com/petshop/backend/controller/EmployeeController.java @@ -0,0 +1,49 @@ +package com.petshop.backend.controller; + +import com.petshop.backend.dto.employee.EmployeeRequest; +import com.petshop.backend.dto.employee.EmployeeResponse; +import com.petshop.backend.service.EmployeeService; +import jakarta.validation.Valid; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/v1/employees") +@PreAuthorize("hasRole('ADMIN')") +public class EmployeeController { + private final EmployeeService employeeService; + + public EmployeeController(EmployeeService employeeService) { + this.employeeService = employeeService; + } + + @GetMapping + public ResponseEntity> getAllEmployees(@RequestParam(required = false) String q, Pageable pageable) { + return ResponseEntity.ok(employeeService.getAllEmployees(q, pageable)); + } + + @GetMapping("/{id}") + public ResponseEntity getEmployeeById(@PathVariable Long id) { + return ResponseEntity.ok(employeeService.getEmployeeById(id)); + } + + @PostMapping + public ResponseEntity createEmployee(@Valid @RequestBody EmployeeRequest request) { + return ResponseEntity.status(HttpStatus.CREATED).body(employeeService.createEmployee(request)); + } + + @PutMapping("/{id}") + public ResponseEntity updateEmployee(@PathVariable Long id, @Valid @RequestBody EmployeeRequest request) { + return ResponseEntity.ok(employeeService.updateEmployee(id, request)); + } + + @DeleteMapping("/{id}") + public ResponseEntity deleteEmployee(@PathVariable Long id) { + employeeService.deleteEmployee(id); + return ResponseEntity.noContent().build(); + } +} diff --git a/src/main/java/com/petshop/backend/dto/customer/CustomerRequest.java b/src/main/java/com/petshop/backend/dto/customer/CustomerRequest.java index d982be81..ded898e3 100644 --- a/src/main/java/com/petshop/backend/dto/customer/CustomerRequest.java +++ b/src/main/java/com/petshop/backend/dto/customer/CustomerRequest.java @@ -14,8 +14,6 @@ public class CustomerRequest { @Email(message = "Invalid email format") private String email; - private String phone; - public String getFirstName() { return firstName; } @@ -40,14 +38,6 @@ public class CustomerRequest { this.email = email; } - public String getPhone() { - return phone; - } - - public void setPhone(String phone) { - this.phone = phone; - } - @Override public boolean equals(Object o) { if (this == o) return true; @@ -55,13 +45,12 @@ public class CustomerRequest { CustomerRequest that = (CustomerRequest) o; return Objects.equals(firstName, that.firstName) && Objects.equals(lastName, that.lastName) && - Objects.equals(email, that.email) && - Objects.equals(phone, that.phone); + Objects.equals(email, that.email); } @Override public int hashCode() { - return Objects.hash(firstName, lastName, email, phone); + return Objects.hash(firstName, lastName, email); } @Override @@ -70,7 +59,6 @@ public class CustomerRequest { "firstName='" + firstName + '\'' + ", lastName='" + lastName + '\'' + ", email='" + email + '\'' + - ", phone='" + phone + '\'' + '}'; } } diff --git a/src/main/java/com/petshop/backend/dto/customer/CustomerResponse.java b/src/main/java/com/petshop/backend/dto/customer/CustomerResponse.java index 7b25f17a..bd05bf76 100644 --- a/src/main/java/com/petshop/backend/dto/customer/CustomerResponse.java +++ b/src/main/java/com/petshop/backend/dto/customer/CustomerResponse.java @@ -8,19 +8,17 @@ public class CustomerResponse { private String firstName; private String lastName; private String email; - private String phone; private LocalDateTime createdAt; private LocalDateTime updatedAt; public CustomerResponse() { } - public CustomerResponse(Long customerId, String firstName, String lastName, String email, String phone, LocalDateTime createdAt, LocalDateTime updatedAt) { + public CustomerResponse(Long customerId, String firstName, String lastName, String email, LocalDateTime createdAt, LocalDateTime updatedAt) { this.customerId = customerId; this.firstName = firstName; this.lastName = lastName; this.email = email; - this.phone = phone; this.createdAt = createdAt; this.updatedAt = updatedAt; } @@ -57,14 +55,6 @@ public class CustomerResponse { this.email = email; } - public String getPhone() { - return phone; - } - - public void setPhone(String phone) { - this.phone = phone; - } - public LocalDateTime getCreatedAt() { return createdAt; } @@ -86,12 +76,12 @@ public class CustomerResponse { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; CustomerResponse that = (CustomerResponse) o; - return Objects.equals(customerId, that.customerId) && Objects.equals(firstName, that.firstName) && Objects.equals(lastName, that.lastName) && Objects.equals(email, that.email) && Objects.equals(phone, that.phone) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt); + return Objects.equals(customerId, that.customerId) && Objects.equals(firstName, that.firstName) && Objects.equals(lastName, that.lastName) && Objects.equals(email, that.email) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt); } @Override public int hashCode() { - return Objects.hash(customerId, firstName, lastName, email, phone, createdAt, updatedAt); + return Objects.hash(customerId, firstName, lastName, email, createdAt, updatedAt); } @Override @@ -101,7 +91,6 @@ public class CustomerResponse { ", firstName='" + firstName + '\'' + ", lastName='" + lastName + '\'' + ", email='" + email + '\'' + - ", phone='" + phone + '\'' + ", createdAt=" + createdAt + ", updatedAt=" + updatedAt + '}'; diff --git a/src/main/java/com/petshop/backend/dto/employee/EmployeeRequest.java b/src/main/java/com/petshop/backend/dto/employee/EmployeeRequest.java new file mode 100644 index 00000000..0dfa25a8 --- /dev/null +++ b/src/main/java/com/petshop/backend/dto/employee/EmployeeRequest.java @@ -0,0 +1,50 @@ +package com.petshop.backend.dto.employee; + +import com.petshop.backend.entity.User; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; + +public class EmployeeRequest { + @NotBlank(message = "Username is required") + @Size(min = 3, max = 50, message = "Username must be between 3 and 50 characters") + private String username; + + @Size(min = 6, message = "Password must be at least 6 characters") + private String password; + + @NotBlank(message = "First name is required") + private String firstName; + + @NotBlank(message = "Last name is required") + private String lastName; + + @Email(message = "Invalid email format") + private String email; + + @Size(max = 20, message = "Phone must not exceed 20 characters") + private String phone; + + @NotNull(message = "Role is required") + private User.Role role; + + private Boolean active = true; + + public String getUsername() { return username; } + public void setUsername(String username) { this.username = username; } + public String getPassword() { return password; } + public void setPassword(String password) { this.password = password; } + public String getFirstName() { return firstName; } + public void setFirstName(String firstName) { this.firstName = firstName; } + public String getLastName() { return lastName; } + public void setLastName(String lastName) { this.lastName = lastName; } + public String getEmail() { return email; } + public void setEmail(String email) { this.email = email; } + public String getPhone() { return phone; } + public void setPhone(String phone) { this.phone = phone; } + public User.Role getRole() { return role; } + public void setRole(User.Role role) { this.role = role; } + public Boolean getActive() { return active; } + public void setActive(Boolean active) { this.active = active; } +} diff --git a/src/main/java/com/petshop/backend/dto/employee/EmployeeResponse.java b/src/main/java/com/petshop/backend/dto/employee/EmployeeResponse.java new file mode 100644 index 00000000..a159fc35 --- /dev/null +++ b/src/main/java/com/petshop/backend/dto/employee/EmployeeResponse.java @@ -0,0 +1,43 @@ +package com.petshop.backend.dto.employee; + +import java.time.LocalDateTime; + +public class EmployeeResponse { + private Long employeeId; + private Long userId; + private String username; + private String firstName; + private String lastName; + private String fullName; + private String email; + private String phone; + private String role; + private Boolean active; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; + + public Long getEmployeeId() { return employeeId; } + public void setEmployeeId(Long employeeId) { this.employeeId = employeeId; } + public Long getUserId() { return userId; } + public void setUserId(Long userId) { this.userId = userId; } + public String getUsername() { return username; } + public void setUsername(String username) { this.username = username; } + public String getFirstName() { return firstName; } + public void setFirstName(String firstName) { this.firstName = firstName; } + public String getLastName() { return lastName; } + public void setLastName(String lastName) { this.lastName = lastName; } + public String getFullName() { return fullName; } + public void setFullName(String fullName) { this.fullName = fullName; } + public String getEmail() { return email; } + public void setEmail(String email) { this.email = email; } + public String getPhone() { return phone; } + public void setPhone(String phone) { this.phone = phone; } + public String getRole() { return role; } + public void setRole(String role) { this.role = role; } + public Boolean getActive() { return active; } + public void setActive(Boolean active) { this.active = active; } + public LocalDateTime getCreatedAt() { return createdAt; } + public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; } + public LocalDateTime getUpdatedAt() { return updatedAt; } + public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; } +} diff --git a/src/main/java/com/petshop/backend/entity/Customer.java b/src/main/java/com/petshop/backend/entity/Customer.java index 1cfa858b..09035619 100644 --- a/src/main/java/com/petshop/backend/entity/Customer.java +++ b/src/main/java/com/petshop/backend/entity/Customer.java @@ -27,9 +27,6 @@ public class Customer { @Column(nullable = false, length = 100) private String email; - @Column(nullable = false, length = 20) - private String phone; - @CreationTimestamp @Column(name = "created_at", updatable = false) private LocalDateTime createdAt; @@ -41,13 +38,12 @@ public class Customer { public Customer() { } - public Customer(Long customerId, Long userId, String firstName, String lastName, String email, String phone, LocalDateTime createdAt, LocalDateTime updatedAt) { + public Customer(Long customerId, Long userId, String firstName, String lastName, String email, LocalDateTime createdAt, LocalDateTime updatedAt) { this.customerId = customerId; this.userId = userId; this.firstName = firstName; this.lastName = lastName; this.email = email; - this.phone = phone; this.createdAt = createdAt; this.updatedAt = updatedAt; } @@ -92,14 +88,6 @@ public class Customer { this.email = email; } - public String getPhone() { - return phone; - } - - public void setPhone(String phone) { - this.phone = phone; - } - public LocalDateTime getCreatedAt() { return createdAt; } @@ -137,7 +125,6 @@ public class Customer { ", firstName='" + firstName + '\'' + ", lastName='" + lastName + '\'' + ", email='" + email + '\'' + - ", phone='" + phone + '\'' + ", createdAt=" + createdAt + ", updatedAt=" + updatedAt + '}'; diff --git a/src/main/java/com/petshop/backend/entity/Employee.java b/src/main/java/com/petshop/backend/entity/Employee.java index 9e825a61..c88216f6 100644 --- a/src/main/java/com/petshop/backend/entity/Employee.java +++ b/src/main/java/com/petshop/backend/entity/Employee.java @@ -27,9 +27,6 @@ public class Employee { @Column(nullable = false, length = 100) private String email; - @Column(nullable = false, length = 20) - private String phone; - @Column(nullable = false, length = 50) private String role; @@ -47,13 +44,12 @@ public class Employee { public Employee() { } - public Employee(Long employeeId, Long userId, String firstName, String lastName, String email, String phone, String role, Boolean isActive, LocalDateTime createdAt, LocalDateTime updatedAt) { + public Employee(Long employeeId, Long userId, String firstName, String lastName, String email, String role, Boolean isActive, LocalDateTime createdAt, LocalDateTime updatedAt) { this.employeeId = employeeId; this.userId = userId; this.firstName = firstName; this.lastName = lastName; this.email = email; - this.phone = phone; this.role = role; this.isActive = isActive; this.createdAt = createdAt; @@ -100,14 +96,6 @@ public class Employee { this.email = email; } - public String getPhone() { - return phone; - } - - public void setPhone(String phone) { - this.phone = phone; - } - public String getRole() { return role; } @@ -161,7 +149,6 @@ public class Employee { ", firstName='" + firstName + '\'' + ", lastName='" + lastName + '\'' + ", email='" + email + '\'' + - ", phone='" + phone + '\'' + ", role='" + role + '\'' + ", isActive=" + isActive + ", createdAt=" + createdAt + diff --git a/src/main/java/com/petshop/backend/repository/CustomerRepository.java b/src/main/java/com/petshop/backend/repository/CustomerRepository.java index f4baa3f9..56e03dbc 100644 --- a/src/main/java/com/petshop/backend/repository/CustomerRepository.java +++ b/src/main/java/com/petshop/backend/repository/CustomerRepository.java @@ -21,6 +21,6 @@ public interface CustomerRepository extends JpaRepository { "LOWER(c.firstName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + "LOWER(c.lastName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + "LOWER(c.email) LIKE LOWER(CONCAT('%', :q, '%')) OR " + - "LOWER(c.phone) LIKE LOWER(CONCAT('%', :q, '%'))") + "EXISTS (SELECT u FROM User u WHERE u.id = c.userId AND LOWER(COALESCE(u.phone, '')) LIKE LOWER(CONCAT('%', :q, '%')))") Page searchCustomers(@Param("q") String query, Pageable pageable); } diff --git a/src/main/java/com/petshop/backend/repository/EmployeeRepository.java b/src/main/java/com/petshop/backend/repository/EmployeeRepository.java index bcb4b138..cfbf715f 100644 --- a/src/main/java/com/petshop/backend/repository/EmployeeRepository.java +++ b/src/main/java/com/petshop/backend/repository/EmployeeRepository.java @@ -1,7 +1,11 @@ package com.petshop.backend.repository; import com.petshop.backend.entity.Employee; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import java.util.List; @@ -11,4 +15,14 @@ import java.util.Optional; public interface EmployeeRepository extends JpaRepository { Optional findByUserId(Long userId); List findAllByEmail(String email); + + @Query("SELECT e FROM Employee e WHERE " + + "LOWER(e.firstName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + + "LOWER(e.lastName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + + "LOWER(e.email) LIKE LOWER(CONCAT('%', :q, '%')) OR " + + "LOWER(e.role) LIKE LOWER(CONCAT('%', :q, '%')) OR " + + "EXISTS (SELECT u FROM User u WHERE u.id = e.userId AND (" + + "LOWER(u.username) LIKE LOWER(CONCAT('%', :q, '%')) OR " + + "LOWER(COALESCE(u.phone, '')) LIKE LOWER(CONCAT('%', :q, '%'))))") + Page searchEmployees(@Param("q") String query, Pageable pageable); } diff --git a/src/main/java/com/petshop/backend/service/CustomerService.java b/src/main/java/com/petshop/backend/service/CustomerService.java index 888c7c82..040be22a 100644 --- a/src/main/java/com/petshop/backend/service/CustomerService.java +++ b/src/main/java/com/petshop/backend/service/CustomerService.java @@ -45,7 +45,6 @@ public class CustomerService { customer.setFirstName(request.getFirstName()); customer.setLastName(request.getLastName()); customer.setEmail(request.getEmail()); - customer.setPhone(request.getPhone()); customer = customerRepository.save(customer); syncLinkedUser(customer); @@ -60,7 +59,6 @@ public class CustomerService { customer.setFirstName(request.getFirstName()); customer.setLastName(request.getLastName()); customer.setEmail(request.getEmail()); - customer.setPhone(request.getPhone()); customer = customerRepository.save(customer); syncLinkedUser(customer); @@ -86,7 +84,6 @@ public class CustomerService { customer.getFirstName(), customer.getLastName(), customer.getEmail(), - customer.getPhone(), customer.getCreatedAt(), customer.getUpdatedAt() ); @@ -98,7 +95,6 @@ public class CustomerService { } userRepository.findById(customer.getUserId()).ifPresent(user -> { user.setEmail(customer.getEmail()); - user.setPhone(customer.getPhone()); user.setFullName((customer.getFirstName() + " " + customer.getLastName()).trim()); userRepository.save(user); }); diff --git a/src/main/java/com/petshop/backend/service/EmployeeService.java b/src/main/java/com/petshop/backend/service/EmployeeService.java new file mode 100644 index 00000000..2199e441 --- /dev/null +++ b/src/main/java/com/petshop/backend/service/EmployeeService.java @@ -0,0 +1,175 @@ +package com.petshop.backend.service; + +import com.petshop.backend.dto.employee.EmployeeRequest; +import com.petshop.backend.dto.employee.EmployeeResponse; +import com.petshop.backend.entity.Employee; +import com.petshop.backend.entity.User; +import com.petshop.backend.exception.ResourceNotFoundException; +import com.petshop.backend.repository.EmployeeRepository; +import com.petshop.backend.repository.UserRepository; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.server.ResponseStatusException; + +import static org.springframework.http.HttpStatus.CONFLICT; + +@Service +public class EmployeeService { + private final EmployeeRepository employeeRepository; + private final UserRepository userRepository; + private final PasswordEncoder passwordEncoder; + private final UserBusinessLinkageService userBusinessLinkageService; + + public EmployeeService(EmployeeRepository employeeRepository, UserRepository userRepository, PasswordEncoder passwordEncoder, UserBusinessLinkageService userBusinessLinkageService) { + this.employeeRepository = employeeRepository; + this.userRepository = userRepository; + this.passwordEncoder = passwordEncoder; + this.userBusinessLinkageService = userBusinessLinkageService; + } + + public Page getAllEmployees(String query, Pageable pageable) { + Page employees; + if (query != null && !query.trim().isEmpty()) { + employees = employeeRepository.searchEmployees(query, pageable); + } else { + employees = employeeRepository.findAll(pageable); + } + return employees.map(this::mapToResponse); + } + + public EmployeeResponse getEmployeeById(Long id) { + Employee employee = employeeRepository.findById(id) + .orElseThrow(() -> new ResourceNotFoundException("Employee not found with id: " + id)); + return mapToResponse(employee); + } + + @Transactional + public EmployeeResponse createEmployee(EmployeeRequest request) { + validateRole(request.getRole()); + if (request.getPassword() == null || request.getPassword().trim().length() < 6) { + throw new IllegalArgumentException("Password must be at least 6 characters"); + } + if (userRepository.findByUsername(request.getUsername()).isPresent()) { + throw new ResponseStatusException(CONFLICT, "Username already exists"); + } + if (request.getEmail() != null && userRepository.findByEmail(request.getEmail()).isPresent()) { + throw new ResponseStatusException(CONFLICT, "Email already exists"); + } + String phone = trimToNull(request.getPhone()); + if (phone != null && userRepository.findByPhone(phone).isPresent()) { + throw new ResponseStatusException(CONFLICT, "Phone already exists"); + } + + User user = new User(); + user.setUsername(request.getUsername()); + user.setPassword(passwordEncoder.encode(request.getPassword())); + user.setFullName(fullName(request)); + user.setEmail(request.getEmail()); + user.setPhone(phone); + user.setRole(request.getRole()); + user.setActive(request.getActive() != null ? request.getActive() : true); + user = userRepository.save(user); + + Employee employee = userBusinessLinkageService.ensureLinkedEmployee(user); + return mapToResponse(employee, user); + } + + @Transactional + public EmployeeResponse updateEmployee(Long id, EmployeeRequest request) { + Employee employee = employeeRepository.findById(id) + .orElseThrow(() -> new ResourceNotFoundException("Employee not found with id: " + id)); + User user = requireLinkedUser(employee); + + validateRole(request.getRole()); + if (!user.getUsername().equals(request.getUsername()) && userRepository.findByUsername(request.getUsername()).isPresent()) { + throw new ResponseStatusException(CONFLICT, "Username already exists"); + } + if (!java.util.Objects.equals(user.getEmail(), request.getEmail()) && request.getEmail() != null && userRepository.findByEmail(request.getEmail()).isPresent()) { + throw new ResponseStatusException(CONFLICT, "Email already exists"); + } + String phone = trimToNull(request.getPhone()); + Long currentUserId = user.getId(); + if (!java.util.Objects.equals(user.getPhone(), phone)) { + userRepository.findByPhone(phone) + .filter(existing -> !existing.getId().equals(currentUserId)) + .ifPresent(existing -> { throw new ResponseStatusException(CONFLICT, "Phone already exists"); }); + } + + user.setUsername(request.getUsername()); + if (request.getPassword() != null && !request.getPassword().trim().isEmpty()) { + user.setPassword(passwordEncoder.encode(request.getPassword())); + user.setTokenVersion(user.getTokenVersion() + 1); + } + user.setEmail(request.getEmail()); + user.setPhone(phone); + user.setFullName(fullName(request)); + user.setRole(request.getRole()); + user.setActive(request.getActive() != null ? request.getActive() : true); + user = userRepository.save(user); + + employee = userBusinessLinkageService.ensureLinkedEmployee(user); + return mapToResponse(employee, user); + } + + @Transactional + public void deleteEmployee(Long id) { + Employee employee = employeeRepository.findById(id) + .orElseThrow(() -> new ResourceNotFoundException("Employee not found with id: " + id)); + if (employee.getUserId() != null && userRepository.existsById(employee.getUserId())) { + userRepository.deleteById(employee.getUserId()); + return; + } + employeeRepository.deleteById(id); + } + + private EmployeeResponse mapToResponse(Employee employee) { + User user = requireLinkedUser(employee); + return mapToResponse(employee, user); + } + + private EmployeeResponse mapToResponse(Employee employee, User user) { + EmployeeResponse response = new EmployeeResponse(); + response.setEmployeeId(employee.getEmployeeId()); + response.setUserId(user.getId()); + response.setUsername(user.getUsername()); + response.setFirstName(employee.getFirstName()); + response.setLastName(employee.getLastName()); + response.setFullName(user.getFullName()); + response.setEmail(user.getEmail()); + response.setPhone(user.getPhone()); + response.setRole(user.getRole().name()); + response.setActive(user.getActive()); + response.setCreatedAt(employee.getCreatedAt()); + response.setUpdatedAt(employee.getUpdatedAt()); + return response; + } + + private User requireLinkedUser(Employee employee) { + if (employee.getUserId() == null) { + throw new ResourceNotFoundException("Employee user account not found"); + } + return userRepository.findById(employee.getUserId()) + .orElseThrow(() -> new ResourceNotFoundException("Employee user account not found")); + } + + private void validateRole(User.Role role) { + if (role != User.Role.STAFF && role != User.Role.ADMIN) { + throw new IllegalArgumentException("Employee role must be STAFF or ADMIN"); + } + } + + private String fullName(EmployeeRequest request) { + return (request.getFirstName().trim() + " " + request.getLastName().trim()).trim(); + } + + private String trimToNull(String value) { + if (value == null) { + return null; + } + String trimmed = value.trim(); + return trimmed.isEmpty() ? null : trimmed; + } +} diff --git a/src/main/java/com/petshop/backend/service/UserBusinessLinkageService.java b/src/main/java/com/petshop/backend/service/UserBusinessLinkageService.java index d0a18d3e..05751688 100644 --- a/src/main/java/com/petshop/backend/service/UserBusinessLinkageService.java +++ b/src/main/java/com/petshop/backend/service/UserBusinessLinkageService.java @@ -50,7 +50,6 @@ public class UserBusinessLinkageService { newEmployee.setFirstName(nameParts[0]); newEmployee.setLastName(nameParts[1]); - newEmployee.setPhone(normalizePhone(user.getPhone(), "000-000-0000")); newEmployee.setIsActive(true); if (user.getRole() == User.Role.ADMIN) { @@ -91,8 +90,6 @@ public class UserBusinessLinkageService { newCustomer.setFirstName(nameParts[0]); newCustomer.setLastName(nameParts[1]); - newCustomer.setPhone(normalizePhone(user.getPhone(), "000-000-0001")); - return syncCustomer(newCustomer, user); } @@ -111,7 +108,6 @@ public class UserBusinessLinkageService { String[] nameParts = splitFullName(user.getFullName()); employee.setFirstName(nameParts[0]); employee.setLastName(nameParts[1]); - employee.setPhone(normalizePhone(user.getPhone(), employee.getPhone())); if (user.getRole() == User.Role.ADMIN) { employee.setRole("Manager"); } else { @@ -126,17 +122,9 @@ public class UserBusinessLinkageService { String[] nameParts = splitFullName(user.getFullName()); customer.setFirstName(nameParts[0]); customer.setLastName(nameParts[1]); - customer.setPhone(normalizePhone(user.getPhone(), customer.getPhone())); return customerRepository.save(customer); } - private String normalizePhone(String phone, String fallback) { - if (phone == null || phone.trim().isEmpty()) { - return fallback; - } - return phone.trim(); - } - private String[] splitFullName(String fullName) { if (fullName == null || fullName.trim().isEmpty()) { return new String[]{"System", "User"}; diff --git a/src/main/resources/db/migration/V7__employee_customer_phone_cutover.sql b/src/main/resources/db/migration/V7__employee_customer_phone_cutover.sql new file mode 100644 index 00000000..fa922a82 --- /dev/null +++ b/src/main/resources/db/migration/V7__employee_customer_phone_cutover.sql @@ -0,0 +1,11 @@ +UPDATE users u +LEFT JOIN customer c ON c.user_id = u.id +LEFT JOIN employee e ON e.user_id = u.id +SET u.phone = COALESCE(NULLIF(u.phone, ''), NULLIF(c.phone, ''), NULLIF(e.phone, '')) +WHERE u.phone IS NULL OR u.phone = ''; + +ALTER TABLE customer + DROP COLUMN phone; + +ALTER TABLE employee + DROP COLUMN phone; From 5b3a91ada0475b8feb98d449ab733a62fb84d703 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Sat, 14 Mar 2026 21:38:42 -0600 Subject: [PATCH 80/84] filter users by role --- .../com/petshop/backend/controller/UserController.java | 4 +++- .../com/petshop/backend/repository/UserRepository.java | 8 ++++++++ .../java/com/petshop/backend/service/UserService.java | 9 +++++++-- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/petshop/backend/controller/UserController.java b/src/main/java/com/petshop/backend/controller/UserController.java index b1ece730..ed48bab2 100644 --- a/src/main/java/com/petshop/backend/controller/UserController.java +++ b/src/main/java/com/petshop/backend/controller/UserController.java @@ -3,6 +3,7 @@ package com.petshop.backend.controller; import com.petshop.backend.dto.common.BulkDeleteRequest; import com.petshop.backend.dto.user.UserRequest; import com.petshop.backend.dto.user.UserResponse; +import com.petshop.backend.entity.User; import com.petshop.backend.service.UserService; import jakarta.validation.Valid; import org.springframework.data.domain.Page; @@ -26,8 +27,9 @@ public class UserController { @GetMapping public ResponseEntity> getAllUsers( @RequestParam(required = false) String q, + @RequestParam(required = false) User.Role role, Pageable pageable) { - return ResponseEntity.ok(userService.getAllUsers(q, pageable)); + return ResponseEntity.ok(userService.getAllUsers(q, role, pageable)); } @GetMapping("/{id}") diff --git a/src/main/java/com/petshop/backend/repository/UserRepository.java b/src/main/java/com/petshop/backend/repository/UserRepository.java index 775c1b18..6bec352f 100644 --- a/src/main/java/com/petshop/backend/repository/UserRepository.java +++ b/src/main/java/com/petshop/backend/repository/UserRepository.java @@ -16,6 +16,7 @@ public interface UserRepository extends JpaRepository { Optional findByEmail(String email); Optional findByPhone(String phone); boolean existsByUsername(String username); + Page findByRole(User.Role role, Pageable pageable); @Query("SELECT u FROM User u WHERE " + "LOWER(u.username) LIKE LOWER(CONCAT('%', :q, '%')) OR " + @@ -23,4 +24,11 @@ public interface UserRepository extends JpaRepository { "LOWER(COALESCE(u.email, '')) LIKE LOWER(CONCAT('%', :q, '%')) OR " + "LOWER(COALESCE(u.phone, '')) LIKE LOWER(CONCAT('%', :q, '%'))") Page searchUsers(@Param("q") String query, Pageable pageable); + + @Query("SELECT u FROM User u WHERE u.role = :role AND (" + + "LOWER(u.username) LIKE LOWER(CONCAT('%', :q, '%')) OR " + + "LOWER(COALESCE(u.fullName, '')) LIKE LOWER(CONCAT('%', :q, '%')) OR " + + "LOWER(COALESCE(u.email, '')) LIKE LOWER(CONCAT('%', :q, '%')) OR " + + "LOWER(COALESCE(u.phone, '')) LIKE LOWER(CONCAT('%', :q, '%')))") + Page searchUsersByRole(@Param("q") String query, @Param("role") User.Role role, Pageable pageable); } diff --git a/src/main/java/com/petshop/backend/service/UserService.java b/src/main/java/com/petshop/backend/service/UserService.java index a54aecd3..c68743ef 100644 --- a/src/main/java/com/petshop/backend/service/UserService.java +++ b/src/main/java/com/petshop/backend/service/UserService.java @@ -28,10 +28,15 @@ public class UserService { this.userBusinessLinkageService = userBusinessLinkageService; } - public Page getAllUsers(String query, Pageable pageable) { + public Page getAllUsers(String query, User.Role role, Pageable pageable) { Page users; - if (query != null && !query.trim().isEmpty()) { + boolean hasQuery = query != null && !query.trim().isEmpty(); + if (hasQuery && role != null) { + users = userRepository.searchUsersByRole(query, role, pageable); + } else if (hasQuery) { users = userRepository.searchUsers(query, pageable); + } else if (role != null) { + users = userRepository.findByRole(role, pageable); } else { users = userRepository.findAll(pageable); } From 8c673fd66fa88f8082ab56c6e0560318120147af Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Sat, 14 Mar 2026 21:45:20 -0600 Subject: [PATCH 81/84] add user filter requests --- petshop-api.postman_collection.json | 105 ++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) diff --git a/petshop-api.postman_collection.json b/petshop-api.postman_collection.json index 6ecec217..73f6d258 100644 --- a/petshop-api.postman_collection.json +++ b/petshop-api.postman_collection.json @@ -2987,6 +2987,111 @@ } ] }, + { + "name": "List Staff Users", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/users?role=STAFF", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ] + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});", + "var jsonData = pm.response.json();", + "pm.test('All returned users are STAFF', function () {", + " pm.expect(jsonData.content.every(function (user) { return user.role === 'STAFF'; })).to.be.true;", + "});" + ] + } + } + ] + }, + { + "name": "List Admin Users", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/users?role=ADMIN", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ] + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});", + "var jsonData = pm.response.json();", + "pm.test('All returned users are ADMIN', function () {", + " pm.expect(jsonData.content.every(function (user) { return user.role === 'ADMIN'; })).to.be.true;", + "});" + ] + } + } + ] + }, + { + "name": "List Customer Users", + "request": { + "method": "GET", + "url": "{{baseUrl}}/api/v1/users?role=CUSTOMER", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + }, + { + "key": "Authorization", + "value": "Bearer {{adminToken}}", + "type": "text" + } + ] + }, + "event": [ + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});", + "var jsonData = pm.response.json();", + "pm.test('All returned users are CUSTOMER', function () {", + " pm.expect(jsonData.content.every(function (user) { return user.role === 'CUSTOMER'; })).to.be.true;", + "});" + ] + } + } + ] + }, { "name": "Get User", "request": { From 9d48692857ce6f78992d0e16841bd0436d9049ed Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Sat, 14 Mar 2026 21:56:02 -0600 Subject: [PATCH 82/84] fix user api validation --- petshop-api.postman_collection.json | 2 +- .../backend/dto/employee/EmployeeRequest.java | 1 + .../exception/GlobalExceptionHandler.java | 23 +++++++++++++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/petshop-api.postman_collection.json b/petshop-api.postman_collection.json index 73f6d258..d95a5b86 100644 --- a/petshop-api.postman_collection.json +++ b/petshop-api.postman_collection.json @@ -167,7 +167,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"username\": \"newcustomer{{$timestamp}}\",\n \"password\": \"password123\",\n \"email\": \"new{{$timestamp}}@example.com\",\n \"fullName\": \"New Customer\"\n}" + "raw": "{\n \"username\": \"newcustomer{{$timestamp}}\",\n \"password\": \"password123\",\n \"email\": \"new{{$timestamp}}@example.com\",\n \"phone\": \"+1-555-01{{$randomInt}}\",\n \"fullName\": \"New Customer\"\n}" } }, "event": [ diff --git a/src/main/java/com/petshop/backend/dto/employee/EmployeeRequest.java b/src/main/java/com/petshop/backend/dto/employee/EmployeeRequest.java index 0dfa25a8..f5fb9020 100644 --- a/src/main/java/com/petshop/backend/dto/employee/EmployeeRequest.java +++ b/src/main/java/com/petshop/backend/dto/employee/EmployeeRequest.java @@ -23,6 +23,7 @@ public class EmployeeRequest { @Email(message = "Invalid email format") private String email; + @NotBlank(message = "Phone is required") @Size(max = 20, message = "Phone must not exceed 20 characters") private String phone; diff --git a/src/main/java/com/petshop/backend/exception/GlobalExceptionHandler.java b/src/main/java/com/petshop/backend/exception/GlobalExceptionHandler.java index bfcfe03d..e055ea44 100644 --- a/src/main/java/com/petshop/backend/exception/GlobalExceptionHandler.java +++ b/src/main/java/com/petshop/backend/exception/GlobalExceptionHandler.java @@ -3,6 +3,8 @@ package com.petshop.backend.exception; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; +import org.springframework.web.server.ResponseStatusException; import org.springframework.validation.FieldError; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; @@ -82,6 +84,27 @@ public class GlobalExceptionHandler { return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error); } + @ExceptionHandler(MethodArgumentTypeMismatchException.class) + public ResponseEntity handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException ex) { + ErrorResponse error = new ErrorResponse( + HttpStatus.BAD_REQUEST.value(), + "Invalid value for parameter: " + ex.getName(), + LocalDateTime.now() + ); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error); + } + + @ExceptionHandler(ResponseStatusException.class) + public ResponseEntity handleResponseStatusException(ResponseStatusException ex) { + String message = ex.getReason() != null ? ex.getReason() : ex.getMessage(); + ErrorResponse error = new ErrorResponse( + ex.getStatusCode().value(), + message, + LocalDateTime.now() + ); + return ResponseEntity.status(ex.getStatusCode()).body(error); + } + @ExceptionHandler(Exception.class) public ResponseEntity handleGenericException(Exception ex) { ErrorResponse error = new ErrorResponse( From 972c090b41c31ac36e79531703b0fdd5bcf4e975 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Sat, 14 Mar 2026 22:06:49 -0600 Subject: [PATCH 83/84] harden user filters --- .../backend/controller/UserController.java | 2 +- .../security/JwtAuthenticationFilter.java | 9 ++++++- .../petshop/backend/service/UserService.java | 26 +++++++++++++++---- 3 files changed, 30 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/petshop/backend/controller/UserController.java b/src/main/java/com/petshop/backend/controller/UserController.java index ed48bab2..8f7e07c3 100644 --- a/src/main/java/com/petshop/backend/controller/UserController.java +++ b/src/main/java/com/petshop/backend/controller/UserController.java @@ -27,7 +27,7 @@ public class UserController { @GetMapping public ResponseEntity> getAllUsers( @RequestParam(required = false) String q, - @RequestParam(required = false) User.Role role, + @RequestParam(required = false) String role, Pageable pageable) { return ResponseEntity.ok(userService.getAllUsers(q, role, pageable)); } diff --git a/src/main/java/com/petshop/backend/security/JwtAuthenticationFilter.java b/src/main/java/com/petshop/backend/security/JwtAuthenticationFilter.java index 8d311f74..d804a3b7 100644 --- a/src/main/java/com/petshop/backend/security/JwtAuthenticationFilter.java +++ b/src/main/java/com/petshop/backend/security/JwtAuthenticationFilter.java @@ -2,6 +2,7 @@ package com.petshop.backend.security; import com.petshop.backend.entity.User; import com.petshop.backend.repository.UserRepository; +import io.jsonwebtoken.JwtException; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; @@ -41,7 +42,13 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { } jwt = authHeader.substring(7); - Long userId = jwtUtil.extractUserId(jwt); + Long userId; + try { + userId = jwtUtil.extractUserId(jwt); + } catch (JwtException | IllegalArgumentException ex) { + writeUnauthorized(response, "Invalid or expired token"); + return; + } if (userId != null && SecurityContextHolder.getContext().getAuthentication() == null) { User user = userRepository.findById(userId).orElse(null); diff --git a/src/main/java/com/petshop/backend/service/UserService.java b/src/main/java/com/petshop/backend/service/UserService.java index c68743ef..3c219172 100644 --- a/src/main/java/com/petshop/backend/service/UserService.java +++ b/src/main/java/com/petshop/backend/service/UserService.java @@ -13,6 +13,9 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.server.ResponseStatusException; +import java.util.Locale; + +import static org.springframework.http.HttpStatus.BAD_REQUEST; import static org.springframework.http.HttpStatus.CONFLICT; @Service @@ -28,15 +31,16 @@ public class UserService { this.userBusinessLinkageService = userBusinessLinkageService; } - public Page getAllUsers(String query, User.Role role, Pageable pageable) { + public Page getAllUsers(String query, String role, Pageable pageable) { + User.Role parsedRole = parseRole(role); Page users; boolean hasQuery = query != null && !query.trim().isEmpty(); - if (hasQuery && role != null) { - users = userRepository.searchUsersByRole(query, role, pageable); + if (hasQuery && parsedRole != null) { + users = userRepository.searchUsersByRole(query, parsedRole, pageable); } else if (hasQuery) { users = userRepository.searchUsers(query, pageable); - } else if (role != null) { - users = userRepository.findByRole(role, pageable); + } else if (parsedRole != null) { + users = userRepository.findByRole(parsedRole, pageable); } else { users = userRepository.findAll(pageable); } @@ -147,4 +151,16 @@ public class UserService { String trimmed = value.trim(); return trimmed.isEmpty() ? null : trimmed; } + + private User.Role parseRole(String role) { + String normalizedRole = trimToNull(role); + if (normalizedRole == null) { + return null; + } + try { + return User.Role.valueOf(normalizedRole.toUpperCase(Locale.ROOT)); + } catch (IllegalArgumentException ex) { + throw new ResponseStatusException(BAD_REQUEST, "Invalid value for parameter: role"); + } + } } From 826798d976242f9a0d360c087216241f7e373244 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Sun, 15 Mar 2026 11:32:26 -0600 Subject: [PATCH 84/84] harden endpoint errors --- .../config/TomcatPathToleranceConfig.java | 19 +++ .../TrailingSlashNormalizationFilter.java | 91 ++++++++++++++ .../WebSocketAuthChannelInterceptor.java | 68 +++++++++-- .../backend/config/WebSocketConfig.java | 5 + .../controller/ChatWebSocketController.java | 61 +++++++++- .../backend/exception/ApiErrorResponder.java | 32 +++++ .../backend/exception/ApiErrorResponse.java | 12 ++ .../exception/GlobalExceptionHandler.java | 113 ++++++++---------- .../security/JwtAuthenticationFilter.java | 22 ++-- .../security/RestAccessDeniedHandler.java | 33 +++++ .../RestAuthenticationEntryPoint.java | 33 +++++ .../backend/security/SecurityConfig.java | 15 ++- .../backend/service/EmployeeService.java | 24 ++-- .../security/JwtAuthenticationFilterTest.java | 10 +- 14 files changed, 439 insertions(+), 99 deletions(-) create mode 100644 src/main/java/com/petshop/backend/config/TomcatPathToleranceConfig.java create mode 100644 src/main/java/com/petshop/backend/config/TrailingSlashNormalizationFilter.java create mode 100644 src/main/java/com/petshop/backend/exception/ApiErrorResponder.java create mode 100644 src/main/java/com/petshop/backend/exception/ApiErrorResponse.java create mode 100644 src/main/java/com/petshop/backend/security/RestAccessDeniedHandler.java create mode 100644 src/main/java/com/petshop/backend/security/RestAuthenticationEntryPoint.java diff --git a/src/main/java/com/petshop/backend/config/TomcatPathToleranceConfig.java b/src/main/java/com/petshop/backend/config/TomcatPathToleranceConfig.java new file mode 100644 index 00000000..9a89c5ab --- /dev/null +++ b/src/main/java/com/petshop/backend/config/TomcatPathToleranceConfig.java @@ -0,0 +1,19 @@ +package com.petshop.backend.config; + +import org.springframework.boot.tomcat.servlet.TomcatServletWebServerFactory; +import org.springframework.boot.web.server.WebServerFactoryCustomizer; +import org.springframework.stereotype.Component; + +@Component +public class TomcatPathToleranceConfig implements WebServerFactoryCustomizer { + + @Override + public void customize(TomcatServletWebServerFactory factory) { + factory.addConnectorCustomizers(connector -> { + connector.setAllowBackslash(true); + connector.setEncodedReverseSolidusHandling("decode"); + connector.setProperty("relaxedPathChars", "\\"); + connector.setProperty("relaxedQueryChars", "\\"); + }); + } +} diff --git a/src/main/java/com/petshop/backend/config/TrailingSlashNormalizationFilter.java b/src/main/java/com/petshop/backend/config/TrailingSlashNormalizationFilter.java new file mode 100644 index 00000000..38ececb9 --- /dev/null +++ b/src/main/java/com/petshop/backend/config/TrailingSlashNormalizationFilter.java @@ -0,0 +1,91 @@ +package com.petshop.backend.config; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequestWrapper; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; + +@Component +@Order(Ordered.HIGHEST_PRECEDENCE) +public class TrailingSlashNormalizationFilter extends OncePerRequestFilter { + + @Override + protected boolean shouldNotFilter(HttpServletRequest request) { + String requestUri = request.getRequestURI(); + if (requestUri == null || requestUri.isBlank()) { + return true; + } + return requestUri.equals(normalizePath(requestUri)); + } + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + String normalizedUri = normalizePath(request.getRequestURI()); + String normalizedServletPath = normalizePath(request.getServletPath()); + String normalizedPathInfo = normalizePath(request.getPathInfo()); + + HttpServletRequestWrapper wrapper = new HttpServletRequestWrapper(request) { + @Override + public String getRequestURI() { + return normalizedUri; + } + + @Override + public StringBuffer getRequestURL() { + String original = super.getRequestURL().toString(); + int schemeSeparator = original.indexOf("://"); + int pathStart = schemeSeparator >= 0 ? original.indexOf('/', schemeSeparator + 3) : original.indexOf('/'); + if (pathStart < 0) { + return new StringBuffer(original); + } + String prefix = original.substring(0, pathStart); + return new StringBuffer(prefix + normalizedUri); + } + + @Override + public String getServletPath() { + return normalizedServletPath; + } + + @Override + public String getPathInfo() { + return normalizedPathInfo; + } + }; + + filterChain.doFilter(wrapper, response); + } + + private String normalizePath(String value) { + if (value == null) { + return null; + } + String normalized = value.replace('\\', '/'); + while (normalized.contains("//")) { + normalized = normalized.replace("//", "/"); + } + if (shouldLowercase(normalized)) { + normalized = normalized.toLowerCase(java.util.Locale.ROOT); + } + int end = normalized.length(); + while (end > 1 && normalized.charAt(end - 1) == '/') { + end--; + } + return normalized.substring(0, end); + } + + private boolean shouldLowercase(String path) { + String lower = path.toLowerCase(java.util.Locale.ROOT); + return lower.startsWith("/api/") + || lower.equals("/api") + || lower.startsWith("/ws/") + || lower.equals("/ws"); + } +} diff --git a/src/main/java/com/petshop/backend/config/WebSocketAuthChannelInterceptor.java b/src/main/java/com/petshop/backend/config/WebSocketAuthChannelInterceptor.java index b62dfe34..c7f23fc4 100644 --- a/src/main/java/com/petshop/backend/config/WebSocketAuthChannelInterceptor.java +++ b/src/main/java/com/petshop/backend/config/WebSocketAuthChannelInterceptor.java @@ -5,6 +5,7 @@ import com.petshop.backend.repository.UserRepository; import com.petshop.backend.security.AppPrincipal; import com.petshop.backend.security.JwtUtil; import com.petshop.backend.service.ChatService; +import io.jsonwebtoken.JwtException; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.simp.stomp.StompCommand; @@ -14,7 +15,10 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticatio import org.springframework.stereotype.Component; import java.security.Principal; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Locale; +import java.util.Map; @Component public class WebSocketAuthChannelInterceptor implements ChannelInterceptor { @@ -45,7 +49,7 @@ public class WebSocketAuthChannelInterceptor implements ChannelInterceptor { throw new IllegalArgumentException("Missing websocket token"); } - Long userId = jwtUtil.extractUserId(token); + Long userId = extractUserId(token); User user = userId == null ? null : userRepository.findById(userId).orElse(null); if (user == null) { throw new IllegalArgumentException("User not found"); @@ -73,15 +77,15 @@ public class WebSocketAuthChannelInterceptor implements ChannelInterceptor { return message; } + if (StompCommand.DISCONNECT.equals(command) || StompCommand.UNSUBSCRIBE.equals(command)) { + return message; + } + User user = resolveUser(accessor.getUser(), accessor); if (user == null) { throw new IllegalArgumentException("Unauthenticated websocket session"); } - if (StompCommand.DISCONNECT.equals(command) || StompCommand.UNSUBSCRIBE.equals(command)) { - return message; - } - if (StompCommand.SUBSCRIBE.equals(command)) { authorizeSubscription(accessor.getDestination(), user); } else if (StompCommand.SEND.equals(command)) { @@ -118,15 +122,22 @@ public class WebSocketAuthChannelInterceptor implements ChannelInterceptor { return null; } - Long userId = jwtUtil.extractUserId(token); + Long userId = extractUserId(token); User user = userId == null ? null : userRepository.findById(userId).orElse(null); - if (user == null || user.getActive() == null || !user.getActive() || !jwtUtil.validateToken(token, user)) { + if (user == null) { throw new IllegalArgumentException("User not found"); } + if (user.getActive() == null || !user.getActive()) { + throw new IllegalArgumentException("User account is inactive"); + } + if (!jwtUtil.validateToken(token, user)) { + throw new IllegalArgumentException("Invalid websocket token"); + } return user; } private void authorizeSubscription(String destination, User user) { + destination = normalizeDestination(destination); if (destination == null || destination.startsWith("/user/queue/")) { return; } @@ -147,6 +158,7 @@ public class WebSocketAuthChannelInterceptor implements ChannelInterceptor { } private void authorizeSend(String destination, User user) { + destination = normalizeDestination(destination); Long conversationId = extractConversationId(destination, "/app/chat/conversations/"); if (conversationId != null && destination.endsWith("/messages") && chatService.hasConversationAccess(conversationId, user.getId(), user.getRole())) { return; @@ -175,13 +187,51 @@ public class WebSocketAuthChannelInterceptor implements ChannelInterceptor { private String firstHeader(StompHeaderAccessor accessor, String name) { List values = accessor.getNativeHeader(name); - return values == null || values.isEmpty() ? null : values.get(0); + if (values != null && !values.isEmpty()) { + return values.get(0); + } + for (String headerName : accessor.toNativeHeaderMap().keySet()) { + if (headerName.equalsIgnoreCase(name)) { + List alternateValues = accessor.getNativeHeader(headerName); + return alternateValues == null || alternateValues.isEmpty() ? null : alternateValues.get(0); + } + } + return null; } private String extractToken(String rawValue) { if (rawValue == null || rawValue.isBlank()) { return null; } - return rawValue.startsWith("Bearer ") ? rawValue.substring(7) : rawValue; + String normalized = rawValue.trim(); + return normalized.regionMatches(true, 0, "Bearer ", 0, 7) ? normalized.substring(7) : normalized; + } + + private String normalizeDestination(String destination) { + if (destination == null || destination.isBlank()) { + return destination; + } + String normalized = destination.replace('\\', '/'); + while (normalized.contains("//")) { + normalized = normalized.replace("//", "/"); + } + return normalized.toLowerCase(Locale.ROOT); + } + + private Long extractUserId(String token) { + try { + return jwtUtil.extractUserId(token); + } catch (JwtException | IllegalArgumentException ex) { + throw new IllegalArgumentException("Invalid websocket token: " + ex.getMessage(), ex); + } + } + + public Map buildErrorPayload(Exception ex, String destination, Principal principal) { + Map response = new LinkedHashMap<>(); + response.put("message", ex.getMessage() == null || ex.getMessage().isBlank() ? "WebSocket request failed" : ex.getMessage()); + response.put("details", ex.getClass().getSimpleName()); + response.put("destination", normalizeDestination(destination)); + response.put("authenticated", principal != null); + return response; } } diff --git a/src/main/java/com/petshop/backend/config/WebSocketConfig.java b/src/main/java/com/petshop/backend/config/WebSocketConfig.java index 27526bec..67dc1048 100644 --- a/src/main/java/com/petshop/backend/config/WebSocketConfig.java +++ b/src/main/java/com/petshop/backend/config/WebSocketConfig.java @@ -33,8 +33,13 @@ public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/ws/chat") .setAllowedOriginPatterns("*"); + registry.addEndpoint("/ws/chat/") + .setAllowedOriginPatterns("*"); registry.addEndpoint("/ws/chat-sockjs") .setAllowedOriginPatterns("*") .withSockJS(); + registry.addEndpoint("/ws/chat-sockjs/") + .setAllowedOriginPatterns("*") + .withSockJS(); } } diff --git a/src/main/java/com/petshop/backend/controller/ChatWebSocketController.java b/src/main/java/com/petshop/backend/controller/ChatWebSocketController.java index d7f1e3b6..ed0a3718 100644 --- a/src/main/java/com/petshop/backend/controller/ChatWebSocketController.java +++ b/src/main/java/com/petshop/backend/controller/ChatWebSocketController.java @@ -1,5 +1,6 @@ package com.petshop.backend.controller; +import com.petshop.backend.config.WebSocketAuthChannelInterceptor; import com.petshop.backend.dto.chat.MessageRequest; import com.petshop.backend.dto.chat.MessageResponse; import com.petshop.backend.entity.User; @@ -10,6 +11,7 @@ import com.petshop.backend.service.ChatRealtimeService; import com.petshop.backend.service.ChatService; import jakarta.validation.Valid; import org.springframework.messaging.handler.annotation.DestinationVariable; +import org.springframework.messaging.handler.annotation.MessageExceptionHandler; import org.springframework.messaging.handler.annotation.MessageMapping; import org.springframework.messaging.handler.annotation.Payload; import org.springframework.messaging.simp.SimpMessageHeaderAccessor; @@ -17,6 +19,9 @@ import org.springframework.messaging.simp.annotation.SendToUser; import org.springframework.stereotype.Controller; import java.security.Principal; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; @Controller public class ChatWebSocketController { @@ -25,12 +30,20 @@ public class ChatWebSocketController { private final ChatRealtimeService chatRealtimeService; private final UserRepository userRepository; private final JwtUtil jwtUtil; + private final WebSocketAuthChannelInterceptor webSocketAuthChannelInterceptor; - public ChatWebSocketController(ChatService chatService, ChatRealtimeService chatRealtimeService, UserRepository userRepository, JwtUtil jwtUtil) { + public ChatWebSocketController( + ChatService chatService, + ChatRealtimeService chatRealtimeService, + UserRepository userRepository, + JwtUtil jwtUtil, + WebSocketAuthChannelInterceptor webSocketAuthChannelInterceptor + ) { this.chatService = chatService; this.chatRealtimeService = chatRealtimeService; this.userRepository = userRepository; this.jwtUtil = jwtUtil; + this.webSocketAuthChannelInterceptor = webSocketAuthChannelInterceptor; } @MessageMapping("/chat/conversations/{id}/messages") @@ -42,6 +55,12 @@ public class ChatWebSocketController { chatRealtimeService.publishConversationUpdate(id); } + @MessageExceptionHandler({IllegalArgumentException.class, RuntimeException.class}) + @SendToUser("/queue/chat/errors") + public Map handleMessageException(Exception ex, SimpMessageHeaderAccessor headerAccessor) { + return webSocketAuthChannelInterceptor.buildErrorPayload(ex, headerAccessor.getDestination(), headerAccessor.getUser()); + } + private User resolveUser(SimpMessageHeaderAccessor headerAccessor) { Principal principal = headerAccessor.getUser(); if (principal instanceof org.springframework.security.authentication.UsernamePasswordAuthenticationToken authenticationToken @@ -55,20 +74,50 @@ public class ChatWebSocketController { .orElseThrow(() -> new IllegalArgumentException("User not found")); } - String tokenHeader = headerAccessor.getFirstNativeHeader("Authorization"); + String tokenHeader = firstHeader(headerAccessor, "Authorization"); if (tokenHeader == null || tokenHeader.isBlank()) { - tokenHeader = headerAccessor.getFirstNativeHeader("token"); + tokenHeader = firstHeader(headerAccessor, "token"); } if (tokenHeader == null || tokenHeader.isBlank()) { throw new IllegalArgumentException("User not authenticated"); } - String token = tokenHeader.startsWith("Bearer ") ? tokenHeader.substring(7) : tokenHeader; - Long userId = jwtUtil.extractUserId(token); + String token = extractToken(tokenHeader); + Long userId; + try { + userId = jwtUtil.extractUserId(token); + } catch (RuntimeException ex) { + throw new IllegalArgumentException("Invalid websocket token", ex); + } User user = userId == null ? null : userRepository.findById(userId).orElse(null); - if (user == null || user.getActive() == null || !user.getActive() || !jwtUtil.validateToken(token, user)) { + if (user == null) { throw new IllegalArgumentException("User not found"); } + if (user.getActive() == null || !user.getActive()) { + throw new IllegalArgumentException("User account is inactive"); + } + if (!jwtUtil.validateToken(token, user)) { + throw new IllegalArgumentException("Invalid websocket token"); + } return user; } + + private String firstHeader(SimpMessageHeaderAccessor headerAccessor, String name) { + List values = headerAccessor.getNativeHeader(name); + if (values != null && !values.isEmpty()) { + return values.get(0); + } + Map> headers = headerAccessor.toNativeHeaderMap(); + for (Map.Entry> entry : headers.entrySet()) { + if (entry.getKey().equalsIgnoreCase(name)) { + return entry.getValue() == null || entry.getValue().isEmpty() ? null : entry.getValue().get(0); + } + } + return null; + } + + private String extractToken(String rawValue) { + String normalized = rawValue.trim(); + return normalized.regionMatches(true, 0, "Bearer ", 0, 7) ? normalized.substring(7) : normalized; + } } diff --git a/src/main/java/com/petshop/backend/exception/ApiErrorResponder.java b/src/main/java/com/petshop/backend/exception/ApiErrorResponder.java new file mode 100644 index 00000000..39f4d66c --- /dev/null +++ b/src/main/java/com/petshop/backend/exception/ApiErrorResponder.java @@ -0,0 +1,32 @@ +package com.petshop.backend.exception; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.json.JsonMapper; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.time.LocalDateTime; + +@Component +public class ApiErrorResponder { + + private final ObjectMapper objectMapper = JsonMapper.builder().findAndAddModules().build(); + + public void write(HttpServletResponse response, HttpStatus status, String message, String details, String path) throws IOException { + response.setStatus(status.value()); + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + objectMapper.writeValue( + response.getWriter(), + new ApiErrorResponse( + status.value(), + message, + details, + path, + LocalDateTime.now() + ) + ); + } +} diff --git a/src/main/java/com/petshop/backend/exception/ApiErrorResponse.java b/src/main/java/com/petshop/backend/exception/ApiErrorResponse.java new file mode 100644 index 00000000..b3aea542 --- /dev/null +++ b/src/main/java/com/petshop/backend/exception/ApiErrorResponse.java @@ -0,0 +1,12 @@ +package com.petshop.backend.exception; + +import java.time.LocalDateTime; + +public record ApiErrorResponse( + int status, + String message, + String details, + String path, + LocalDateTime timestamp +) { +} diff --git a/src/main/java/com/petshop/backend/exception/GlobalExceptionHandler.java b/src/main/java/com/petshop/backend/exception/GlobalExceptionHandler.java index e055ea44..b41f8789 100644 --- a/src/main/java/com/petshop/backend/exception/GlobalExceptionHandler.java +++ b/src/main/java/com/petshop/backend/exception/GlobalExceptionHandler.java @@ -1,14 +1,15 @@ package com.petshop.backend.exception; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.dao.DataIntegrityViolationException; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.dao.DataIntegrityViolationException; -import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; -import org.springframework.web.server.ResponseStatusException; import org.springframework.validation.FieldError; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; +import org.springframework.web.server.ResponseStatusException; import java.time.LocalDateTime; import java.util.HashMap; @@ -18,27 +19,17 @@ import java.util.Map; public class GlobalExceptionHandler { @ExceptionHandler(ResourceNotFoundException.class) - public ResponseEntity handleResourceNotFound(ResourceNotFoundException ex) { - ErrorResponse error = new ErrorResponse( - HttpStatus.NOT_FOUND.value(), - ex.getMessage(), - LocalDateTime.now() - ); - return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error); + public ResponseEntity handleResourceNotFound(ResourceNotFoundException ex, HttpServletRequest request) { + return buildErrorResponse(HttpStatus.NOT_FOUND, ex.getMessage(), ex, request); } @ExceptionHandler(BusinessException.class) - public ResponseEntity handleBusinessException(BusinessException ex) { - ErrorResponse error = new ErrorResponse( - HttpStatus.BAD_REQUEST.value(), - ex.getMessage(), - LocalDateTime.now() - ); - return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error); + public ResponseEntity handleBusinessException(BusinessException ex, HttpServletRequest request) { + return buildErrorResponse(HttpStatus.BAD_REQUEST, ex.getMessage(), ex, request); } @ExceptionHandler(MethodArgumentNotValidException.class) - public ResponseEntity> handleValidationExceptions(MethodArgumentNotValidException ex) { + public ResponseEntity> handleValidationExceptions(MethodArgumentNotValidException ex, HttpServletRequest request) { Map errors = new HashMap<>(); ex.getBindingResult().getAllErrors().forEach((error) -> { String fieldName = ((FieldError) error).getField(); @@ -48,72 +39,74 @@ public class GlobalExceptionHandler { Map response = new HashMap<>(); response.put("status", HttpStatus.BAD_REQUEST.value()); + response.put("message", "Validation failed"); response.put("errors", errors); + response.put("details", buildDetails(ex)); + response.put("path", request.getRequestURI()); response.put("timestamp", LocalDateTime.now()); return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response); } @ExceptionHandler(org.springframework.security.access.AccessDeniedException.class) - public ResponseEntity handleAccessDeniedException(org.springframework.security.access.AccessDeniedException ex) { - ErrorResponse error = new ErrorResponse( - HttpStatus.FORBIDDEN.value(), - ex.getMessage(), - LocalDateTime.now() - ); - return ResponseEntity.status(HttpStatus.FORBIDDEN).body(error); + public ResponseEntity handleAccessDeniedException(org.springframework.security.access.AccessDeniedException ex, HttpServletRequest request) { + return buildErrorResponse(HttpStatus.FORBIDDEN, ex.getMessage(), ex, request); } @ExceptionHandler(IllegalArgumentException.class) - public ResponseEntity handleIllegalArgumentException(IllegalArgumentException ex) { - ErrorResponse error = new ErrorResponse( - HttpStatus.BAD_REQUEST.value(), - ex.getMessage(), - LocalDateTime.now() - ); - return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error); + public ResponseEntity handleIllegalArgumentException(IllegalArgumentException ex, HttpServletRequest request) { + return buildErrorResponse(HttpStatus.BAD_REQUEST, ex.getMessage(), ex, request); } @ExceptionHandler(DataIntegrityViolationException.class) - public ResponseEntity handleDataIntegrityViolationException(DataIntegrityViolationException ex) { - ErrorResponse error = new ErrorResponse( - HttpStatus.BAD_REQUEST.value(), - "Operation violates existing data relationships", - LocalDateTime.now() - ); - return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error); + public ResponseEntity handleDataIntegrityViolationException(DataIntegrityViolationException ex, HttpServletRequest request) { + return buildErrorResponse(HttpStatus.BAD_REQUEST, "Operation violates existing data relationships", ex, request); } @ExceptionHandler(MethodArgumentTypeMismatchException.class) - public ResponseEntity handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException ex) { - ErrorResponse error = new ErrorResponse( - HttpStatus.BAD_REQUEST.value(), - "Invalid value for parameter: " + ex.getName(), - LocalDateTime.now() - ); - return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error); + public ResponseEntity handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException ex, HttpServletRequest request) { + String message = "Invalid value for parameter: " + ex.getName(); + if (ex.getValue() != null) { + message += " (" + ex.getValue() + ")"; + } + return buildErrorResponse(HttpStatus.BAD_REQUEST, message, ex, request); } @ExceptionHandler(ResponseStatusException.class) - public ResponseEntity handleResponseStatusException(ResponseStatusException ex) { + public ResponseEntity handleResponseStatusException(ResponseStatusException ex, HttpServletRequest request) { String message = ex.getReason() != null ? ex.getReason() : ex.getMessage(); - ErrorResponse error = new ErrorResponse( - ex.getStatusCode().value(), - message, - LocalDateTime.now() - ); - return ResponseEntity.status(ex.getStatusCode()).body(error); + return buildErrorResponse(HttpStatus.valueOf(ex.getStatusCode().value()), message, ex, request); } @ExceptionHandler(Exception.class) - public ResponseEntity handleGenericException(Exception ex) { - ErrorResponse error = new ErrorResponse( - HttpStatus.INTERNAL_SERVER_ERROR.value(), - "An unexpected error occurred: " + ex.getMessage(), + public ResponseEntity handleGenericException(Exception ex, HttpServletRequest request) { + String message = ex.getMessage() == null || ex.getMessage().isBlank() + ? "Unexpected server error" + : ex.getMessage(); + return buildErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR, message, ex, request); + } + + private ResponseEntity buildErrorResponse(HttpStatus status, String message, Exception ex, HttpServletRequest request) { + ApiErrorResponse error = new ApiErrorResponse( + status.value(), + message, + buildDetails(ex), + request.getRequestURI(), LocalDateTime.now() ); - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error); + return ResponseEntity.status(status).body(error); + } + + private String buildDetails(Exception ex) { + Throwable rootCause = ex; + while (rootCause.getCause() != null && rootCause.getCause() != rootCause) { + rootCause = rootCause.getCause(); + } + + String rootMessage = rootCause.getMessage(); + if (rootMessage == null || rootMessage.isBlank()) { + return rootCause.getClass().getSimpleName(); + } + return rootCause.getClass().getSimpleName() + ": " + rootMessage; } } - -record ErrorResponse(int status, String message, LocalDateTime timestamp) {} diff --git a/src/main/java/com/petshop/backend/security/JwtAuthenticationFilter.java b/src/main/java/com/petshop/backend/security/JwtAuthenticationFilter.java index d804a3b7..a4caaaef 100644 --- a/src/main/java/com/petshop/backend/security/JwtAuthenticationFilter.java +++ b/src/main/java/com/petshop/backend/security/JwtAuthenticationFilter.java @@ -1,6 +1,7 @@ package com.petshop.backend.security; import com.petshop.backend.entity.User; +import com.petshop.backend.exception.ApiErrorResponder; import com.petshop.backend.repository.UserRepository; import io.jsonwebtoken.JwtException; import jakarta.servlet.FilterChain; @@ -15,17 +16,17 @@ import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; -import java.time.LocalDateTime; - @Component public class JwtAuthenticationFilter extends OncePerRequestFilter { private final JwtUtil jwtUtil; private final UserRepository userRepository; + private final ApiErrorResponder apiErrorResponder; - public JwtAuthenticationFilter(JwtUtil jwtUtil, UserRepository userRepository) { + public JwtAuthenticationFilter(JwtUtil jwtUtil, UserRepository userRepository, ApiErrorResponder apiErrorResponder) { this.jwtUtil = jwtUtil; this.userRepository = userRepository; + this.apiErrorResponder = apiErrorResponder; } @Override @@ -46,18 +47,18 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { try { userId = jwtUtil.extractUserId(jwt); } catch (JwtException | IllegalArgumentException ex) { - writeUnauthorized(response, "Invalid or expired token"); + writeUnauthorized(request, response, "Invalid or expired token", ex); return; } if (userId != null && SecurityContextHolder.getContext().getAuthentication() == null) { User user = userRepository.findById(userId).orElse(null); if (user == null || user.getActive() == null || !user.getActive()) { - writeUnauthorized(response, "User account is inactive"); + writeUnauthorized(request, response, "User account is inactive", null); return; } if (!jwtUtil.validateToken(jwt, user)) { - writeUnauthorized(response, "Invalid or expired token"); + writeUnauthorized(request, response, "Invalid or expired token", null); return; } @@ -78,11 +79,8 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { filterChain.doFilter(request, response); } - private void writeUnauthorized(HttpServletResponse response, String message) throws IOException { - response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); - response.setContentType("application/json"); - response.getWriter().write( - "{\"status\":401,\"message\":\"" + message + "\",\"timestamp\":\"" + LocalDateTime.now() + "\"}" - ); + private void writeUnauthorized(HttpServletRequest request, HttpServletResponse response, String message, Exception ex) throws IOException { + String details = ex == null ? message : ex.getClass().getSimpleName() + ": " + ex.getMessage(); + apiErrorResponder.write(response, org.springframework.http.HttpStatus.UNAUTHORIZED, message, details, request.getRequestURI()); } } diff --git a/src/main/java/com/petshop/backend/security/RestAccessDeniedHandler.java b/src/main/java/com/petshop/backend/security/RestAccessDeniedHandler.java new file mode 100644 index 00000000..2ef240e9 --- /dev/null +++ b/src/main/java/com/petshop/backend/security/RestAccessDeniedHandler.java @@ -0,0 +1,33 @@ +package com.petshop.backend.security; + +import com.petshop.backend.exception.ApiErrorResponder; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.http.HttpStatus; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.web.access.AccessDeniedHandler; +import org.springframework.stereotype.Component; + +import java.io.IOException; + +@Component +public class RestAccessDeniedHandler implements AccessDeniedHandler { + + private final ApiErrorResponder apiErrorResponder; + + public RestAccessDeniedHandler(ApiErrorResponder apiErrorResponder) { + this.apiErrorResponder = apiErrorResponder; + } + + @Override + public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { + apiErrorResponder.write( + response, + HttpStatus.FORBIDDEN, + "Access Denied", + accessDeniedException.getClass().getSimpleName() + ": " + accessDeniedException.getMessage(), + request.getRequestURI() + ); + } +} diff --git a/src/main/java/com/petshop/backend/security/RestAuthenticationEntryPoint.java b/src/main/java/com/petshop/backend/security/RestAuthenticationEntryPoint.java new file mode 100644 index 00000000..2ae541b4 --- /dev/null +++ b/src/main/java/com/petshop/backend/security/RestAuthenticationEntryPoint.java @@ -0,0 +1,33 @@ +package com.petshop.backend.security; + +import com.petshop.backend.exception.ApiErrorResponder; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.http.HttpStatus; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.stereotype.Component; + +import java.io.IOException; + +@Component +public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint { + + private final ApiErrorResponder apiErrorResponder; + + public RestAuthenticationEntryPoint(ApiErrorResponder apiErrorResponder) { + this.apiErrorResponder = apiErrorResponder; + } + + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { + apiErrorResponder.write( + response, + HttpStatus.UNAUTHORIZED, + "Authentication required", + authException.getClass().getSimpleName() + ": " + authException.getMessage(), + request.getRequestURI() + ); + } +} diff --git a/src/main/java/com/petshop/backend/security/SecurityConfig.java b/src/main/java/com/petshop/backend/security/SecurityConfig.java index 0a893c18..00ce63f8 100644 --- a/src/main/java/com/petshop/backend/security/SecurityConfig.java +++ b/src/main/java/com/petshop/backend/security/SecurityConfig.java @@ -25,10 +25,19 @@ public class SecurityConfig { private final JwtAuthenticationFilter jwtAuthFilter; private final UserDetailsService userDetailsService; + private final RestAuthenticationEntryPoint restAuthenticationEntryPoint; + private final RestAccessDeniedHandler restAccessDeniedHandler; - public SecurityConfig(JwtAuthenticationFilter jwtAuthFilter, UserDetailsService userDetailsService) { + public SecurityConfig( + JwtAuthenticationFilter jwtAuthFilter, + UserDetailsService userDetailsService, + RestAuthenticationEntryPoint restAuthenticationEntryPoint, + RestAccessDeniedHandler restAccessDeniedHandler + ) { this.jwtAuthFilter = jwtAuthFilter; this.userDetailsService = userDetailsService; + this.restAuthenticationEntryPoint = restAuthenticationEntryPoint; + this.restAccessDeniedHandler = restAccessDeniedHandler; } @Bean @@ -47,6 +56,10 @@ public class SecurityConfig { .requestMatchers(HttpMethod.GET, "/api/v1/appointments/availability").permitAll() .anyRequest().authenticated() ) + .exceptionHandling(ex -> ex + .authenticationEntryPoint(restAuthenticationEntryPoint) + .accessDeniedHandler(restAccessDeniedHandler) + ) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authenticationProvider(daoAuthenticationProvider()) .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class); diff --git a/src/main/java/com/petshop/backend/service/EmployeeService.java b/src/main/java/com/petshop/backend/service/EmployeeService.java index 2199e441..baf83bb8 100644 --- a/src/main/java/com/petshop/backend/service/EmployeeService.java +++ b/src/main/java/com/petshop/backend/service/EmployeeService.java @@ -126,22 +126,22 @@ public class EmployeeService { } private EmployeeResponse mapToResponse(Employee employee) { - User user = requireLinkedUser(employee); + User user = employee.getUserId() == null ? null : userRepository.findById(employee.getUserId()).orElse(null); return mapToResponse(employee, user); } private EmployeeResponse mapToResponse(Employee employee, User user) { EmployeeResponse response = new EmployeeResponse(); response.setEmployeeId(employee.getEmployeeId()); - response.setUserId(user.getId()); - response.setUsername(user.getUsername()); + response.setUserId(user != null ? user.getId() : employee.getUserId()); + response.setUsername(user != null ? user.getUsername() : null); response.setFirstName(employee.getFirstName()); response.setLastName(employee.getLastName()); - response.setFullName(user.getFullName()); - response.setEmail(user.getEmail()); - response.setPhone(user.getPhone()); - response.setRole(user.getRole().name()); - response.setActive(user.getActive()); + response.setFullName(user != null ? user.getFullName() : fullName(employee)); + response.setEmail(user != null ? user.getEmail() : employee.getEmail()); + response.setPhone(user != null ? user.getPhone() : null); + response.setRole(user != null ? user.getRole().name() : normalizeRole(employee.getRole())); + response.setActive(user != null ? user.getActive() : employee.getIsActive()); response.setCreatedAt(employee.getCreatedAt()); response.setUpdatedAt(employee.getUpdatedAt()); return response; @@ -165,6 +165,14 @@ public class EmployeeService { return (request.getFirstName().trim() + " " + request.getLastName().trim()).trim(); } + private String fullName(Employee employee) { + return (employee.getFirstName().trim() + " " + employee.getLastName().trim()).trim(); + } + + private String normalizeRole(String role) { + return role == null ? null : role.trim().toUpperCase(java.util.Locale.ROOT); + } + private String trimToNull(String value) { if (value == null) { return null; diff --git a/src/test/java/com/petshop/backend/security/JwtAuthenticationFilterTest.java b/src/test/java/com/petshop/backend/security/JwtAuthenticationFilterTest.java index fa8b429c..4d7ce01a 100644 --- a/src/test/java/com/petshop/backend/security/JwtAuthenticationFilterTest.java +++ b/src/test/java/com/petshop/backend/security/JwtAuthenticationFilterTest.java @@ -1,6 +1,7 @@ package com.petshop.backend.security; import com.petshop.backend.entity.User; +import com.petshop.backend.exception.ApiErrorResponder; import com.petshop.backend.repository.UserRepository; import jakarta.servlet.FilterChain; import org.junit.jupiter.api.AfterEach; @@ -42,7 +43,7 @@ class JwtAuthenticationFilterTest { User user = buildUser(); String token = jwtUtil.generateToken(user); AtomicBoolean chainCalled = new AtomicBoolean(false); - JwtAuthenticationFilter filter = new JwtAuthenticationFilter(jwtUtil, userRepositoryFor(user)); + JwtAuthenticationFilter filter = new JwtAuthenticationFilter(jwtUtil, userRepositoryFor(user), new ApiErrorResponder()); MockHttpServletRequest request = new MockHttpServletRequest(); request.addHeader("Authorization", "Bearer " + token); @@ -63,7 +64,7 @@ class JwtAuthenticationFilterTest { User user = buildUser(); user.setActive(false); String token = jwtUtil.generateToken(user); - JwtAuthenticationFilter filter = new JwtAuthenticationFilter(jwtUtil, userRepositoryFor(user)); + JwtAuthenticationFilter filter = new JwtAuthenticationFilter(jwtUtil, userRepositoryFor(user), new ApiErrorResponder()); MockHttpServletRequest request = new MockHttpServletRequest(); request.addHeader("Authorization", "Bearer " + token); @@ -73,6 +74,8 @@ class JwtAuthenticationFilterTest { }); assertEquals(401, response.getStatus()); + assertTrue(response.getContentAsString().contains("\"message\":\"User account is inactive\"")); + assertTrue(response.getContentAsString().contains("\"path\":\"\"")); assertNull(SecurityContextHolder.getContext().getAuthentication()); } @@ -81,7 +84,7 @@ class JwtAuthenticationFilterTest { User user = buildUser(); String token = jwtUtil.generateToken(user); user.setTokenVersion(4); - JwtAuthenticationFilter filter = new JwtAuthenticationFilter(jwtUtil, userRepositoryFor(user)); + JwtAuthenticationFilter filter = new JwtAuthenticationFilter(jwtUtil, userRepositoryFor(user), new ApiErrorResponder()); MockHttpServletRequest request = new MockHttpServletRequest(); request.addHeader("Authorization", "Bearer " + token); @@ -91,6 +94,7 @@ class JwtAuthenticationFilterTest { }); assertEquals(401, response.getStatus()); + assertTrue(response.getContentAsString().contains("\"message\":\"Invalid or expired token\"")); assertNull(SecurityContextHolder.getContext().getAuthentication()); }