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
This commit is contained in:
@@ -22,8 +22,10 @@ public class AdoptionController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
public ResponseEntity<Page<AdoptionResponse>> getAllAdoptions(Pageable pageable) {
|
public ResponseEntity<Page<AdoptionResponse>> getAllAdoptions(
|
||||||
return ResponseEntity.ok(adoptionService.getAllAdoptions(pageable));
|
@RequestParam(required = false) String q,
|
||||||
|
Pageable pageable) {
|
||||||
|
return ResponseEntity.ok(adoptionService.getAllAdoptions(q, pageable));
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/{id}")
|
@GetMapping("/{id}")
|
||||||
@@ -49,7 +51,7 @@ public class AdoptionController {
|
|||||||
return ResponseEntity.noContent().build();
|
return ResponseEntity.noContent().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/bulk-delete")
|
@DeleteMapping
|
||||||
public ResponseEntity<Void> bulkDeleteAdoptions(@Valid @RequestBody BulkDeleteRequest request) {
|
public ResponseEntity<Void> bulkDeleteAdoptions(@Valid @RequestBody BulkDeleteRequest request) {
|
||||||
adoptionService.bulkDeleteAdoptions(request);
|
adoptionService.bulkDeleteAdoptions(request);
|
||||||
return ResponseEntity.noContent().build();
|
return ResponseEntity.noContent().build();
|
||||||
|
|||||||
@@ -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<DashboardResponse> getDashboard(
|
||||||
|
@RequestParam(defaultValue = "30") int days,
|
||||||
|
@RequestParam(defaultValue = "10") int top) {
|
||||||
|
return ResponseEntity.ok(analyticsService.getDashboardData(days, top));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,6 +11,9 @@ import org.springframework.http.HttpStatus;
|
|||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v1/appointments")
|
@RequestMapping("/api/v1/appointments")
|
||||||
public class AppointmentController {
|
public class AppointmentController {
|
||||||
@@ -22,8 +25,10 @@ public class AppointmentController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
public ResponseEntity<Page<AppointmentResponse>> getAllAppointments(Pageable pageable) {
|
public ResponseEntity<Page<AppointmentResponse>> getAllAppointments(
|
||||||
return ResponseEntity.ok(appointmentService.getAllAppointments(pageable));
|
@RequestParam(required = false) String q,
|
||||||
|
Pageable pageable) {
|
||||||
|
return ResponseEntity.ok(appointmentService.getAllAppointments(q, pageable));
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/{id}")
|
@GetMapping("/{id}")
|
||||||
@@ -49,9 +54,18 @@ public class AppointmentController {
|
|||||||
return ResponseEntity.noContent().build();
|
return ResponseEntity.noContent().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/bulk-delete")
|
@DeleteMapping
|
||||||
public ResponseEntity<Void> bulkDeleteAppointments(@Valid @RequestBody BulkDeleteRequest request) {
|
public ResponseEntity<Void> bulkDeleteAppointments(@Valid @RequestBody BulkDeleteRequest request) {
|
||||||
appointmentService.bulkDeleteAppointments(request);
|
appointmentService.bulkDeleteAppointments(request);
|
||||||
return ResponseEntity.noContent().build();
|
return ResponseEntity.noContent().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/availability")
|
||||||
|
public ResponseEntity<List<String>> checkAvailability(
|
||||||
|
@RequestParam Long storeId,
|
||||||
|
@RequestParam Long serviceId,
|
||||||
|
@RequestParam String date) {
|
||||||
|
LocalDate appointmentDate = LocalDate.parse(date);
|
||||||
|
return ResponseEntity.ok(appointmentService.checkAvailability(storeId, serviceId, appointmentDate));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.petshop.backend.controller;
|
|||||||
|
|
||||||
import com.petshop.backend.dto.auth.LoginRequest;
|
import com.petshop.backend.dto.auth.LoginRequest;
|
||||||
import com.petshop.backend.dto.auth.LoginResponse;
|
import com.petshop.backend.dto.auth.LoginResponse;
|
||||||
|
import com.petshop.backend.dto.auth.RegisterRequest;
|
||||||
import com.petshop.backend.dto.auth.UserInfoResponse;
|
import com.petshop.backend.dto.auth.UserInfoResponse;
|
||||||
import com.petshop.backend.entity.User;
|
import com.petshop.backend.entity.User;
|
||||||
import com.petshop.backend.repository.UserRepository;
|
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.context.SecurityContextHolder;
|
||||||
import org.springframework.security.core.userdetails.UserDetails;
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@@ -28,11 +30,47 @@ public class AuthController {
|
|||||||
private final AuthenticationManager authenticationManager;
|
private final AuthenticationManager authenticationManager;
|
||||||
private final UserRepository userRepository;
|
private final UserRepository userRepository;
|
||||||
private final JwtUtil jwtUtil;
|
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.authenticationManager = authenticationManager;
|
||||||
this.userRepository = userRepository;
|
this.userRepository = userRepository;
|
||||||
this.jwtUtil = jwtUtil;
|
this.jwtUtil = jwtUtil;
|
||||||
|
this.passwordEncoder = passwordEncoder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/register")
|
||||||
|
public ResponseEntity<?> register(@Valid @RequestBody RegisterRequest request) {
|
||||||
|
if (userRepository.findByUsername(request.getEmail()).isPresent()) {
|
||||||
|
Map<String, String> 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")
|
@PostMapping("/login")
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ public class CategoryController {
|
|||||||
return ResponseEntity.noContent().build();
|
return ResponseEntity.noContent().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/bulk-delete")
|
@DeleteMapping
|
||||||
public ResponseEntity<Void> bulkDeleteCategories(@Valid @RequestBody BulkDeleteRequest request) {
|
public ResponseEntity<Void> bulkDeleteCategories(@Valid @RequestBody BulkDeleteRequest request) {
|
||||||
categoryService.bulkDeleteCategories(request);
|
categoryService.bulkDeleteCategories(request);
|
||||||
return ResponseEntity.noContent().build();
|
return ResponseEntity.noContent().build();
|
||||||
|
|||||||
@@ -24,8 +24,10 @@ public class InventoryController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
public ResponseEntity<Page<InventoryResponse>> getAllInventory(Pageable pageable) {
|
public ResponseEntity<Page<InventoryResponse>> getAllInventory(
|
||||||
return ResponseEntity.ok(inventoryService.getAllInventory(pageable));
|
@RequestParam(required = false) String q,
|
||||||
|
Pageable pageable) {
|
||||||
|
return ResponseEntity.ok(inventoryService.getAllInventory(q, pageable));
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/{id}")
|
@GetMapping("/{id}")
|
||||||
@@ -51,7 +53,7 @@ public class InventoryController {
|
|||||||
return ResponseEntity.noContent().build();
|
return ResponseEntity.noContent().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/bulk-delete")
|
@DeleteMapping
|
||||||
public ResponseEntity<Void> bulkDeleteInventory(@Valid @RequestBody BulkDeleteRequest request) {
|
public ResponseEntity<Void> bulkDeleteInventory(@Valid @RequestBody BulkDeleteRequest request) {
|
||||||
inventoryService.bulkDeleteInventory(request);
|
inventoryService.bulkDeleteInventory(request);
|
||||||
return ResponseEntity.noContent().build();
|
return ResponseEntity.noContent().build();
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ public class PetController {
|
|||||||
return ResponseEntity.noContent().build();
|
return ResponseEntity.noContent().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/bulk-delete")
|
@DeleteMapping
|
||||||
public ResponseEntity<Void> bulkDeletePets(@Valid @RequestBody BulkDeleteRequest request) {
|
public ResponseEntity<Void> bulkDeletePets(@Valid @RequestBody BulkDeleteRequest request) {
|
||||||
petService.bulkDeletePets(request);
|
petService.bulkDeletePets(request);
|
||||||
return ResponseEntity.noContent().build();
|
return ResponseEntity.noContent().build();
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ public class ProductController {
|
|||||||
return ResponseEntity.noContent().build();
|
return ResponseEntity.noContent().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/bulk-delete")
|
@DeleteMapping
|
||||||
public ResponseEntity<Void> bulkDeleteProducts(@Valid @RequestBody BulkDeleteRequest request) {
|
public ResponseEntity<Void> bulkDeleteProducts(@Valid @RequestBody BulkDeleteRequest request) {
|
||||||
productService.bulkDeleteProducts(request);
|
productService.bulkDeleteProducts(request);
|
||||||
return ResponseEntity.noContent().build();
|
return ResponseEntity.noContent().build();
|
||||||
|
|||||||
@@ -24,8 +24,10 @@ public class ProductSupplierController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
public ResponseEntity<Page<ProductSupplierResponse>> getAllProductSuppliers(Pageable pageable) {
|
public ResponseEntity<Page<ProductSupplierResponse>> getAllProductSuppliers(
|
||||||
return ResponseEntity.ok(productSupplierService.getAllProductSuppliers(pageable));
|
@RequestParam(required = false) String q,
|
||||||
|
Pageable pageable) {
|
||||||
|
return ResponseEntity.ok(productSupplierService.getAllProductSuppliers(q, pageable));
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/{productId}/{supplierId}")
|
@GetMapping("/{productId}/{supplierId}")
|
||||||
@@ -56,7 +58,7 @@ public class ProductSupplierController {
|
|||||||
return ResponseEntity.noContent().build();
|
return ResponseEntity.noContent().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/bulk-delete")
|
@DeleteMapping
|
||||||
public ResponseEntity<Void> bulkDeleteProductSuppliers(@Valid @RequestBody BulkDeleteProductSupplierRequest request) {
|
public ResponseEntity<Void> bulkDeleteProductSuppliers(@Valid @RequestBody BulkDeleteProductSupplierRequest request) {
|
||||||
productSupplierService.bulkDeleteProductSuppliers(request);
|
productSupplierService.bulkDeleteProductSuppliers(request);
|
||||||
return ResponseEntity.noContent().build();
|
return ResponseEntity.noContent().build();
|
||||||
|
|||||||
@@ -20,8 +20,10 @@ public class PurchaseOrderController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
public ResponseEntity<Page<PurchaseOrderResponse>> getAllPurchaseOrders(Pageable pageable) {
|
public ResponseEntity<Page<PurchaseOrderResponse>> getAllPurchaseOrders(
|
||||||
return ResponseEntity.ok(purchaseOrderService.getAllPurchaseOrders(pageable));
|
@RequestParam(required = false) String q,
|
||||||
|
Pageable pageable) {
|
||||||
|
return ResponseEntity.ok(purchaseOrderService.getAllPurchaseOrders(q, pageable));
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/{id}")
|
@GetMapping("/{id}")
|
||||||
|
|||||||
@@ -21,8 +21,10 @@ public class SaleController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
public ResponseEntity<Page<SaleResponse>> getAllSales(Pageable pageable) {
|
public ResponseEntity<Page<SaleResponse>> getAllSales(
|
||||||
return ResponseEntity.ok(saleService.getAllSales(pageable));
|
@RequestParam(required = false) String q,
|
||||||
|
Pageable pageable) {
|
||||||
|
return ResponseEntity.ok(saleService.getAllSales(q, pageable));
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/{id}")
|
@GetMapping("/{id}")
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ public class ServiceController {
|
|||||||
return ResponseEntity.noContent().build();
|
return ResponseEntity.noContent().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/bulk-delete")
|
@DeleteMapping
|
||||||
public ResponseEntity<Void> bulkDeleteServices(@Valid @RequestBody BulkDeleteRequest request) {
|
public ResponseEntity<Void> bulkDeleteServices(@Valid @RequestBody BulkDeleteRequest request) {
|
||||||
serviceService.bulkDeleteServices(request);
|
serviceService.bulkDeleteServices(request);
|
||||||
return ResponseEntity.noContent().build();
|
return ResponseEntity.noContent().build();
|
||||||
|
|||||||
@@ -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<Page<StoreResponse>> getAllStores(
|
||||||
|
@RequestParam(required = false) String q,
|
||||||
|
Pageable pageable) {
|
||||||
|
return ResponseEntity.ok(storeService.getAllStores(q, pageable));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -53,7 +53,7 @@ public class SupplierController {
|
|||||||
return ResponseEntity.noContent().build();
|
return ResponseEntity.noContent().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/bulk-delete")
|
@DeleteMapping
|
||||||
public ResponseEntity<Void> bulkDeleteSuppliers(@Valid @RequestBody BulkDeleteRequest request) {
|
public ResponseEntity<Void> bulkDeleteSuppliers(@Valid @RequestBody BulkDeleteRequest request) {
|
||||||
supplierService.bulkDeleteSuppliers(request);
|
supplierService.bulkDeleteSuppliers(request);
|
||||||
return ResponseEntity.noContent().build();
|
return ResponseEntity.noContent().build();
|
||||||
|
|||||||
@@ -24,8 +24,10 @@ public class UserController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
public ResponseEntity<Page<UserResponse>> getAllUsers(Pageable pageable) {
|
public ResponseEntity<Page<UserResponse>> getAllUsers(
|
||||||
return ResponseEntity.ok(userService.getAllUsers(pageable));
|
@RequestParam(required = false) String q,
|
||||||
|
Pageable pageable) {
|
||||||
|
return ResponseEntity.ok(userService.getAllUsers(q, pageable));
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/{id}")
|
@GetMapping("/{id}")
|
||||||
@@ -51,7 +53,7 @@ public class UserController {
|
|||||||
return ResponseEntity.noContent().build();
|
return ResponseEntity.noContent().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/bulk-delete")
|
@DeleteMapping
|
||||||
public ResponseEntity<Void> bulkDeleteUsers(@Valid @RequestBody BulkDeleteRequest request) {
|
public ResponseEntity<Void> bulkDeleteUsers(@Valid @RequestBody BulkDeleteRequest request) {
|
||||||
userService.bulkDeleteUsers(request);
|
userService.bulkDeleteUsers(request);
|
||||||
return ResponseEntity.noContent().build();
|
return ResponseEntity.noContent().build();
|
||||||
|
|||||||
@@ -74,9 +74,8 @@ public class DashboardResponse {
|
|||||||
", dailySales=" + dailySales +
|
", dailySales=" + dailySales +
|
||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
class SalesSummary {
|
public static class SalesSummary {
|
||||||
private BigDecimal totalRevenue;
|
private BigDecimal totalRevenue;
|
||||||
private Long totalSales;
|
private Long totalSales;
|
||||||
private BigDecimal totalRefunds;
|
private BigDecimal totalRefunds;
|
||||||
@@ -148,7 +147,7 @@ class SalesSummary {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class InventorySummary {
|
public static class InventorySummary {
|
||||||
private Long totalProducts;
|
private Long totalProducts;
|
||||||
private Long lowStockProducts;
|
private Long lowStockProducts;
|
||||||
private Long outOfStockProducts;
|
private Long outOfStockProducts;
|
||||||
@@ -209,7 +208,7 @@ class InventorySummary {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class TopProduct {
|
public static class TopProduct {
|
||||||
private Long productId;
|
private Long productId;
|
||||||
private String productName;
|
private String productName;
|
||||||
private Long quantitySold;
|
private Long quantitySold;
|
||||||
@@ -281,7 +280,7 @@ class TopProduct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DailySales {
|
public static class DailySales {
|
||||||
private String date;
|
private String date;
|
||||||
private BigDecimal revenue;
|
private BigDecimal revenue;
|
||||||
private Long salesCount;
|
private Long salesCount;
|
||||||
@@ -341,3 +340,4 @@ class DailySales {
|
|||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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]'" +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -43,7 +43,7 @@ public class User {
|
|||||||
private LocalDateTime updatedAt;
|
private LocalDateTime updatedAt;
|
||||||
|
|
||||||
public enum Role {
|
public enum Role {
|
||||||
STAFF, ADMIN
|
STAFF, ADMIN, CUSTOMER
|
||||||
}
|
}
|
||||||
|
|
||||||
public User() {
|
public User() {
|
||||||
|
|||||||
@@ -1,9 +1,18 @@
|
|||||||
package com.petshop.backend.repository;
|
package com.petshop.backend.repository;
|
||||||
|
|
||||||
import com.petshop.backend.entity.Adoption;
|
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.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
import org.springframework.data.repository.query.Param;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
public interface AdoptionRepository extends JpaRepository<Adoption, Long> {
|
public interface AdoptionRepository extends JpaRepository<Adoption, Long> {
|
||||||
|
|
||||||
|
@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<Adoption> searchAdoptions(@Param("q") String query, Pageable pageable);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package com.petshop.backend.repository;
|
package com.petshop.backend.repository;
|
||||||
|
|
||||||
import com.petshop.backend.entity.Appointment;
|
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.JpaRepository;
|
||||||
import org.springframework.data.jpa.repository.Query;
|
import org.springframework.data.jpa.repository.Query;
|
||||||
import org.springframework.data.repository.query.Param;
|
import org.springframework.data.repository.query.Param;
|
||||||
@@ -15,4 +17,13 @@ public interface AppointmentRepository extends JpaRepository<Appointment, Long>
|
|||||||
|
|
||||||
@Query("SELECT a FROM Appointment a WHERE a.appointmentDate = :date AND a.appointmentTime = :time")
|
@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);
|
List<Appointment> 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<Appointment> 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<Appointment> searchAppointments(@Param("q") String query, Pageable pageable);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package com.petshop.backend.repository;
|
package com.petshop.backend.repository;
|
||||||
|
|
||||||
import com.petshop.backend.entity.Inventory;
|
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.JpaRepository;
|
||||||
import org.springframework.data.jpa.repository.Query;
|
import org.springframework.data.jpa.repository.Query;
|
||||||
import org.springframework.data.repository.query.Param;
|
import org.springframework.data.repository.query.Param;
|
||||||
@@ -13,4 +15,9 @@ public interface InventoryRepository extends JpaRepository<Inventory, Long> {
|
|||||||
|
|
||||||
@Query("SELECT i FROM Inventory i WHERE i.product.id = :productId AND i.store.id = :storeId")
|
@Query("SELECT i FROM Inventory i WHERE i.product.id = :productId AND i.store.id = :storeId")
|
||||||
Optional<Inventory> findByProductIdAndStoreId(@Param("productId") Long productId, @Param("storeId") Long storeId);
|
Optional<Inventory> 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<Inventory> searchInventory(@Param("q") String query, Pageable pageable);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,18 @@
|
|||||||
package com.petshop.backend.repository;
|
package com.petshop.backend.repository;
|
||||||
|
|
||||||
import com.petshop.backend.entity.ProductSupplier;
|
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.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
import org.springframework.data.repository.query.Param;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
public interface ProductSupplierRepository extends JpaRepository<ProductSupplier, ProductSupplier.ProductSupplierId> {
|
public interface ProductSupplierRepository extends JpaRepository<ProductSupplier, ProductSupplier.ProductSupplierId> {
|
||||||
|
|
||||||
|
@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<ProductSupplier> searchProductSuppliers(@Param("q") String query, Pageable pageable);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,18 @@
|
|||||||
package com.petshop.backend.repository;
|
package com.petshop.backend.repository;
|
||||||
|
|
||||||
import com.petshop.backend.entity.PurchaseOrder;
|
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.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
import org.springframework.data.repository.query.Param;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
public interface PurchaseOrderRepository extends JpaRepository<PurchaseOrder, Long> {
|
public interface PurchaseOrderRepository extends JpaRepository<PurchaseOrder, Long> {
|
||||||
|
|
||||||
|
@Query("SELECT po FROM PurchaseOrder po WHERE " +
|
||||||
|
"LOWER(po.supplier.supplierName) LIKE LOWER(CONCAT('%', :q, '%')) OR " +
|
||||||
|
"LOWER(po.notes) LIKE LOWER(CONCAT('%', :q, '%'))")
|
||||||
|
Page<PurchaseOrder> searchPurchaseOrders(@Param("q") String query, Pageable pageable);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,19 @@
|
|||||||
package com.petshop.backend.repository;
|
package com.petshop.backend.repository;
|
||||||
|
|
||||||
import com.petshop.backend.entity.Sale;
|
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.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
import org.springframework.data.repository.query.Param;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
public interface SaleRepository extends JpaRepository<Sale, Long> {
|
public interface SaleRepository extends JpaRepository<Sale, Long> {
|
||||||
|
|
||||||
|
@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<Sale> searchSales(@Param("q") String query, Pageable pageable);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,18 @@
|
|||||||
package com.petshop.backend.repository;
|
package com.petshop.backend.repository;
|
||||||
|
|
||||||
import com.petshop.backend.entity.Store;
|
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.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
import org.springframework.data.repository.query.Param;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
public interface StoreRepository extends JpaRepository<Store, Long> {
|
public interface StoreRepository extends JpaRepository<Store, Long> {
|
||||||
|
|
||||||
|
@Query("SELECT s FROM Store s WHERE " +
|
||||||
|
"LOWER(s.storeName) LIKE LOWER(CONCAT('%', :q, '%')) OR " +
|
||||||
|
"LOWER(s.storeLocation) LIKE LOWER(CONCAT('%', :q, '%'))")
|
||||||
|
Page<Store> searchStores(@Param("q") String query, Pageable pageable);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
package com.petshop.backend.repository;
|
package com.petshop.backend.repository;
|
||||||
|
|
||||||
import com.petshop.backend.entity.User;
|
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.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
import org.springframework.data.repository.query.Param;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
@@ -10,4 +14,10 @@ import java.util.Optional;
|
|||||||
public interface UserRepository extends JpaRepository<User, Long> {
|
public interface UserRepository extends JpaRepository<User, Long> {
|
||||||
Optional<User> findByUsername(String username);
|
Optional<User> findByUsername(String username);
|
||||||
boolean existsByUsername(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<User> searchUsers(@Param("q") String query, Pageable pageable);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ public class SecurityConfig {
|
|||||||
http
|
http
|
||||||
.csrf(AbstractHttpConfigurer::disable)
|
.csrf(AbstractHttpConfigurer::disable)
|
||||||
.authorizeHttpRequests(auth -> auth
|
.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("/swagger-ui/**", "/v3/api-docs/**", "/swagger-ui.html").permitAll()
|
||||||
.requestMatchers(HttpMethod.GET, "/api/v1/dropdowns/suppliers").hasRole("ADMIN")
|
.requestMatchers(HttpMethod.GET, "/api/v1/dropdowns/suppliers").hasRole("ADMIN")
|
||||||
.requestMatchers("/api/v1/inventory/**").hasRole("ADMIN")
|
.requestMatchers("/api/v1/inventory/**").hasRole("ADMIN")
|
||||||
|
|||||||
@@ -28,8 +28,14 @@ public class AdoptionService {
|
|||||||
this.customerRepository = customerRepository;
|
this.customerRepository = customerRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Page<AdoptionResponse> getAllAdoptions(Pageable pageable) {
|
public Page<AdoptionResponse> getAllAdoptions(String query, Pageable pageable) {
|
||||||
return adoptionRepository.findAll(pageable).map(this::mapToResponse);
|
Page<Adoption> 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) {
|
public AdoptionResponse getAdoptionById(Long id) {
|
||||||
|
|||||||
141
src/main/java/com/petshop/backend/service/AnalyticsService.java
Normal file
141
src/main/java/com/petshop/backend/service/AnalyticsService.java
Normal file
@@ -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<Sale> sales = saleRepository.findAll().stream()
|
||||||
|
.filter(sale -> sale.getSaleDate().isAfter(startDate))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
List<Refund> refunds = refundRepository.findAll().stream()
|
||||||
|
.filter(refund -> refund.getRefundDate().isAfter(startDate))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
DashboardResponse.SalesSummary salesSummary = calculateSalesSummary(sales, refunds);
|
||||||
|
DashboardResponse.InventorySummary inventorySummary = calculateInventorySummary();
|
||||||
|
List<DashboardResponse.TopProduct> topProducts = calculateTopProducts(sales, top);
|
||||||
|
List<DashboardResponse.DailySales> dailySales = calculateDailySales(sales, days);
|
||||||
|
|
||||||
|
return new DashboardResponse(salesSummary, inventorySummary, topProducts, dailySales);
|
||||||
|
}
|
||||||
|
|
||||||
|
private DashboardResponse.SalesSummary calculateSalesSummary(List<Sale> sales, List<Refund> 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<Inventory> 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<DashboardResponse.TopProduct> calculateTopProducts(List<Sale> sales, int top) {
|
||||||
|
Map<Long, DashboardResponse.TopProduct> 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<DashboardResponse.DailySales> calculateDailySales(List<Sale> sales, int days) {
|
||||||
|
Map<LocalDate, DashboardResponse.DailySales> 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,6 +16,9 @@ import org.springframework.data.domain.Pageable;
|
|||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
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.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@@ -36,8 +39,14 @@ public class AppointmentService {
|
|||||||
this.petRepository = petRepository;
|
this.petRepository = petRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Page<AppointmentResponse> getAllAppointments(Pageable pageable) {
|
public Page<AppointmentResponse> getAllAppointments(String query, Pageable pageable) {
|
||||||
return appointmentRepository.findAll(pageable).map(this::mapToResponse);
|
Page<Appointment> 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) {
|
public AppointmentResponse getAppointmentById(Long id) {
|
||||||
@@ -107,6 +116,30 @@ public class AppointmentService {
|
|||||||
appointmentRepository.deleteAllById(request.getIds());
|
appointmentRepository.deleteAllById(request.getIds());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<String> 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<Appointment> existingAppointments = appointmentRepository.findByServiceAndDate(serviceId, date);
|
||||||
|
Set<LocalTime> bookedTimes = existingAppointments.stream()
|
||||||
|
.map(Appointment::getAppointmentTime)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
|
List<String> 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<Pet> fetchPets(List<Long> petIds) {
|
private Set<Pet> fetchPets(List<Long> petIds) {
|
||||||
Set<Pet> pets = new HashSet<>();
|
Set<Pet> pets = new HashSet<>();
|
||||||
for (Long petId : petIds) {
|
for (Long petId : petIds) {
|
||||||
|
|||||||
@@ -30,8 +30,14 @@ public class InventoryService {
|
|||||||
this.storeRepository = storeRepository;
|
this.storeRepository = storeRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Page<InventoryResponse> getAllInventory(Pageable pageable) {
|
public Page<InventoryResponse> getAllInventory(String query, Pageable pageable) {
|
||||||
return inventoryRepository.findAll(pageable).map(this::mapToResponse);
|
Page<Inventory> 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) {
|
public InventoryResponse getInventoryById(Long id) {
|
||||||
|
|||||||
@@ -28,8 +28,14 @@ public class ProductSupplierService {
|
|||||||
this.supplierRepository = supplierRepository;
|
this.supplierRepository = supplierRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Page<ProductSupplierResponse> getAllProductSuppliers(Pageable pageable) {
|
public Page<ProductSupplierResponse> getAllProductSuppliers(String query, Pageable pageable) {
|
||||||
return productSupplierRepository.findAll(pageable).map(this::mapToResponse);
|
Page<ProductSupplier> 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) {
|
public ProductSupplierResponse getProductSupplierById(Long productId, Long supplierId) {
|
||||||
|
|||||||
@@ -22,8 +22,14 @@ public class PurchaseOrderService {
|
|||||||
this.purchaseOrderRepository = purchaseOrderRepository;
|
this.purchaseOrderRepository = purchaseOrderRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Page<PurchaseOrderResponse> getAllPurchaseOrders(Pageable pageable) {
|
public Page<PurchaseOrderResponse> getAllPurchaseOrders(String query, Pageable pageable) {
|
||||||
return purchaseOrderRepository.findAll(pageable).map(this::mapToResponse);
|
Page<PurchaseOrder> 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) {
|
public PurchaseOrderResponse getPurchaseOrderById(Long id) {
|
||||||
|
|||||||
@@ -36,8 +36,14 @@ public class SaleService {
|
|||||||
this.userRepository = userRepository;
|
this.userRepository = userRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Page<SaleResponse> getAllSales(Pageable pageable) {
|
public Page<SaleResponse> getAllSales(String query, Pageable pageable) {
|
||||||
return saleRepository.findAll(pageable).map(this::mapToResponse);
|
Page<Sale> 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) {
|
public SaleResponse getSaleById(Long id) {
|
||||||
|
|||||||
37
src/main/java/com/petshop/backend/service/StoreService.java
Normal file
37
src/main/java/com/petshop/backend/service/StoreService.java
Normal file
@@ -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<StoreResponse> getAllStores(String query, Pageable pageable) {
|
||||||
|
Page<Store> 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()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,8 +23,14 @@ public class UserService {
|
|||||||
this.passwordEncoder = passwordEncoder;
|
this.passwordEncoder = passwordEncoder;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Page<UserResponse> getAllUsers(Pageable pageable) {
|
public Page<UserResponse> getAllUsers(String query, Pageable pageable) {
|
||||||
return userRepository.findAll(pageable).map(this::mapToResponse);
|
Page<User> 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) {
|
public UserResponse getUserById(Long id) {
|
||||||
|
|||||||
Reference in New Issue
Block a user