Merge branch 'main' into AttachmentsToChat
This commit is contained in:
@@ -16,13 +16,11 @@ import retrofit2.http.Query;
|
||||
|
||||
public interface InventoryApi {
|
||||
|
||||
// GET /api/v1/inventory?q=...&page=...&size=...&category=...&storeId=...&sort=...
|
||||
@GET("api/v1/inventory")
|
||||
Call<PageResponse<InventoryDTO>> getAllInventory(
|
||||
@Query("page") int page,
|
||||
@Query("size") int size,
|
||||
@Query("q") String query,
|
||||
@Query("category") String category,
|
||||
@Query("storeId") Long storeId,
|
||||
@Query("sort") String sort);
|
||||
|
||||
|
||||
@@ -3,14 +3,10 @@ package com.example.petstoremobile.api;
|
||||
import com.example.petstoremobile.dtos.MessageDTO;
|
||||
import com.example.petstoremobile.dtos.SendMessageRequest;
|
||||
import java.util.List;
|
||||
import okhttp3.MultipartBody;
|
||||
import okhttp3.RequestBody;
|
||||
import retrofit2.Call;
|
||||
import retrofit2.http.Body;
|
||||
import retrofit2.http.GET;
|
||||
import retrofit2.http.Multipart;
|
||||
import retrofit2.http.POST;
|
||||
import retrofit2.http.Part;
|
||||
import retrofit2.http.Path;
|
||||
|
||||
//api calls to get and send messages
|
||||
@@ -21,12 +17,4 @@ public interface MessageApi {
|
||||
|
||||
@POST("api/v1/chat/conversations/{id}/messages")
|
||||
Call<MessageDTO> sendMessage(@Path("id") Long conversationId, @Body SendMessageRequest request);
|
||||
|
||||
@Multipart
|
||||
@POST("api/v1/chat/conversations/{id}/messages/attachment")
|
||||
Call<MessageDTO> sendMessageWithAttachment(
|
||||
@Path("id") Long conversationId,
|
||||
@Part("content") RequestBody content,
|
||||
@Part MultipartBody.Part file
|
||||
);
|
||||
}
|
||||
@@ -13,7 +13,7 @@ public interface PurchaseOrderApi {
|
||||
Call<PageResponse<PurchaseOrderDTO>> getAllPurchaseOrders(
|
||||
@Query("page") int page,
|
||||
@Query("size") int size,
|
||||
@Query("query") String query,
|
||||
@Query("q") String query,
|
||||
@Query("storeId") Long storeId,
|
||||
@Query("sort") String sort);
|
||||
|
||||
|
||||
@@ -5,10 +5,8 @@ import com.example.petstoremobile.dtos.SaleDTO;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.http.Body;
|
||||
import retrofit2.http.DELETE;
|
||||
import retrofit2.http.GET;
|
||||
import retrofit2.http.POST;
|
||||
import retrofit2.http.PUT;
|
||||
import retrofit2.http.Path;
|
||||
import retrofit2.http.Query;
|
||||
|
||||
@@ -24,10 +22,4 @@ public interface SaleApi {
|
||||
|
||||
@POST("api/v1/sales")
|
||||
Call<SaleDTO> createSale(@Body SaleDTO sale);
|
||||
|
||||
@PUT("api/v1/sales/{id}")
|
||||
Call<SaleDTO> updateSale(@Path("id") Long id, @Body SaleDTO sale);
|
||||
|
||||
@DELETE("api/v1/sales/{id}")
|
||||
Call<Void> deleteSale(@Path("id") Long id);
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.example.petstoremobile.api.auth;
|
||||
|
||||
import com.example.petstoremobile.dtos.AuthDTO;
|
||||
import com.example.petstoremobile.dtos.AvatarUploadResponse;
|
||||
import com.example.petstoremobile.dtos.UserDTO;
|
||||
|
||||
import java.util.Map;
|
||||
@@ -36,7 +37,7 @@ public interface AuthApi {
|
||||
//upload avatar endpoint
|
||||
@Multipart
|
||||
@POST("api/v1/auth/me/avatar")
|
||||
Call<UserDTO> uploadAvatar(@Part MultipartBody.Part avatar);
|
||||
Call<AvatarUploadResponse> uploadAvatar(@Part MultipartBody.Part avatar);
|
||||
|
||||
//delete avatar endpoint
|
||||
@DELETE("api/v1/auth/me/avatar")
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.example.petstoremobile.dtos;
|
||||
|
||||
public class AvatarUploadResponse {
|
||||
private String avatarUrl;
|
||||
private String message;
|
||||
|
||||
public AvatarUploadResponse() {
|
||||
}
|
||||
|
||||
public String getAvatarUrl() {
|
||||
return avatarUrl;
|
||||
}
|
||||
|
||||
public void setAvatarUrl(String avatarUrl) {
|
||||
this.avatarUrl = avatarUrl;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import android.provider.OpenableColumns;
|
||||
import android.util.Log;
|
||||
import android.view.*;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.widget.Toast;
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -28,21 +29,16 @@ import com.example.petstoremobile.dtos.SendMessageRequest;
|
||||
import com.example.petstoremobile.models.Chat;
|
||||
import com.example.petstoremobile.models.Message;
|
||||
import com.example.petstoremobile.services.ChatNotificationService;
|
||||
import com.example.petstoremobile.utils.FileUtils;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
import com.example.petstoremobile.viewmodels.ChatViewModel;
|
||||
import com.example.petstoremobile.websocket.StompChatManager;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.*;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
import dagger.hilt.android.AndroidEntryPoint;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.MultipartBody;
|
||||
import okhttp3.RequestBody;
|
||||
|
||||
@AndroidEntryPoint
|
||||
public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickListener, StompChatManager.MessageListener,
|
||||
@@ -361,26 +357,10 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
||||
binding.etMessage.setText("");
|
||||
removeAttachment();
|
||||
|
||||
try {
|
||||
File file = FileUtils.getFileFromUri(requireContext(), uri);
|
||||
if (file == null) return;
|
||||
|
||||
String mimeType = requireContext().getContentResolver().getType(uri);
|
||||
RequestBody requestFile = RequestBody.create(file, MediaType.parse(mimeType != null ? mimeType : "application/octet-stream"));
|
||||
MultipartBody.Part filePart = MultipartBody.Part.createFormData("file", file.getName(), requestFile);
|
||||
RequestBody contentPart = RequestBody.create(text, MediaType.parse("text/plain"));
|
||||
|
||||
viewModel.sendMessageWithAttachment(activeConversationId, contentPart, filePart).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
messageList.add(dtoToModel(resource.data));
|
||||
messageAdapter.notifyItemInserted(messageList.size() - 1);
|
||||
scrollToBottom();
|
||||
loadConversations();
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error sending message with attachment", e);
|
||||
if (!text.isEmpty()) {
|
||||
binding.etMessage.setText(text);
|
||||
}
|
||||
Toast.makeText(requireContext(), "File attachments are not supported", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -230,9 +230,7 @@ public class ProfileFragment extends Fragment {
|
||||
viewModel.uploadAvatar(body).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource == null) return;
|
||||
if (resource.status == Resource.Status.SUCCESS) {
|
||||
currentUser = resource.data;
|
||||
Toast.makeText(getContext(), "Avatar updated successfully", Toast.LENGTH_SHORT).show();
|
||||
// Reload image after successful upload
|
||||
loadProfileData();
|
||||
} else if (resource.status == Resource.Status.ERROR) {
|
||||
Toast.makeText(getContext(), "Upload failed: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||
|
||||
@@ -224,7 +224,7 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn
|
||||
}
|
||||
|
||||
//Load all inventory items from the backend using viewModel
|
||||
viewModel.getAllInventory(query, null, storeId, currentPage, PAGE_SIZE, "product.prodName").observe(getViewLifecycleOwner(), resource -> {
|
||||
viewModel.getAllInventory(query, storeId, currentPage, PAGE_SIZE, "product.prodName").observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource == null) return;
|
||||
|
||||
// Check the status to see if the resource is loaded and display the data
|
||||
|
||||
@@ -7,6 +7,7 @@ import androidx.lifecycle.MutableLiveData;
|
||||
import com.example.petstoremobile.api.auth.AuthApi;
|
||||
import com.example.petstoremobile.api.auth.TokenManager;
|
||||
import com.example.petstoremobile.dtos.AuthDTO;
|
||||
import com.example.petstoremobile.dtos.AvatarUploadResponse;
|
||||
import com.example.petstoremobile.dtos.UserDTO;
|
||||
import com.example.petstoremobile.utils.ErrorUtils;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
@@ -83,7 +84,7 @@ public class AuthRepository extends BaseRepository {
|
||||
/**
|
||||
* Uploads a multipart image to be used as the current user's avatar.
|
||||
*/
|
||||
public LiveData<Resource<UserDTO>> uploadAvatar(MultipartBody.Part avatar) {
|
||||
public LiveData<Resource<AvatarUploadResponse>> uploadAvatar(MultipartBody.Part avatar) {
|
||||
return executeCall(authApi.uploadAvatar(avatar));
|
||||
}
|
||||
|
||||
|
||||
@@ -17,9 +17,6 @@ import java.util.List;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import okhttp3.MultipartBody;
|
||||
import okhttp3.RequestBody;
|
||||
|
||||
/**
|
||||
* Repository for handling chat-related data operations.
|
||||
*/
|
||||
@@ -58,13 +55,6 @@ public class ChatRepository extends BaseRepository {
|
||||
return executeCall(messageApi.sendMessage(conversationId, request));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message with a file attachment to a conversation.
|
||||
*/
|
||||
public LiveData<Resource<MessageDTO>> sendMessageWithAttachment(Long conversationId, RequestBody content, MultipartBody.Part file) {
|
||||
return executeCall(messageApi.sendMessageWithAttachment(conversationId, content, file));
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a paginated list of customers.
|
||||
*/
|
||||
|
||||
@@ -24,8 +24,8 @@ public class InventoryRepository extends BaseRepository {
|
||||
/**
|
||||
* Retrieves a paginated list of inventory items from the API with optional search, category, storeId and sort.
|
||||
*/
|
||||
public LiveData<Resource<PageResponse<InventoryDTO>>> getAllInventory(String query, String category, Long storeId, int page, int size, String sort) {
|
||||
return executeCall(inventoryApi.getAllInventory(page, size, query, category, storeId, sort));
|
||||
public LiveData<Resource<PageResponse<InventoryDTO>>> getAllInventory(String query, Long storeId, int page, int size, String sort) {
|
||||
return executeCall(inventoryApi.getAllInventory(page, size, query, storeId, sort));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,6 +4,7 @@ import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import com.example.petstoremobile.dtos.AuthDTO;
|
||||
import com.example.petstoremobile.dtos.AvatarUploadResponse;
|
||||
import com.example.petstoremobile.dtos.UserDTO;
|
||||
import com.example.petstoremobile.repositories.AuthRepository;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
@@ -48,7 +49,7 @@ public class AuthViewModel extends ViewModel {
|
||||
/**
|
||||
* Uploads a new avatar image for the current user.
|
||||
*/
|
||||
public LiveData<Resource<UserDTO>> uploadAvatar(MultipartBody.Part avatar) {
|
||||
public LiveData<Resource<AvatarUploadResponse>> uploadAvatar(MultipartBody.Part avatar) {
|
||||
return repository.uploadAvatar(avatar);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,9 +16,6 @@ import java.util.List;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||
import okhttp3.MultipartBody;
|
||||
import okhttp3.RequestBody;
|
||||
|
||||
/**
|
||||
* ViewModel for managing chat-related UI state and data operations.
|
||||
*/
|
||||
@@ -52,13 +49,6 @@ public class ChatViewModel extends ViewModel {
|
||||
return repository.sendMessage(conversationId, request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message with a file attachment to a conversation.
|
||||
*/
|
||||
public LiveData<Resource<MessageDTO>> sendMessageWithAttachment(Long conversationId, RequestBody content, MultipartBody.Part file) {
|
||||
return repository.sendMessageWithAttachment(conversationId, content, file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a paginated list of customers.
|
||||
*/
|
||||
|
||||
@@ -35,8 +35,8 @@ public class InventoryViewModel extends ViewModel {
|
||||
/**
|
||||
* Retrieves a paginated list of inventory items, with optional filtering and sorting.
|
||||
*/
|
||||
public LiveData<Resource<PageResponse<InventoryDTO>>> getAllInventory(String query, String category, Long storeId, int page, int size, String sort) {
|
||||
return inventoryRepository.getAllInventory(query, category, storeId, page, size, sort);
|
||||
public LiveData<Resource<PageResponse<InventoryDTO>>> getAllInventory(String query, Long storeId, int page, int size, String sort) {
|
||||
return inventoryRepository.getAllInventory(query, storeId, page, size, sort);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -13,6 +13,7 @@ import com.petshop.backend.repository.UserRepository;
|
||||
import com.petshop.backend.security.JwtUtil;
|
||||
import com.petshop.backend.service.AvatarStorageService;
|
||||
import com.petshop.backend.util.AuthenticationHelper;
|
||||
import com.petshop.backend.util.PhoneUtils;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.HttpStatus;
|
||||
@@ -53,19 +54,23 @@ public class AuthController {
|
||||
|
||||
@PostMapping("/register")
|
||||
public ResponseEntity<?> register(@Valid @RequestBody RegisterRequest request) {
|
||||
if (userRepository.findByUsername(request.getUsername()).isPresent()) {
|
||||
String username = trimToNull(request.getUsername());
|
||||
String email = trimToNull(request.getEmail());
|
||||
NameParts nameParts = splitFullName(request.getFullName());
|
||||
String phone = normalizePhone(request.getPhone());
|
||||
|
||||
if (userRepository.findByUsername(username).isPresent()) {
|
||||
Map<String, String> error = new HashMap<>();
|
||||
error.put("message", "Username already exists");
|
||||
return ResponseEntity.status(HttpStatus.CONFLICT).body(error);
|
||||
}
|
||||
|
||||
if (userRepository.findByEmail(request.getEmail()).isPresent()) {
|
||||
if (userRepository.findByEmail(email).isPresent()) {
|
||||
Map<String, String> error = new HashMap<>();
|
||||
error.put("message", "Email already exists");
|
||||
return ResponseEntity.status(HttpStatus.CONFLICT).body(error);
|
||||
}
|
||||
|
||||
String phone = trimToNull(request.getPhone());
|
||||
if (phone != null && userRepository.findByPhone(phone).isPresent()) {
|
||||
Map<String, String> error = new HashMap<>();
|
||||
error.put("message", "Phone already exists");
|
||||
@@ -73,10 +78,12 @@ public class AuthController {
|
||||
}
|
||||
|
||||
User user = new User();
|
||||
user.setUsername(request.getUsername());
|
||||
user.setUsername(username);
|
||||
user.setPassword(passwordEncoder.encode(request.getPassword()));
|
||||
user.setEmail(request.getEmail());
|
||||
user.setFullName(request.getFullName());
|
||||
user.setEmail(email);
|
||||
user.setFirstName(nameParts.firstName());
|
||||
user.setLastName(nameParts.lastName());
|
||||
user.setFullName(nameParts.fullName());
|
||||
user.setPhone(phone);
|
||||
user.setRole(User.Role.CUSTOMER);
|
||||
user.setActive(true);
|
||||
@@ -143,31 +150,36 @@ public class AuthController {
|
||||
User user = getAuthenticatedUser();
|
||||
boolean invalidateToken = false;
|
||||
|
||||
if (request.getUsername() != null && !request.getUsername().equals(user.getUsername())) {
|
||||
if (userRepository.findByUsername(request.getUsername()).isPresent()) {
|
||||
String username = trimToNull(request.getUsername());
|
||||
if (username != null && !username.equals(user.getUsername())) {
|
||||
if (userRepository.findByUsername(username).isPresent()) {
|
||||
Map<String, String> error = new HashMap<>();
|
||||
error.put("message", "Username already exists");
|
||||
return ResponseEntity.status(HttpStatus.CONFLICT).body(error);
|
||||
}
|
||||
user.setUsername(request.getUsername());
|
||||
user.setUsername(username);
|
||||
invalidateToken = true;
|
||||
}
|
||||
|
||||
if (request.getEmail() != null && !request.getEmail().equals(user.getEmail())) {
|
||||
if (userRepository.findByEmail(request.getEmail()).isPresent()) {
|
||||
String email = trimToNull(request.getEmail());
|
||||
if (email != null && !email.equals(user.getEmail())) {
|
||||
if (userRepository.findByEmail(email).isPresent()) {
|
||||
Map<String, String> error = new HashMap<>();
|
||||
error.put("message", "Email already exists");
|
||||
return ResponseEntity.status(HttpStatus.CONFLICT).body(error);
|
||||
}
|
||||
user.setEmail(request.getEmail());
|
||||
user.setEmail(email);
|
||||
}
|
||||
|
||||
if (request.getFullName() != null) {
|
||||
user.setFullName(request.getFullName());
|
||||
NameParts nameParts = splitFullName(request.getFullName());
|
||||
user.setFirstName(nameParts.firstName());
|
||||
user.setLastName(nameParts.lastName());
|
||||
user.setFullName(nameParts.fullName());
|
||||
}
|
||||
|
||||
if (request.getPhone() != null) {
|
||||
String phone = trimToNull(request.getPhone());
|
||||
String phone = normalizePhone(request.getPhone());
|
||||
if (!java.util.Objects.equals(phone, user.getPhone())) {
|
||||
if (phone != null && userRepository.findByPhone(phone)
|
||||
.filter(existing -> !existing.getId().equals(user.getId()))
|
||||
@@ -196,11 +208,15 @@ public class AuthController {
|
||||
private UserInfoResponse toUserInfoResponse(User user) {
|
||||
StoreLocation primaryStore = user.getPrimaryStore();
|
||||
Long customerId = user.getRole() == User.Role.CUSTOMER ? user.getId() : null;
|
||||
String fullName = user.getFullName();
|
||||
if (fullName == null || fullName.isBlank()) {
|
||||
fullName = joinFullName(user.getFirstName(), user.getLastName());
|
||||
}
|
||||
return new UserInfoResponse(
|
||||
user.getId(),
|
||||
user.getUsername(),
|
||||
user.getEmail(),
|
||||
user.getFullName(),
|
||||
fullName,
|
||||
user.getPhone(),
|
||||
avatarStorageService.toOwnerAvatarUrl(user),
|
||||
user.getRole().name(),
|
||||
@@ -218,6 +234,36 @@ public class AuthController {
|
||||
return trimmed.isEmpty() ? null : trimmed;
|
||||
}
|
||||
|
||||
private String normalizePhone(String value) {
|
||||
return trimToNull(PhoneUtils.normalize(trimToNull(value)));
|
||||
}
|
||||
|
||||
private NameParts splitFullName(String value) {
|
||||
String normalized = trimToNull(value);
|
||||
if (normalized == null) {
|
||||
throw new IllegalArgumentException("Full name is required");
|
||||
}
|
||||
String[] parts = normalized.split("\\s+", 2);
|
||||
String firstName = parts[0];
|
||||
String lastName = parts.length > 1 ? parts[1] : "";
|
||||
return new NameParts(firstName, lastName, joinFullName(firstName, lastName));
|
||||
}
|
||||
|
||||
private String joinFullName(String firstName, String lastName) {
|
||||
String first = trimToNull(firstName);
|
||||
String last = trimToNull(lastName);
|
||||
if (first == null) {
|
||||
return last == null ? null : last;
|
||||
}
|
||||
if (last == null) {
|
||||
return first;
|
||||
}
|
||||
return first + " " + last;
|
||||
}
|
||||
|
||||
private record NameParts(String firstName, String lastName, String fullName) {
|
||||
}
|
||||
|
||||
@PostMapping("/me/avatar")
|
||||
public ResponseEntity<?> uploadAvatar(@RequestParam("avatar") MultipartFile file) {
|
||||
User user = getAuthenticatedUser();
|
||||
|
||||
@@ -153,6 +153,16 @@ public class DropdownController {
|
||||
);
|
||||
}
|
||||
|
||||
@GetMapping("/customers/{customerId}/pets")
|
||||
@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
|
||||
public ResponseEntity<List<DropdownOption>> getCustomerPets(@PathVariable Long customerId) {
|
||||
return ResponseEntity.ok(
|
||||
petRepository.findAllByOwner_IdOrderByPetNameAsc(customerId).stream()
|
||||
.map(p -> new DropdownOption(p.getPetId(), p.getPetName()))
|
||||
.collect(Collectors.toList())
|
||||
);
|
||||
}
|
||||
|
||||
@GetMapping("/suppliers")
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
public ResponseEntity<List<DropdownOption>> getSuppliers() {
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
package com.petshop.backend.controller;
|
||||
|
||||
import com.petshop.backend.dto.pet.MyPetRequest;
|
||||
import com.petshop.backend.dto.pet.MyPetResponse;
|
||||
import com.petshop.backend.entity.User;
|
||||
import com.petshop.backend.repository.UserRepository;
|
||||
import com.petshop.backend.service.PetService;
|
||||
import com.petshop.backend.util.AuthenticationHelper;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/my-pets")
|
||||
@PreAuthorize("isAuthenticated()")
|
||||
public class MyPetController {
|
||||
|
||||
private final PetService petService;
|
||||
private final UserRepository userRepository;
|
||||
|
||||
public MyPetController(PetService petService, UserRepository userRepository) {
|
||||
this.petService = petService;
|
||||
this.userRepository = userRepository;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public ResponseEntity<List<MyPetResponse>> getMyPets() {
|
||||
return ResponseEntity.ok(petService.getMyPets(currentUserId()));
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public ResponseEntity<MyPetResponse> createMyPet(@Valid @RequestBody MyPetRequest request) {
|
||||
return ResponseEntity.ok(petService.createMyPet(currentUserId(), request));
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
public ResponseEntity<MyPetResponse> updateMyPet(@PathVariable Long id, @Valid @RequestBody MyPetRequest request) {
|
||||
return ResponseEntity.ok(petService.updateMyPet(currentUserId(), id, request));
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
public ResponseEntity<Void> deleteMyPet(@PathVariable Long id) {
|
||||
petService.deleteMyPet(currentUserId(), id);
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
|
||||
@PostMapping("/{id}/image")
|
||||
public ResponseEntity<?> uploadMyPetImage(@PathVariable Long id, @RequestParam("image") MultipartFile image) {
|
||||
try {
|
||||
return ResponseEntity.ok(petService.uploadMyPetImage(currentUserId(), id, image));
|
||||
} catch (IllegalArgumentException ex) {
|
||||
return ResponseEntity.badRequest().body(Map.of("message", ex.getMessage()));
|
||||
} catch (IOException ex) {
|
||||
return ResponseEntity.badRequest().body(Map.of("message", "Failed to upload pet image: " + ex.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
private Long currentUserId() {
|
||||
User user = AuthenticationHelper.getAuthenticatedUser(userRepository);
|
||||
return user.getId();
|
||||
}
|
||||
}
|
||||
@@ -48,12 +48,8 @@ public class PetImageController {
|
||||
|
||||
@GetMapping("/{id}/image")
|
||||
public ResponseEntity<Resource> getPetImage(@PathVariable Long id) {
|
||||
try {
|
||||
PetService.ImagePayload payload = petService.loadPetImage(id, currentUserId(), currentUserRole());
|
||||
return ResponseEntity.ok().contentType(payload.mediaType()).body(payload.resource());
|
||||
} catch (PetService.ForbiddenImageAccessException ex) {
|
||||
return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
|
||||
}
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}/image")
|
||||
|
||||
@@ -15,9 +15,7 @@ import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/refunds")
|
||||
@@ -33,8 +31,7 @@ public class RefundController {
|
||||
|
||||
@PostMapping
|
||||
@PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')")
|
||||
public ResponseEntity<?> createRefund(@Valid @RequestBody RefundRequest request) {
|
||||
try {
|
||||
public ResponseEntity<RefundResponse> createRefund(@Valid @RequestBody RefundRequest request) {
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
String role = authentication.getAuthorities().stream()
|
||||
.findFirst()
|
||||
@@ -47,13 +44,7 @@ public class RefundController {
|
||||
customerId = user.getId();
|
||||
}
|
||||
|
||||
RefundResponse refund = refundService.createRefund(request, customerId);
|
||||
return ResponseEntity.status(HttpStatus.CREATED).body(refund);
|
||||
} catch (RuntimeException e) {
|
||||
Map<String, String> error = new HashMap<>();
|
||||
error.put("message", e.getMessage());
|
||||
return ResponseEntity.badRequest().body(error);
|
||||
}
|
||||
return ResponseEntity.status(HttpStatus.CREATED).body(refundService.createRefund(request, customerId));
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
@@ -77,8 +68,7 @@ public class RefundController {
|
||||
|
||||
@GetMapping("/{id}")
|
||||
@PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')")
|
||||
public ResponseEntity<?> getRefundById(@PathVariable Long id) {
|
||||
try {
|
||||
public ResponseEntity<RefundResponse> getRefundById(@PathVariable Long id) {
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
String role = authentication.getAuthorities().stream()
|
||||
.findFirst()
|
||||
@@ -91,40 +81,19 @@ public class RefundController {
|
||||
customerId = user.getId();
|
||||
}
|
||||
|
||||
RefundResponse refund = refundService.getRefundById(id, customerId);
|
||||
return ResponseEntity.ok(refund);
|
||||
} catch (RuntimeException e) {
|
||||
Map<String, String> error = new HashMap<>();
|
||||
error.put("message", e.getMessage());
|
||||
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
|
||||
}
|
||||
return ResponseEntity.ok(refundService.getRefundById(id, customerId));
|
||||
}
|
||||
|
||||
@PutMapping("/{id}")
|
||||
@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
|
||||
public ResponseEntity<?> updateRefund(@PathVariable Long id, @Valid @RequestBody RefundUpdateRequest request) {
|
||||
try {
|
||||
RefundResponse refund = refundService.updateRefundStatus(id, request.getStatus());
|
||||
return ResponseEntity.ok(refund);
|
||||
} catch (RuntimeException e) {
|
||||
Map<String, String> error = new HashMap<>();
|
||||
error.put("message", e.getMessage());
|
||||
return ResponseEntity.badRequest().body(error);
|
||||
}
|
||||
public ResponseEntity<RefundResponse> updateRefund(@PathVariable Long id, @Valid @RequestBody RefundUpdateRequest request) {
|
||||
return ResponseEntity.ok(refundService.updateRefundStatus(id, request.getStatus()));
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id}")
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
public ResponseEntity<?> deleteRefund(@PathVariable Long id) {
|
||||
try {
|
||||
public ResponseEntity<Void> deleteRefund(@PathVariable Long id) {
|
||||
refundService.deleteRefund(id);
|
||||
Map<String, String> response = new HashMap<>();
|
||||
response.put("message", "Refund deleted successfully");
|
||||
return ResponseEntity.ok(response);
|
||||
} catch (RuntimeException e) {
|
||||
Map<String, String> error = new HashMap<>();
|
||||
error.put("message", e.getMessage());
|
||||
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
|
||||
}
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.petshop.backend.dto.pet;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Size;
|
||||
|
||||
public class MyPetRequest {
|
||||
|
||||
@NotBlank(message = "Pet name is required")
|
||||
@Size(max = 50, message = "Pet name must not exceed 50 characters")
|
||||
private String petName;
|
||||
|
||||
@NotBlank(message = "Species is required")
|
||||
@Size(max = 50, message = "Species must not exceed 50 characters")
|
||||
private String species;
|
||||
|
||||
@Size(max = 50, message = "Breed must not exceed 50 characters")
|
||||
private String breed;
|
||||
|
||||
public String getPetName() {
|
||||
return petName;
|
||||
}
|
||||
|
||||
public void setPetName(String petName) {
|
||||
this.petName = petName;
|
||||
}
|
||||
|
||||
public String getSpecies() {
|
||||
return species;
|
||||
}
|
||||
|
||||
public void setSpecies(String species) {
|
||||
this.species = species;
|
||||
}
|
||||
|
||||
public String getBreed() {
|
||||
return breed;
|
||||
}
|
||||
|
||||
public void setBreed(String breed) {
|
||||
this.breed = breed;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.petshop.backend.dto.pet;
|
||||
|
||||
public class MyPetResponse {
|
||||
|
||||
private Long customerPetId;
|
||||
private String petName;
|
||||
private String species;
|
||||
private String breed;
|
||||
private String imageUrl;
|
||||
|
||||
public MyPetResponse() {
|
||||
}
|
||||
|
||||
public MyPetResponse(Long customerPetId, String petName, String species, String breed, String imageUrl) {
|
||||
this.customerPetId = customerPetId;
|
||||
this.petName = petName;
|
||||
this.species = species;
|
||||
this.breed = breed;
|
||||
this.imageUrl = imageUrl;
|
||||
}
|
||||
|
||||
public Long getCustomerPetId() {
|
||||
return customerPetId;
|
||||
}
|
||||
|
||||
public void setCustomerPetId(Long customerPetId) {
|
||||
this.customerPetId = customerPetId;
|
||||
}
|
||||
|
||||
public String getPetName() {
|
||||
return petName;
|
||||
}
|
||||
|
||||
public void setPetName(String petName) {
|
||||
this.petName = petName;
|
||||
}
|
||||
|
||||
public String getSpecies() {
|
||||
return species;
|
||||
}
|
||||
|
||||
public void setSpecies(String species) {
|
||||
this.species = species;
|
||||
}
|
||||
|
||||
public String getBreed() {
|
||||
return breed;
|
||||
}
|
||||
|
||||
public void setBreed(String breed) {
|
||||
this.breed = breed;
|
||||
}
|
||||
|
||||
public String getImageUrl() {
|
||||
return imageUrl;
|
||||
}
|
||||
|
||||
public void setImageUrl(String imageUrl) {
|
||||
this.imageUrl = imageUrl;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.petshop.backend.exception;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import com.fasterxml.jackson.databind.json.JsonMapper;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.http.HttpStatus;
|
||||
@@ -13,7 +14,10 @@ import java.time.LocalDateTime;
|
||||
@Component
|
||||
public class ApiErrorResponder {
|
||||
|
||||
private final ObjectMapper objectMapper = JsonMapper.builder().findAndAddModules().build();
|
||||
private final ObjectMapper objectMapper = JsonMapper.builder()
|
||||
.findAndAddModules()
|
||||
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
|
||||
.build();
|
||||
|
||||
public void write(HttpServletResponse response, HttpStatus status, String message, String details, String path) throws IOException {
|
||||
response.setStatus(status.value());
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
package com.petshop.backend.exception;
|
||||
|
||||
import com.petshop.backend.service.PetService;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
import org.springframework.data.core.PropertyReferenceException;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.validation.FieldError;
|
||||
import org.springframework.web.HttpRequestMethodNotSupportedException;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
import org.springframework.web.servlet.resource.NoResourceFoundException;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashMap;
|
||||
@@ -78,6 +82,26 @@ public class GlobalExceptionHandler {
|
||||
return buildErrorResponse(HttpStatus.valueOf(ex.getStatusCode().value()), message, ex, request);
|
||||
}
|
||||
|
||||
@ExceptionHandler(NoResourceFoundException.class)
|
||||
public ResponseEntity<ApiErrorResponse> handleNoResourceFound(NoResourceFoundException ex, HttpServletRequest request) {
|
||||
return buildErrorResponse(HttpStatus.NOT_FOUND, "Route not found", ex, request);
|
||||
}
|
||||
|
||||
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
|
||||
public ResponseEntity<ApiErrorResponse> handleMethodNotSupported(HttpRequestMethodNotSupportedException ex, HttpServletRequest request) {
|
||||
return buildErrorResponse(HttpStatus.METHOD_NOT_ALLOWED, ex.getMessage(), ex, request);
|
||||
}
|
||||
|
||||
@ExceptionHandler(PropertyReferenceException.class)
|
||||
public ResponseEntity<ApiErrorResponse> handleBadSortProperty(PropertyReferenceException ex, HttpServletRequest request) {
|
||||
return buildErrorResponse(HttpStatus.BAD_REQUEST, "Invalid sort field: " + ex.getPropertyName(), ex, request);
|
||||
}
|
||||
|
||||
@ExceptionHandler(PetService.ForbiddenImageAccessException.class)
|
||||
public ResponseEntity<ApiErrorResponse> handleForbiddenImageAccess(PetService.ForbiddenImageAccessException ex, HttpServletRequest request) {
|
||||
return buildErrorResponse(HttpStatus.FORBIDDEN, "Access to this pet image is not allowed", ex, request);
|
||||
}
|
||||
|
||||
@ExceptionHandler(Exception.class)
|
||||
public ResponseEntity<ApiErrorResponse> handleGenericException(Exception ex, HttpServletRequest request) {
|
||||
String message = ex.getMessage() == null || ex.getMessage().isBlank()
|
||||
|
||||
@@ -9,11 +9,14 @@ import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Repository
|
||||
public interface PetRepository extends JpaRepository<Pet, Long> {
|
||||
|
||||
List<Pet> findAllByPetStatusIgnoreCaseOrderByPetNameAsc(String petStatus);
|
||||
List<Pet> findAllByOwner_IdOrderByPetNameAsc(Long ownerId);
|
||||
Optional<Pet> findByIdAndOwner_Id(Long id, Long ownerId);
|
||||
|
||||
@Query("SELECT p FROM Pet p WHERE " +
|
||||
"(:q IS NULL OR LOWER(p.petName) LIKE LOWER(CONCAT('%', :q, '%')) OR LOWER(p.petSpecies) LIKE LOWER(CONCAT('%', :q, '%')) OR LOWER(COALESCE(p.petBreed, '')) LIKE LOWER(CONCAT('%', :q, '%'))) AND " +
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package com.petshop.backend.service;
|
||||
|
||||
import com.petshop.backend.dto.common.BulkDeleteRequest;
|
||||
import com.petshop.backend.dto.pet.MyPetRequest;
|
||||
import com.petshop.backend.dto.pet.MyPetResponse;
|
||||
import com.petshop.backend.dto.pet.PetRequest;
|
||||
import com.petshop.backend.dto.pet.PetResponse;
|
||||
import com.petshop.backend.entity.Adoption;
|
||||
@@ -25,6 +27,7 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
@Service
|
||||
@@ -82,6 +85,50 @@ public class PetService {
|
||||
return mapToResponse(pet);
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public List<MyPetResponse> getMyPets(Long ownerUserId) {
|
||||
return petRepository.findAllByOwner_IdOrderByPetNameAsc(ownerUserId).stream()
|
||||
.map(this::mapToMyPetResponse)
|
||||
.toList();
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public MyPetResponse createMyPet(Long ownerUserId, MyPetRequest request) {
|
||||
User owner = userRepository.findById(ownerUserId)
|
||||
.orElseThrow(() -> new ResourceNotFoundException("Customer not found with id: " + ownerUserId));
|
||||
Pet pet = new Pet();
|
||||
pet.setOwner(owner);
|
||||
pet.setStore(null);
|
||||
pet.setPetStatus("Owned");
|
||||
applyMyPetRequest(pet, request);
|
||||
return mapToMyPetResponse(petRepository.save(pet));
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public MyPetResponse updateMyPet(Long ownerUserId, Long petId, MyPetRequest request) {
|
||||
Pet pet = findOwnedPet(ownerUserId, petId);
|
||||
pet.setPetStatus("Owned");
|
||||
pet.setStore(null);
|
||||
applyMyPetRequest(pet, request);
|
||||
return mapToMyPetResponse(petRepository.save(pet));
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void deleteMyPet(Long ownerUserId, Long petId) {
|
||||
Pet pet = findOwnedPet(ownerUserId, petId);
|
||||
deleteStoredImageIfPresent(pet.getImageUrl());
|
||||
petRepository.delete(pet);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public MyPetResponse uploadMyPetImage(Long ownerUserId, Long petId, MultipartFile file) throws IOException {
|
||||
validateImageFile(file);
|
||||
Pet pet = findOwnedPet(ownerUserId, petId);
|
||||
deleteStoredImageIfPresent(pet.getImageUrl());
|
||||
pet.setImageUrl(catalogImageStorageService.storePetImage(file));
|
||||
return mapToMyPetResponse(petRepository.save(pet));
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public PetResponse createPet(PetRequest request) {
|
||||
Pet pet = new Pet();
|
||||
@@ -225,6 +272,11 @@ public class PetService {
|
||||
}
|
||||
}
|
||||
|
||||
private Pet findOwnedPet(Long ownerUserId, Long petId) {
|
||||
return petRepository.findByIdAndOwner_Id(petId, ownerUserId)
|
||||
.orElseThrow(() -> new ResourceNotFoundException("Pet not found with id: " + petId));
|
||||
}
|
||||
|
||||
private void deleteStoredImageIfPresent(String storedImagePath) {
|
||||
if (storedImagePath == null || storedImagePath.isBlank()) {
|
||||
return;
|
||||
@@ -276,6 +328,32 @@ public class PetService {
|
||||
);
|
||||
}
|
||||
|
||||
private MyPetResponse mapToMyPetResponse(Pet pet) {
|
||||
return new MyPetResponse(
|
||||
pet.getPetId(),
|
||||
pet.getPetName(),
|
||||
pet.getPetSpecies(),
|
||||
pet.getPetBreed(),
|
||||
pet.getImageUrl() != null && !pet.getImageUrl().isBlank() ? "/api/v1/pets/" + pet.getPetId() + "/image" : null
|
||||
);
|
||||
}
|
||||
|
||||
private void applyMyPetRequest(Pet pet, MyPetRequest request) {
|
||||
pet.setPetName(request.getPetName().trim());
|
||||
pet.setPetSpecies(request.getSpecies().trim());
|
||||
pet.setPetBreed(normalizeOptional(request.getBreed()));
|
||||
pet.setPetAge(null);
|
||||
pet.setPetPrice(null);
|
||||
}
|
||||
|
||||
private String normalizeOptional(String value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
String trimmed = value.trim();
|
||||
return trimmed.isEmpty() ? null : trimmed;
|
||||
}
|
||||
|
||||
private void applyOwnerAndStore(Pet pet, PetRequest request) {
|
||||
if ("owned".equalsIgnoreCase(request.getPetStatus())) {
|
||||
if (request.getCustomerId() != null) {
|
||||
|
||||
@@ -8,6 +8,8 @@ import com.petshop.backend.entity.Product;
|
||||
import com.petshop.backend.entity.Refund;
|
||||
import com.petshop.backend.entity.RefundItem;
|
||||
import com.petshop.backend.entity.Sale;
|
||||
import com.petshop.backend.exception.BusinessException;
|
||||
import com.petshop.backend.exception.ResourceNotFoundException;
|
||||
import com.petshop.backend.repository.ProductRepository;
|
||||
import com.petshop.backend.repository.RefundRepository;
|
||||
import com.petshop.backend.repository.SaleRepository;
|
||||
@@ -40,14 +42,14 @@ public class RefundService {
|
||||
@Transactional
|
||||
public RefundResponse createRefund(RefundRequest request, Long customerId) {
|
||||
Sale sale = saleRepository.findById(request.getSaleId())
|
||||
.orElseThrow(() -> new RuntimeException("Sale not found"));
|
||||
.orElseThrow(() -> new BusinessException("Sale not found"));
|
||||
|
||||
if (sale.getCustomer() == null) {
|
||||
throw new RuntimeException("Sale has no associated customer");
|
||||
throw new BusinessException("Sale has no associated customer");
|
||||
}
|
||||
|
||||
if (customerId != null && !sale.getCustomer().getId().equals(customerId)) {
|
||||
throw new RuntimeException("You can only create refunds for your own purchases");
|
||||
throw new BusinessException("You can only create refunds for your own purchases");
|
||||
}
|
||||
|
||||
Refund refund = new Refund();
|
||||
@@ -59,13 +61,13 @@ public class RefundService {
|
||||
BigDecimal totalAmount = BigDecimal.ZERO;
|
||||
for (var itemRequest : request.getItems()) {
|
||||
Product product = productRepository.findById(itemRequest.getProdId())
|
||||
.orElseThrow(() -> new RuntimeException("Product not found: " + itemRequest.getProdId()));
|
||||
.orElseThrow(() -> new BusinessException("Product not found: " + itemRequest.getProdId()));
|
||||
|
||||
BigDecimal unitPrice = sale.getItems().stream()
|
||||
.filter(item -> item.getProduct().getProdId().equals(itemRequest.getProdId()))
|
||||
.findFirst()
|
||||
.map(item -> item.getUnitPrice())
|
||||
.orElseThrow(() -> new RuntimeException("Product " + itemRequest.getProdId() + " was not in original sale"));
|
||||
.orElseThrow(() -> new BusinessException("Product " + itemRequest.getProdId() + " was not in original sale"));
|
||||
|
||||
RefundItem refundItem = new RefundItem();
|
||||
refundItem.setProduct(product);
|
||||
@@ -84,10 +86,10 @@ public class RefundService {
|
||||
@Transactional(readOnly = true)
|
||||
public RefundResponse getRefundById(Long id, Long customerId) {
|
||||
Refund refund = refundRepository.findById(id)
|
||||
.orElseThrow(() -> new RuntimeException("Refund not found"));
|
||||
.orElseThrow(() -> new ResourceNotFoundException("Refund not found"));
|
||||
|
||||
if (customerId != null && !refund.getCustomerId().equals(customerId)) {
|
||||
throw new RuntimeException("You can only view your own refunds");
|
||||
throw new ResourceNotFoundException("You can only view your own refunds");
|
||||
}
|
||||
|
||||
return toResponse(refund);
|
||||
@@ -111,18 +113,18 @@ public class RefundService {
|
||||
@Transactional
|
||||
public RefundResponse updateRefundStatus(Long id, String status) {
|
||||
Refund refund = refundRepository.findById(id)
|
||||
.orElseThrow(() -> new RuntimeException("Refund not found"));
|
||||
.orElseThrow(() -> new ResourceNotFoundException("Refund not found"));
|
||||
|
||||
Refund.RefundStatus newStatus;
|
||||
try {
|
||||
newStatus = Refund.RefundStatus.valueOf(status.toUpperCase());
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new RuntimeException("Invalid status: " + status);
|
||||
throw new BusinessException("Invalid status: " + status);
|
||||
}
|
||||
|
||||
if (refund.getStatus() == Refund.RefundStatus.PENDING && newStatus == Refund.RefundStatus.APPROVED) {
|
||||
Sale originalSale = saleRepository.findById(refund.getSaleId())
|
||||
.orElseThrow(() -> new RuntimeException("Original sale not found"));
|
||||
.orElseThrow(() -> new ResourceNotFoundException("Original sale not found"));
|
||||
|
||||
SaleRequest saleRequest = new SaleRequest();
|
||||
saleRequest.setStoreId(originalSale.getStore().getStoreId());
|
||||
@@ -150,7 +152,7 @@ public class RefundService {
|
||||
@Transactional
|
||||
public void deleteRefund(Long id) {
|
||||
if (!refundRepository.existsById(id)) {
|
||||
throw new RuntimeException("Refund not found");
|
||||
throw new ResourceNotFoundException("Refund not found");
|
||||
}
|
||||
refundRepository.deleteById(id);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ public class AdoptionRequest {
|
||||
private Long petId;
|
||||
private Long customerId;
|
||||
private Long employeeId;
|
||||
private Long sourceStoreId;
|
||||
private LocalDate adoptionDate;
|
||||
private String adoptionStatus;
|
||||
|
||||
@@ -36,6 +37,14 @@ public class AdoptionRequest {
|
||||
this.employeeId = employeeId;
|
||||
}
|
||||
|
||||
public Long getSourceStoreId() {
|
||||
return sourceStoreId;
|
||||
}
|
||||
|
||||
public void setSourceStoreId(Long sourceStoreId) {
|
||||
this.sourceStoreId = sourceStoreId;
|
||||
}
|
||||
|
||||
public LocalDate getAdoptionDate() {
|
||||
return adoptionDate;
|
||||
}
|
||||
|
||||
@@ -2,11 +2,8 @@ package org.example.petshopdesktop.api.dto.appointment;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalTime;
|
||||
import java.util.List;
|
||||
|
||||
public class AppointmentRequest {
|
||||
private List<Long> petIds;
|
||||
private List<Long> customerPetIds;
|
||||
private Long customerId;
|
||||
private Long storeId;
|
||||
private Long serviceId;
|
||||
@@ -14,26 +11,11 @@ public class AppointmentRequest {
|
||||
private LocalDate appointmentDate;
|
||||
private LocalTime appointmentTime;
|
||||
private String appointmentStatus;
|
||||
private Long petId;
|
||||
|
||||
public AppointmentRequest() {
|
||||
}
|
||||
|
||||
public List<Long> getPetIds() {
|
||||
return petIds;
|
||||
}
|
||||
|
||||
public void setPetIds(List<Long> petIds) {
|
||||
this.petIds = petIds;
|
||||
}
|
||||
|
||||
public List<Long> getCustomerPetIds() {
|
||||
return customerPetIds;
|
||||
}
|
||||
|
||||
public void setCustomerPetIds(List<Long> customerPetIds) {
|
||||
this.customerPetIds = customerPetIds;
|
||||
}
|
||||
|
||||
public Long getCustomerId() {
|
||||
return customerId;
|
||||
}
|
||||
@@ -89,4 +71,12 @@ public class AppointmentRequest {
|
||||
public void setAppointmentStatus(String appointmentStatus) {
|
||||
this.appointmentStatus = appointmentStatus;
|
||||
}
|
||||
|
||||
public Long getPetId() {
|
||||
return petId;
|
||||
}
|
||||
|
||||
public void setPetId(Long petId) {
|
||||
this.petId = petId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,16 +10,14 @@ public class AppointmentResponse {
|
||||
private Long storeId;
|
||||
private String storeName;
|
||||
private Long serviceId;
|
||||
private java.util.List<String> petNames;
|
||||
private java.util.List<Long> petIds;
|
||||
private java.util.List<String> customerPetNames;
|
||||
private java.util.List<Long> customerPetIds;
|
||||
private String serviceName;
|
||||
private Long employeeId;
|
||||
private String employeeName;
|
||||
private LocalDate appointmentDate;
|
||||
private LocalTime appointmentTime;
|
||||
private String appointmentStatus;
|
||||
private String petName;
|
||||
private Long petId;
|
||||
|
||||
public AppointmentResponse() {
|
||||
}
|
||||
@@ -72,36 +70,20 @@ public class AppointmentResponse {
|
||||
this.serviceId = serviceId;
|
||||
}
|
||||
|
||||
public java.util.List<String> getPetNames() {
|
||||
return petNames;
|
||||
public String getPetName() {
|
||||
return petName;
|
||||
}
|
||||
|
||||
public void setPetNames(java.util.List<String> petNames) {
|
||||
this.petNames = petNames;
|
||||
public void setPetName(String petName) {
|
||||
this.petName = petName;
|
||||
}
|
||||
|
||||
public java.util.List<Long> getPetIds() {
|
||||
return petIds;
|
||||
public Long getPetId() {
|
||||
return petId;
|
||||
}
|
||||
|
||||
public void setPetIds(java.util.List<Long> petIds) {
|
||||
this.petIds = petIds;
|
||||
}
|
||||
|
||||
public java.util.List<String> getCustomerPetNames() {
|
||||
return customerPetNames;
|
||||
}
|
||||
|
||||
public void setCustomerPetNames(java.util.List<String> customerPetNames) {
|
||||
this.customerPetNames = customerPetNames;
|
||||
}
|
||||
|
||||
public java.util.List<Long> getCustomerPetIds() {
|
||||
return customerPetIds;
|
||||
}
|
||||
|
||||
public void setCustomerPetIds(java.util.List<Long> customerPetIds) {
|
||||
this.customerPetIds = customerPetIds;
|
||||
public void setPetId(Long petId) {
|
||||
this.petId = petId;
|
||||
}
|
||||
|
||||
public String getServiceName() {
|
||||
|
||||
@@ -2,6 +2,10 @@ package org.example.petshopdesktop.api.dto.chat;
|
||||
|
||||
public class MessageRequest {
|
||||
private String content;
|
||||
private String attachmentUrl;
|
||||
private String attachmentName;
|
||||
private String attachmentMimeType;
|
||||
private Long attachmentSizeBytes;
|
||||
|
||||
public MessageRequest() {
|
||||
}
|
||||
@@ -17,4 +21,36 @@ public class MessageRequest {
|
||||
public void setContent(String content) {
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
public String getAttachmentUrl() {
|
||||
return attachmentUrl;
|
||||
}
|
||||
|
||||
public void setAttachmentUrl(String attachmentUrl) {
|
||||
this.attachmentUrl = attachmentUrl;
|
||||
}
|
||||
|
||||
public String getAttachmentName() {
|
||||
return attachmentName;
|
||||
}
|
||||
|
||||
public void setAttachmentName(String attachmentName) {
|
||||
this.attachmentName = attachmentName;
|
||||
}
|
||||
|
||||
public String getAttachmentMimeType() {
|
||||
return attachmentMimeType;
|
||||
}
|
||||
|
||||
public void setAttachmentMimeType(String attachmentMimeType) {
|
||||
this.attachmentMimeType = attachmentMimeType;
|
||||
}
|
||||
|
||||
public Long getAttachmentSizeBytes() {
|
||||
return attachmentSizeBytes;
|
||||
}
|
||||
|
||||
public void setAttachmentSizeBytes(Long attachmentSizeBytes) {
|
||||
this.attachmentSizeBytes = attachmentSizeBytes;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,10 @@ public class MessageResponse {
|
||||
private String content;
|
||||
private LocalDateTime timestamp;
|
||||
private Boolean isRead;
|
||||
private String attachmentUrl;
|
||||
private String attachmentName;
|
||||
private String attachmentMimeType;
|
||||
private Long attachmentSizeBytes;
|
||||
|
||||
public MessageResponse() {
|
||||
}
|
||||
@@ -60,4 +64,36 @@ public class MessageResponse {
|
||||
public void setIsRead(Boolean isRead) {
|
||||
this.isRead = isRead;
|
||||
}
|
||||
|
||||
public String getAttachmentUrl() {
|
||||
return attachmentUrl;
|
||||
}
|
||||
|
||||
public void setAttachmentUrl(String attachmentUrl) {
|
||||
this.attachmentUrl = attachmentUrl;
|
||||
}
|
||||
|
||||
public String getAttachmentName() {
|
||||
return attachmentName;
|
||||
}
|
||||
|
||||
public void setAttachmentName(String attachmentName) {
|
||||
this.attachmentName = attachmentName;
|
||||
}
|
||||
|
||||
public String getAttachmentMimeType() {
|
||||
return attachmentMimeType;
|
||||
}
|
||||
|
||||
public void setAttachmentMimeType(String attachmentMimeType) {
|
||||
this.attachmentMimeType = attachmentMimeType;
|
||||
}
|
||||
|
||||
public Long getAttachmentSizeBytes() {
|
||||
return attachmentSizeBytes;
|
||||
}
|
||||
|
||||
public void setAttachmentSizeBytes(Long attachmentSizeBytes) {
|
||||
this.attachmentSizeBytes = attachmentSizeBytes;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,9 +5,12 @@ public class EmployeeRequest {
|
||||
private String password;
|
||||
private String firstName;
|
||||
private String lastName;
|
||||
private String fullName;
|
||||
private String email;
|
||||
private String phone;
|
||||
private String role;
|
||||
private String staffRole;
|
||||
private Long primaryStoreId;
|
||||
private Boolean active;
|
||||
|
||||
public String getUsername() { return username; }
|
||||
@@ -18,12 +21,18 @@ public class EmployeeRequest {
|
||||
public void setFirstName(String firstName) { this.firstName = firstName; }
|
||||
public String getLastName() { return lastName; }
|
||||
public void setLastName(String lastName) { this.lastName = lastName; }
|
||||
public String getFullName() { return fullName; }
|
||||
public void setFullName(String fullName) { this.fullName = fullName; }
|
||||
public String getEmail() { return email; }
|
||||
public void setEmail(String email) { this.email = email; }
|
||||
public String getPhone() { return phone; }
|
||||
public void setPhone(String phone) { this.phone = phone; }
|
||||
public String getRole() { return role; }
|
||||
public void setRole(String role) { this.role = role; }
|
||||
public String getStaffRole() { return staffRole; }
|
||||
public void setStaffRole(String staffRole) { this.staffRole = staffRole; }
|
||||
public Long getPrimaryStoreId() { return primaryStoreId; }
|
||||
public void setPrimaryStoreId(Long primaryStoreId) { this.primaryStoreId = primaryStoreId; }
|
||||
public Boolean getActive() { return active; }
|
||||
public void setActive(Boolean active) { this.active = active; }
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.example.petshopdesktop.api.dto.employee;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
public class EmployeeResponse {
|
||||
private Long id;
|
||||
private Long employeeId;
|
||||
private Long userId;
|
||||
private String username;
|
||||
@@ -12,13 +13,17 @@ public class EmployeeResponse {
|
||||
private String email;
|
||||
private String phone;
|
||||
private String role;
|
||||
private String staffRole;
|
||||
private Long primaryStoreId;
|
||||
private Boolean active;
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
public Long getEmployeeId() { return employeeId; }
|
||||
public Long getId() { return id; }
|
||||
public void setId(Long id) { this.id = id; }
|
||||
public Long getEmployeeId() { return employeeId != null ? employeeId : id; }
|
||||
public void setEmployeeId(Long employeeId) { this.employeeId = employeeId; }
|
||||
public Long getUserId() { return userId; }
|
||||
public Long getUserId() { return userId != null ? userId : id; }
|
||||
public void setUserId(Long userId) { this.userId = userId; }
|
||||
public String getUsername() { return username; }
|
||||
public void setUsername(String username) { this.username = username; }
|
||||
@@ -34,6 +39,10 @@ public class EmployeeResponse {
|
||||
public void setPhone(String phone) { this.phone = phone; }
|
||||
public String getRole() { return role; }
|
||||
public void setRole(String role) { this.role = role; }
|
||||
public String getStaffRole() { return staffRole; }
|
||||
public void setStaffRole(String staffRole) { this.staffRole = staffRole; }
|
||||
public Long getPrimaryStoreId() { return primaryStoreId; }
|
||||
public void setPrimaryStoreId(Long primaryStoreId) { this.primaryStoreId = primaryStoreId; }
|
||||
public Boolean getActive() { return active; }
|
||||
public void setActive(Boolean active) { this.active = active; }
|
||||
public LocalDateTime getCreatedAt() { return createdAt; }
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.example.petshopdesktop.api.dto.inventory;
|
||||
public class InventoryRequest {
|
||||
private Long prodId;
|
||||
private Integer quantity;
|
||||
private Long storeId;
|
||||
|
||||
public InventoryRequest() {
|
||||
}
|
||||
@@ -22,4 +23,12 @@ public class InventoryRequest {
|
||||
public void setQuantity(Integer quantity) {
|
||||
this.quantity = quantity;
|
||||
}
|
||||
|
||||
public Long getStoreId() {
|
||||
return storeId;
|
||||
}
|
||||
|
||||
public void setStoreId(Long storeId) {
|
||||
this.storeId = storeId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ public class InventoryResponse {
|
||||
private Long prodId;
|
||||
private String productName;
|
||||
private String categoryName;
|
||||
private Long storeId;
|
||||
private String storeName;
|
||||
private Integer quantity;
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
@@ -46,6 +48,22 @@ public class InventoryResponse {
|
||||
this.categoryName = categoryName;
|
||||
}
|
||||
|
||||
public Long getStoreId() {
|
||||
return storeId;
|
||||
}
|
||||
|
||||
public void setStoreId(Long storeId) {
|
||||
this.storeId = storeId;
|
||||
}
|
||||
|
||||
public String getStoreName() {
|
||||
return storeName;
|
||||
}
|
||||
|
||||
public void setStoreName(String storeName) {
|
||||
this.storeName = storeName;
|
||||
}
|
||||
|
||||
public Integer getQuantity() {
|
||||
return quantity;
|
||||
}
|
||||
|
||||
@@ -231,18 +231,12 @@ public class AppointmentController {
|
||||
}
|
||||
|
||||
private AppointmentDTO mapToAppointmentDTO(AppointmentResponse response) {
|
||||
Long petId = response.getCustomerPetIds() != null && !response.getCustomerPetIds().isEmpty()
|
||||
? response.getCustomerPetIds().get(0)
|
||||
: response.getPetIds() != null && !response.getPetIds().isEmpty() ? response.getPetIds().get(0) : null;
|
||||
String petName = response.getCustomerPetNames() != null && !response.getCustomerPetNames().isEmpty()
|
||||
? String.join(", ", response.getCustomerPetNames())
|
||||
: String.join(", ", response.getPetNames());
|
||||
return new AppointmentDTO(
|
||||
response.getAppointmentId().intValue(),
|
||||
response.getCustomerId() != null ? response.getCustomerId().intValue() : 0,
|
||||
response.getCustomerName(),
|
||||
petId != null ? petId.intValue() : 0,
|
||||
petName,
|
||||
response.getPetId() != null ? response.getPetId().intValue() : 0,
|
||||
response.getPetName(),
|
||||
response.getServiceId() != null ? response.getServiceId().intValue() : 0,
|
||||
response.getServiceName(),
|
||||
response.getEmployeeId() != null ? response.getEmployeeId().intValue() : 0,
|
||||
|
||||
@@ -123,6 +123,7 @@ public class ChatController {
|
||||
|
||||
@FXML
|
||||
void btnSendClicked() {
|
||||
try {
|
||||
if (selectedConversation == null) {
|
||||
lblChatStatus.setText("Select a conversation");
|
||||
return;
|
||||
@@ -138,6 +139,13 @@ public class ChatController {
|
||||
if (!sent) {
|
||||
sendMessageFallback(selectedConversation.getId(), content);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
ActivityLogger.getInstance().logException(
|
||||
"ChatController.btnSendClicked",
|
||||
e,
|
||||
"Sending chat message");
|
||||
lblChatStatus.setText("Chat send failed");
|
||||
}
|
||||
}
|
||||
|
||||
private void loadCustomers() {
|
||||
@@ -223,17 +231,31 @@ public class ChatController {
|
||||
private void renderMessages(List<MessageResponse> messages) {
|
||||
vbMessages.getChildren().clear();
|
||||
for (MessageResponse message : messages) {
|
||||
try {
|
||||
vbMessages.getChildren().add(createMessageBubble(message));
|
||||
} catch (Exception e) {
|
||||
ActivityLogger.getInstance().logException(
|
||||
"ChatController.renderMessages",
|
||||
e,
|
||||
"Rendering chat message");
|
||||
}
|
||||
}
|
||||
scrollMessagesToBottom();
|
||||
}
|
||||
|
||||
private void appendMessageIfSelected(MessageResponse message) {
|
||||
try {
|
||||
upsertConversationForMessage(message);
|
||||
if (selectedConversation != null && selectedConversation.getId().equals(message.getConversationId())) {
|
||||
vbMessages.getChildren().add(createMessageBubble(message));
|
||||
scrollMessagesToBottom();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
ActivityLogger.getInstance().logException(
|
||||
"ChatController.appendMessageIfSelected",
|
||||
e,
|
||||
"Appending chat message");
|
||||
}
|
||||
}
|
||||
|
||||
private void upsertConversation(ConversationResponse conversation) {
|
||||
@@ -284,15 +306,34 @@ public class ChatController {
|
||||
Label author = new Label(resolveAuthorLabel(message));
|
||||
author.setStyle("-fx-font-weight: bold; -fx-text-fill: " + (mine ? "#ffffff" : "#1f2937") + ";");
|
||||
|
||||
Label content = new Label(message.getContent());
|
||||
content.setWrapText(true);
|
||||
content.setStyle("-fx-text-fill: " + (mine ? "#ffffff" : "#1f2937") + ";");
|
||||
|
||||
String timestampText = message.getTimestamp() == null ? "" : TIME_FORMATTER.format(message.getTimestamp());
|
||||
Label timestamp = new Label(timestampText);
|
||||
timestamp.setStyle("-fx-text-fill: " + (mine ? "#dbeafe" : "#94a3b8") + "; -fx-font-size: 11px;");
|
||||
|
||||
VBox bubble = new VBox(4, author, content, timestamp);
|
||||
VBox bubble = new VBox(4, author);
|
||||
String contentText = message.getContent() == null ? "" : message.getContent();
|
||||
if (!contentText.isBlank()) {
|
||||
Label content = new Label(contentText);
|
||||
content.setWrapText(true);
|
||||
content.setStyle("-fx-text-fill: " + (mine ? "#ffffff" : "#1f2937") + ";");
|
||||
bubble.getChildren().add(content);
|
||||
}
|
||||
|
||||
if (message.getAttachmentUrl() != null && !message.getAttachmentUrl().isBlank()) {
|
||||
String attachmentLabel = message.getAttachmentName();
|
||||
if (attachmentLabel == null || attachmentLabel.isBlank()) {
|
||||
attachmentLabel = "Attachment";
|
||||
}
|
||||
if (message.getAttachmentSizeBytes() != null && message.getAttachmentSizeBytes() > 0) {
|
||||
attachmentLabel = attachmentLabel + " (" + formatSize(message.getAttachmentSizeBytes()) + ")";
|
||||
}
|
||||
Label attachment = new Label(attachmentLabel);
|
||||
attachment.setWrapText(true);
|
||||
attachment.setStyle("-fx-text-fill: " + (mine ? "#dbeafe" : "#0f766e") + "; -fx-underline: true;");
|
||||
bubble.getChildren().add(attachment);
|
||||
}
|
||||
|
||||
bubble.getChildren().add(timestamp);
|
||||
bubble.setMaxWidth(420);
|
||||
bubble.setStyle(mine
|
||||
? "-fx-background-color: #0f766e; -fx-background-radius: 14; -fx-padding: 12;"
|
||||
@@ -347,4 +388,17 @@ public class ChatController {
|
||||
private void scrollMessagesToBottom() {
|
||||
Platform.runLater(() -> spMessages.setVvalue(1.0));
|
||||
}
|
||||
private String formatSize(Long bytes) {
|
||||
if (bytes == null || bytes <= 0) {
|
||||
return "";
|
||||
}
|
||||
double size = bytes;
|
||||
String[] units = {"B", "KB", "MB", "GB"};
|
||||
int unitIndex = 0;
|
||||
while (size >= 1024 && unitIndex < units.length - 1) {
|
||||
size = size / 1024;
|
||||
unitIndex++;
|
||||
}
|
||||
return unitIndex == 0 ? String.format("%.0f %s", size, units[unitIndex]) : String.format("%.1f %s", size, units[unitIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,8 +235,8 @@ public class InventoryController {
|
||||
response.getProdId() != null ? response.getProdId().intValue() : 0,
|
||||
response.getProductName(),
|
||||
response.getCategoryName() != null ? response.getCategoryName() : "",
|
||||
0,
|
||||
"N/A",
|
||||
response.getStoreId() != null ? response.getStoreId().intValue() : 0,
|
||||
response.getStoreName() != null ? response.getStoreName() : "",
|
||||
response.getQuantity() != null ? response.getQuantity() : 0,
|
||||
0
|
||||
);
|
||||
|
||||
@@ -186,10 +186,14 @@ public class StaffAccountsController {
|
||||
long userId = employee.getUserId() != null ? employee.getUserId() : 0L;
|
||||
long employeeId = employee.getEmployeeId() != null ? employee.getEmployeeId() : 0L;
|
||||
String username = employee.getUsername();
|
||||
String firstName = employee.getFirstName() != null ? employee.getFirstName() : "";
|
||||
String lastName = employee.getLastName() != null ? employee.getLastName() : "";
|
||||
if (firstName.isBlank() && lastName.isBlank()) {
|
||||
String fullName = employee.getFullName() != null ? employee.getFullName() : "";
|
||||
String[] names = splitFullName(fullName);
|
||||
String firstName = names[0];
|
||||
String lastName = names[1];
|
||||
firstName = names[0];
|
||||
lastName = names[1];
|
||||
}
|
||||
String email = employee.getEmail() != null ? employee.getEmail() : "";
|
||||
String phone = employee.getPhone() != null ? employee.getPhone() : "";
|
||||
String role = employee.getRole() != null ? employee.getRole() : "STAFF";
|
||||
|
||||
@@ -190,10 +190,16 @@ public class AdoptionDialogController {
|
||||
|
||||
if (errorMsg.isEmpty()) {
|
||||
try {
|
||||
Long storeId = UserSession.getInstance().getStoreId();
|
||||
if (storeId == null || storeId <= 0) {
|
||||
throw new IllegalStateException("Store is not set for this account");
|
||||
}
|
||||
|
||||
AdoptionRequest request = new AdoptionRequest();
|
||||
request.setPetId(cbPet.getSelectionModel().getSelectedItem().getId());
|
||||
request.setCustomerId(cbCustomer.getSelectionModel().getSelectedItem().getId());
|
||||
request.setEmployeeId(cbEmployee.getSelectionModel().getSelectedItem().getId());
|
||||
request.setSourceStoreId(storeId);
|
||||
request.setAdoptionDate(dpAdoptionDate.getValue());
|
||||
request.setAdoptionStatus(cbAdoptionStatus.getValue());
|
||||
|
||||
|
||||
@@ -21,7 +21,6 @@ import org.example.petshopdesktop.util.ActivityLogger;
|
||||
import java.time.LocalTime;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.Collections;
|
||||
import java.util.Objects;
|
||||
|
||||
public class AppointmentDialogController {
|
||||
@@ -215,7 +214,7 @@ public class AppointmentDialogController {
|
||||
}
|
||||
|
||||
AppointmentRequest request = new AppointmentRequest();
|
||||
request.setCustomerPetIds(Collections.singletonList(cbPet.getValue().getId()));
|
||||
request.setPetId(cbPet.getValue().getId());
|
||||
request.setCustomerId(cbCustomer.getValue().getId());
|
||||
request.setStoreId(storeId);
|
||||
request.setServiceId(cbService.getValue().getId());
|
||||
|
||||
@@ -20,6 +20,7 @@ import org.example.petshopdesktop.api.dto.inventory.InventoryResponse;
|
||||
import org.example.petshopdesktop.api.dto.product.ProductResponse;
|
||||
import org.example.petshopdesktop.api.endpoints.InventoryApi;
|
||||
import org.example.petshopdesktop.api.endpoints.ProductApi;
|
||||
import org.example.petshopdesktop.auth.UserSession;
|
||||
import org.example.petshopdesktop.models.Product;
|
||||
import org.example.petshopdesktop.util.ActivityLogger;
|
||||
|
||||
@@ -127,6 +128,10 @@ public class InventoryDialogController {
|
||||
try {
|
||||
InventoryRequest request = new InventoryRequest();
|
||||
Product selectedProduct = cbProduct.getSelectionModel().getSelectedItem();
|
||||
Long storeId = UserSession.getInstance().getStoreId();
|
||||
if (storeId == null || storeId <= 0) {
|
||||
throw new IllegalStateException("Store is not set for this account");
|
||||
}
|
||||
request.setProdId((long) selectedProduct.getProdId());
|
||||
int quantity;
|
||||
try {
|
||||
@@ -135,6 +140,7 @@ public class InventoryDialogController {
|
||||
throw new IllegalArgumentException("Invalid quantity format");
|
||||
}
|
||||
request.setQuantity(quantity);
|
||||
request.setStoreId(storeId);
|
||||
|
||||
if (mode.equals("Add")) {
|
||||
InventoryApi.getInstance().createInventory(request);
|
||||
|
||||
@@ -11,6 +11,7 @@ import javafx.stage.Stage;
|
||||
import org.example.petshopdesktop.Validator;
|
||||
import org.example.petshopdesktop.api.dto.employee.EmployeeRequest;
|
||||
import org.example.petshopdesktop.api.endpoints.EmployeeApi;
|
||||
import org.example.petshopdesktop.auth.UserSession;
|
||||
import org.example.petshopdesktop.models.StaffAccount;
|
||||
import org.example.petshopdesktop.util.ActivityLogger;
|
||||
|
||||
@@ -104,14 +105,18 @@ public class StaffEditDialogController {
|
||||
|
||||
new Thread(() -> {
|
||||
try {
|
||||
Long storeId = UserSession.getInstance().getStoreId();
|
||||
EmployeeRequest request = new EmployeeRequest();
|
||||
request.setUsername(username);
|
||||
request.setPassword(password.isEmpty() ? null : password);
|
||||
request.setFirstName(firstName);
|
||||
request.setLastName(lastName);
|
||||
request.setFullName(firstName + " " + lastName);
|
||||
request.setEmail(email);
|
||||
request.setPhone(phone);
|
||||
request.setRole(staffAccount.getRole());
|
||||
request.setStaffRole("Staff");
|
||||
request.setPrimaryStoreId(storeId);
|
||||
request.setActive(staffAccount.isActive());
|
||||
|
||||
EmployeeApi.getInstance().updateEmployee(staffAccount.getEmployeeId(), request);
|
||||
|
||||
@@ -11,6 +11,7 @@ import javafx.scene.control.TextField;
|
||||
import javafx.stage.Stage;
|
||||
import org.example.petshopdesktop.api.dto.employee.EmployeeRequest;
|
||||
import org.example.petshopdesktop.api.endpoints.EmployeeApi;
|
||||
import org.example.petshopdesktop.auth.UserSession;
|
||||
import org.example.petshopdesktop.Validator;
|
||||
import org.example.petshopdesktop.util.ActivityLogger;
|
||||
|
||||
@@ -89,14 +90,18 @@ public class StaffRegisterDialogController {
|
||||
|
||||
new Thread(() -> {
|
||||
try {
|
||||
Long storeId = UserSession.getInstance().getStoreId();
|
||||
EmployeeRequest request = new EmployeeRequest();
|
||||
request.setUsername(username);
|
||||
request.setPassword(password);
|
||||
request.setFirstName(firstName);
|
||||
request.setLastName(lastName);
|
||||
request.setFullName(firstName + " " + lastName);
|
||||
request.setEmail(email);
|
||||
request.setPhone(phone);
|
||||
request.setRole("STAFF");
|
||||
request.setStaffRole("Staff");
|
||||
request.setPrimaryStoreId(storeId);
|
||||
request.setActive(true);
|
||||
|
||||
EmployeeApi.getInstance().createEmployee(request);
|
||||
|
||||
114
web/package-lock.json
generated
114
web/package-lock.json
generated
@@ -8,7 +8,7 @@
|
||||
"name": "threaded-pets",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"next": "16.1.6",
|
||||
"next": "^16.2.2",
|
||||
"react": "19.2.3",
|
||||
"react-dom": "19.2.3"
|
||||
},
|
||||
@@ -1032,9 +1032,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/env": {
|
||||
"version": "16.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@next/env/-/env-16.1.6.tgz",
|
||||
"integrity": "sha512-N1ySLuZjnAtN3kFnwhAwPvZah8RJxKasD7x1f8shFqhncnWZn4JMfg37diLNuoHsLAlrDfM3g4mawVdtAG8XLQ==",
|
||||
"version": "16.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@next/env/-/env-16.2.2.tgz",
|
||||
"integrity": "sha512-LqSGz5+xGk9EL/iBDr2yo/CgNQV6cFsNhRR2xhSXYh7B/hb4nePCxlmDvGEKG30NMHDFf0raqSyOZiQrO7BkHQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@next/eslint-plugin-next": {
|
||||
@@ -1048,9 +1048,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-darwin-arm64": {
|
||||
"version": "16.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.1.6.tgz",
|
||||
"integrity": "sha512-wTzYulosJr/6nFnqGW7FrG3jfUUlEf8UjGA0/pyypJl42ExdVgC6xJgcXQ+V8QFn6niSG2Pb8+MIG1mZr2vczw==",
|
||||
"version": "16.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.2.2.tgz",
|
||||
"integrity": "sha512-B92G3ulrwmkDSEJEp9+XzGLex5wC1knrmCSIylyVeiAtCIfvEJYiN3v5kXPlYt5R4RFlsfO/v++aKV63Acrugg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -1064,9 +1064,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-darwin-x64": {
|
||||
"version": "16.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.1.6.tgz",
|
||||
"integrity": "sha512-BLFPYPDO+MNJsiDWbeVzqvYd4NyuRrEYVB5k2N3JfWncuHAy2IVwMAOlVQDFjj+krkWzhY2apvmekMkfQR0CUQ==",
|
||||
"version": "16.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.2.2.tgz",
|
||||
"integrity": "sha512-7ZwSgNKJNQiwW0CKhNm9B1WS2L1Olc4B2XY0hPYCAL3epFnugMhuw5TMWzMilQ3QCZcCHoYm9NGWTHbr5REFxw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -1080,9 +1080,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-linux-arm64-gnu": {
|
||||
"version": "16.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.1.6.tgz",
|
||||
"integrity": "sha512-OJYkCd5pj/QloBvoEcJ2XiMnlJkRv9idWA/j0ugSuA34gMT6f5b7vOiCQHVRpvStoZUknhl6/UxOXL4OwtdaBw==",
|
||||
"version": "16.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.2.2.tgz",
|
||||
"integrity": "sha512-c3m8kBHMziMgo2fICOP/cd/5YlrxDU5YYjAJeQLyFsCqVF8xjOTH/QYG4a2u48CvvZZSj1eHQfBCbyh7kBr30Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -1096,9 +1096,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-linux-arm64-musl": {
|
||||
"version": "16.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.1.6.tgz",
|
||||
"integrity": "sha512-S4J2v+8tT3NIO9u2q+S0G5KdvNDjXfAv06OhfOzNDaBn5rw84DGXWndOEB7d5/x852A20sW1M56vhC/tRVbccQ==",
|
||||
"version": "16.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.2.2.tgz",
|
||||
"integrity": "sha512-VKLuscm0P/mIfzt+SDdn2+8TNNJ7f0qfEkA+az7OqQbjzKdBxAHs0UvuiVoCtbwX+dqMEL9U54b5wQ/aN3dHeg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -1112,9 +1112,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-linux-x64-gnu": {
|
||||
"version": "16.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.1.6.tgz",
|
||||
"integrity": "sha512-2eEBDkFlMMNQnkTyPBhQOAyn2qMxyG2eE7GPH2WIDGEpEILcBPI/jdSv4t6xupSP+ot/jkfrCShLAa7+ZUPcJQ==",
|
||||
"version": "16.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.2.2.tgz",
|
||||
"integrity": "sha512-kU3OPHJq6sBUjOk7wc5zJ7/lipn8yGldMoAv4z67j6ov6Xo/JvzA7L7LCsyzzsXmgLEhk3Qkpwqaq/1+XpNR3g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -1128,9 +1128,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-linux-x64-musl": {
|
||||
"version": "16.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.1.6.tgz",
|
||||
"integrity": "sha512-oicJwRlyOoZXVlxmIMaTq7f8pN9QNbdes0q2FXfRsPhfCi8n8JmOZJm5oo1pwDaFbnnD421rVU409M3evFbIqg==",
|
||||
"version": "16.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.2.2.tgz",
|
||||
"integrity": "sha512-CKXRILyErMtUftp+coGcZ38ZwE/Aqq45VMCcRLr2I4OXKrgxIBDXHnBgeX/UMil0S09i2JXaDL3Q+TN8D/cKmg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -1144,9 +1144,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-win32-arm64-msvc": {
|
||||
"version": "16.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.1.6.tgz",
|
||||
"integrity": "sha512-gQmm8izDTPgs+DCWH22kcDmuUp7NyiJgEl18bcr8irXA5N2m2O+JQIr6f3ct42GOs9c0h8QF3L5SzIxcYAAXXw==",
|
||||
"version": "16.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.2.2.tgz",
|
||||
"integrity": "sha512-sS/jSk5VUoShUqINJFvNjVT7JfR5ORYj/+/ZpOYbbIohv/lQfduWnGAycq2wlknbOql2xOR0DoV0s6Xfcy49+g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -1160,9 +1160,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-win32-x64-msvc": {
|
||||
"version": "16.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.1.6.tgz",
|
||||
"integrity": "sha512-NRfO39AIrzBnixKbjuo2YiYhB6o9d8v/ymU9m/Xk8cyVk+k7XylniXkHwjs4s70wedVffc6bQNbufk5v0xEm0A==",
|
||||
"version": "16.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.2.2.tgz",
|
||||
"integrity": "sha512-aHaKceJgdySReT7qeck5oShucxWRiiEuwCGK8HHALe6yZga8uyFpLkPgaRw3kkF04U7ROogL/suYCNt/+CuXGA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -1741,9 +1741,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
|
||||
"version": "5.0.4",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz",
|
||||
"integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==",
|
||||
"version": "5.0.5",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
|
||||
"integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -2422,9 +2422,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.12",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
||||
"version": "1.1.13",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz",
|
||||
"integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -3568,9 +3568,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/flatted": {
|
||||
"version": "3.3.4",
|
||||
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.4.tgz",
|
||||
"integrity": "sha512-3+mMldrTAPdta5kjX2G2J7iX4zxtnwpdA8Tr2ZSjkyPSanvbZAcy6flmtnXbEybHrDcU9641lxrMfFuUxVz9vA==",
|
||||
"version": "3.4.2",
|
||||
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz",
|
||||
"integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
@@ -4954,14 +4954,14 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/next": {
|
||||
"version": "16.1.6",
|
||||
"resolved": "https://registry.npmjs.org/next/-/next-16.1.6.tgz",
|
||||
"integrity": "sha512-hkyRkcu5x/41KoqnROkfTm2pZVbKxvbZRuNvKXLRXxs3VfyO0WhY50TQS40EuKO9SW3rBj/sF3WbVwDACeMZyw==",
|
||||
"version": "16.2.2",
|
||||
"resolved": "https://registry.npmjs.org/next/-/next-16.2.2.tgz",
|
||||
"integrity": "sha512-i6AJdyVa4oQjyvX/6GeER8dpY/xlIV+4NMv/svykcLtURJSy/WzDnnUk/TM4d0uewFHK7xSQz4TbIwPgjky+3A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@next/env": "16.1.6",
|
||||
"@next/env": "16.2.2",
|
||||
"@swc/helpers": "0.5.15",
|
||||
"baseline-browser-mapping": "^2.8.3",
|
||||
"baseline-browser-mapping": "^2.9.19",
|
||||
"caniuse-lite": "^1.0.30001579",
|
||||
"postcss": "8.4.31",
|
||||
"styled-jsx": "5.1.6"
|
||||
@@ -4973,15 +4973,15 @@
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@next/swc-darwin-arm64": "16.1.6",
|
||||
"@next/swc-darwin-x64": "16.1.6",
|
||||
"@next/swc-linux-arm64-gnu": "16.1.6",
|
||||
"@next/swc-linux-arm64-musl": "16.1.6",
|
||||
"@next/swc-linux-x64-gnu": "16.1.6",
|
||||
"@next/swc-linux-x64-musl": "16.1.6",
|
||||
"@next/swc-win32-arm64-msvc": "16.1.6",
|
||||
"@next/swc-win32-x64-msvc": "16.1.6",
|
||||
"sharp": "^0.34.4"
|
||||
"@next/swc-darwin-arm64": "16.2.2",
|
||||
"@next/swc-darwin-x64": "16.2.2",
|
||||
"@next/swc-linux-arm64-gnu": "16.2.2",
|
||||
"@next/swc-linux-arm64-musl": "16.2.2",
|
||||
"@next/swc-linux-x64-gnu": "16.2.2",
|
||||
"@next/swc-linux-x64-musl": "16.2.2",
|
||||
"@next/swc-win32-arm64-msvc": "16.2.2",
|
||||
"@next/swc-win32-x64-msvc": "16.2.2",
|
||||
"sharp": "^0.34.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@opentelemetry/api": "^1.1.0",
|
||||
@@ -5298,9 +5298,9 @@
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/picomatch": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
|
||||
"integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -6099,9 +6099,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tinyglobby/node_modules/picomatch": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
|
||||
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"lint": "eslint"
|
||||
},
|
||||
"dependencies": {
|
||||
"next": "16.1.6",
|
||||
"next": "^16.2.2",
|
||||
"react": "19.2.3",
|
||||
"react-dom": "19.2.3"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user