Backend correctness fixes
This commit is contained in:
@@ -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<String, String> error = new HashMap<>();
|
||||
error.put("message", "Invalid username or password");
|
||||
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(error);
|
||||
} catch (DisabledException e) {
|
||||
Map<String, String> error = new HashMap<>();
|
||||
error.put("message", e.getMessage());
|
||||
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ public class ChatController {
|
||||
}
|
||||
|
||||
@PostMapping("/conversations")
|
||||
@PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')")
|
||||
@PreAuthorize("hasRole('CUSTOMER')")
|
||||
public ResponseEntity<ConversationResponse> createConversation(@Valid @RequestBody ConversationRequest request) {
|
||||
User user = getCurrentUser();
|
||||
ConversationResponse response = chatService.createConversation(user.getId(), request);
|
||||
|
||||
@@ -46,6 +46,7 @@ public class DropdownController {
|
||||
}
|
||||
|
||||
@GetMapping("/customers")
|
||||
@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
|
||||
public ResponseEntity<List<DropdownOption>> getCustomers() {
|
||||
return ResponseEntity.ok(
|
||||
customerRepository.findAll().stream()
|
||||
|
||||
@@ -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 +
|
||||
|
||||
@@ -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<String> petNames, List<Long> 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<String> petNames, List<Long> 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 +
|
||||
|
||||
@@ -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<Pet> pets, LocalDateTime createdAt, LocalDateTime updatedAt) {
|
||||
public Appointment(Long appointmentId, Customer customer, StoreLocation store, Service service, LocalDate appointmentDate, LocalTime appointmentTime, String appointmentStatus, Set<Pet> 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 +
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -18,8 +18,8 @@ public interface AppointmentRepository extends JpaRepository<Appointment, Long>
|
||||
@Query("SELECT a FROM Appointment a WHERE a.appointmentDate = :date AND a.appointmentTime = :time")
|
||||
List<Appointment> 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<Appointment> 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<Appointment> 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 " +
|
||||
|
||||
@@ -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<EmployeeStore, EmployeeStore.EmployeeStoreId> {
|
||||
Optional<EmployeeStore> findByEmployeeEmployeeId(Long employeeId);
|
||||
}
|
||||
@@ -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<Sale, Long> {
|
||||
|
||||
@@ -16,4 +18,6 @@ public interface SaleRepository extends JpaRepository<Sale, Long> {
|
||||
"LOWER(s.employee.lastName) LIKE LOWER(CONCAT('%', :q, '%')) OR " +
|
||||
"LOWER(s.store.storeName) LIKE LOWER(CONCAT('%', :q, '%'))")
|
||||
Page<Sale> searchSales(@Param("q") String query, Pageable pageable);
|
||||
|
||||
List<Sale> findByOriginalSaleSaleId(Long originalSaleId);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<AppointmentResponse> 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<Pet> 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<Pet> 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<String> 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<Appointment> existingAppointments = appointmentRepository.findByServiceAndDate(serviceId, date);
|
||||
Set<LocalTime> bookedTimes = existingAppointments.stream()
|
||||
.map(Appointment::getAppointmentTime)
|
||||
.collect(Collectors.toSet());
|
||||
List<Appointment> existingAppointments = appointmentRepository.findByStoreAndDate(storeId, date);
|
||||
|
||||
List<String> 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<Appointment> 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<Appointment> 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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()));
|
||||
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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');
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
Reference in New Issue
Block a user