Fix backend security, validation, and API contracts
This commit is contained in:
@@ -22,6 +22,7 @@ public class SaleController {
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
|
||||
public ResponseEntity<Page<SaleResponse>> getAllSales(
|
||||
@RequestParam(required = false) String q,
|
||||
Pageable pageable) {
|
||||
@@ -29,6 +30,7 @@ public class SaleController {
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
|
||||
public ResponseEntity<SaleResponse> getSaleById(@PathVariable Long id) {
|
||||
return ResponseEntity.ok(saleService.getSaleById(id));
|
||||
}
|
||||
|
||||
@@ -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 +
|
||||
'}';
|
||||
|
||||
@@ -10,4 +10,5 @@ import java.util.List;
|
||||
public interface ConversationRepository extends JpaRepository<Conversation, Long> {
|
||||
List<Conversation> findByCustomerId(Long customerId);
|
||||
List<Conversation> findByStaffId(Long staffId);
|
||||
List<Conversation> findByStaffIdIsNull();
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -119,6 +119,7 @@ public class AdoptionService {
|
||||
adoption.getCustomer().getFirstName() + " " + adoption.getCustomer().getLastName(),
|
||||
adoption.getAdoptionDate(),
|
||||
adoption.getAdoptionStatus(),
|
||||
adoption.getPet().getPetPrice(),
|
||||
adoption.getCreatedAt(),
|
||||
adoption.getUpdatedAt()
|
||||
);
|
||||
|
||||
@@ -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<Pet> fetchPets(List<Long> petIds) {
|
||||
Set<Pet> pets = new HashSet<>();
|
||||
for (Long petId : petIds) {
|
||||
|
||||
@@ -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<Conversation> assignedToMe = conversationRepository.findByStaffId(userId);
|
||||
List<Conversation> 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<Message> 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<Message> messages = messageRepository.findByConversationIdOrderByTimestampAsc(conversationId);
|
||||
|
||||
@@ -84,32 +84,69 @@ public class SaleService {
|
||||
BigDecimal totalAmount = BigDecimal.ZERO;
|
||||
List<SaleItem> 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);
|
||||
|
||||
@@ -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:
|
||||
|
||||
250
src/main/resources/db/migration/V1__baseline_schema.sql
Normal file
250
src/main/resources/db/migration/V1__baseline_schema.sql
Normal file
@@ -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;
|
||||
Reference in New Issue
Block a user