done ?
This commit is contained in:
		
							
								
								
									
										3
									
								
								.idea/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.idea/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| # Default ignored files | ||||
| /shelf/ | ||||
| /workspace.xml | ||||
							
								
								
									
										7
									
								
								.idea/encodings.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								.idea/encodings.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <project version="4"> | ||||
|   <component name="Encoding"> | ||||
|     <file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" /> | ||||
|     <file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" /> | ||||
|   </component> | ||||
| </project> | ||||
							
								
								
									
										10
									
								
								.idea/inspectionProfiles/Project_Default.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								.idea/inspectionProfiles/Project_Default.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| <component name="InspectionProjectProfileManager"> | ||||
|   <profile version="1.0"> | ||||
|     <option name="myName" value="Project Default" /> | ||||
|     <inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false"> | ||||
|       <option name="processCode" value="true" /> | ||||
|       <option name="processLiterals" value="true" /> | ||||
|       <option name="processComments" value="true" /> | ||||
|     </inspection_tool> | ||||
|   </profile> | ||||
| </component> | ||||
							
								
								
									
										12
									
								
								.idea/misc.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								.idea/misc.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <project version="4"> | ||||
|   <component name="ExternalStorageConfigurationManager" enabled="true" /> | ||||
|   <component name="MavenProjectsManager"> | ||||
|     <option name="originalFiles"> | ||||
|       <list> | ||||
|         <option value="$PROJECT_DIR$/pom.xml" /> | ||||
|       </list> | ||||
|     </option> | ||||
|   </component> | ||||
|   <component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="corretto-21" project-jdk-type="JavaSDK" /> | ||||
| </project> | ||||
							
								
								
									
										6
									
								
								.idea/vcs.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								.idea/vcs.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <project version="4"> | ||||
|   <component name="VcsDirectoryMappings"> | ||||
|     <mapping directory="" vcs="Git" /> | ||||
|   </component> | ||||
| </project> | ||||
| @@ -0,0 +1,24 @@ | ||||
| package fr.iut_fbleau.but3.dev62.mylibrary.book; | ||||
|  | ||||
| import java.time.LocalDate; | ||||
| import java.util.List; | ||||
|  | ||||
| import lombok.AllArgsConstructor; | ||||
| import lombok.Builder; | ||||
| import lombok.Getter; | ||||
|  | ||||
| @Builder | ||||
| @Getter | ||||
| @AllArgsConstructor | ||||
| public class BookDTO { | ||||
|     private final String isbn; | ||||
|     private final String title; | ||||
|     private final String author; | ||||
|     private final String publisher; | ||||
|     private final LocalDate publicationDate; | ||||
|     private final double price; | ||||
|     private final int stock; | ||||
|     private final List<Category> categories; | ||||
|     private final String description; | ||||
|     private final String language; | ||||
| } | ||||
| @@ -0,0 +1,17 @@ | ||||
| package fr.iut_fbleau.but3.dev62.mylibrary.book; | ||||
|  | ||||
| import java.time.LocalDate; | ||||
| import java.util.List; | ||||
|  | ||||
| public record BookInfo( | ||||
|         String isbn, | ||||
|         String title, | ||||
|         String author, | ||||
|         String publisher, | ||||
|         LocalDate publicationDate, | ||||
|         double price, | ||||
|         int stock, | ||||
|         List<Category> categories, | ||||
|         String description, | ||||
|         String language | ||||
| ) {} | ||||
| @@ -0,0 +1,24 @@ | ||||
| package fr.iut_fbleau.but3.dev62.mylibrary.book; | ||||
|  | ||||
| public enum Category { | ||||
|     FICTION, | ||||
|     NON_FICTION, | ||||
|     SCIENCE_FICTION, | ||||
|     FANTASY, | ||||
|     MYSTERY, | ||||
|     THRILLER, | ||||
|     ROMANCE, | ||||
|     BIOGRAPHY, | ||||
|     HISTORY, | ||||
|     POETRY, | ||||
|     CHILDREN, | ||||
|     YOUNG_ADULT, | ||||
|     SCIENCE, | ||||
|     PHILOSOPHY, | ||||
|     SELF_HELP, | ||||
|     TRAVEL, | ||||
|     COOKING, | ||||
|     ART, | ||||
|     RELIGION, | ||||
|     REFERENCE | ||||
| } | ||||
| @@ -0,0 +1,40 @@ | ||||
| package fr.iut_fbleau.but3.dev62.mylibrary.book.converter; | ||||
|  | ||||
| import fr.iut_fbleau.but3.dev62.mylibrary.book.BookDTO; | ||||
| import fr.iut_fbleau.but3.dev62.mylibrary.book.BookInfo; | ||||
| import fr.iut_fbleau.but3.dev62.mylibrary.book.entity.Book; | ||||
|  | ||||
| public final class BookConverter { | ||||
|  | ||||
|     private BookConverter() {} | ||||
|  | ||||
|     public static Book toDomain(BookInfo info) { | ||||
|         return Book.builder() | ||||
|                 .isbn(info.isbn()) | ||||
|                 .title(info.title()) | ||||
|                 .author(info.author()) | ||||
|                 .publisher(info.publisher()) | ||||
|                 .publicationDate(info.publicationDate()) | ||||
|                 .price(info.price()) | ||||
|                 .stock(info.stock()) | ||||
|                 .categories(info.categories()) | ||||
|                 .description(info.description()) | ||||
|                 .language(info.language()) | ||||
|                 .build(); | ||||
|     } | ||||
|  | ||||
|     public static BookDTO toDTO(Book book) { | ||||
|         return BookDTO.builder() | ||||
|                 .isbn(book.getIsbn()) | ||||
|                 .title(book.getTitle()) | ||||
|                 .author(book.getAuthor()) | ||||
|                 .publisher(book.getPublisher()) | ||||
|                 .publicationDate(book.getPublicationDate()) | ||||
|                 .price(book.getPrice()) | ||||
|                 .stock(book.getStock()) | ||||
|                 .categories(book.getCategories()) | ||||
|                 .description(book.getDescription()) | ||||
|                 .language(book.getLanguage()) | ||||
|                 .build(); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,33 @@ | ||||
| package fr.iut_fbleau.but3.dev62.mylibrary.book.entity; | ||||
|  | ||||
| import fr.iut_fbleau.but3.dev62.mylibrary.book.Category; | ||||
| import java.time.LocalDate; | ||||
| import java.util.List; | ||||
| import lombok.Builder; | ||||
| import lombok.Getter; | ||||
|  | ||||
| @Builder | ||||
| @Getter | ||||
| public class Book { | ||||
|     private String isbn; | ||||
|     private String title; | ||||
|     private String author; | ||||
|     private String publisher; | ||||
|     private LocalDate publicationDate; | ||||
|     private double price; | ||||
|     private int stock; | ||||
|     private List<Category> categories; | ||||
|     private String description; | ||||
|     private String language; | ||||
|  | ||||
|     public void addStock(int quantityToAdd) { | ||||
|         this.stock += quantityToAdd; | ||||
|     } | ||||
|  | ||||
|     public void removeStock(int quantityToRemove) { | ||||
|         if (quantityToRemove > this.stock) { | ||||
|             throw new IllegalArgumentException("Pas assez de stock pour retirer : " + quantityToRemove); | ||||
|         } | ||||
|         this.stock -= quantityToRemove; | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,13 @@ | ||||
| package fr.iut_fbleau.but3.dev62.mylibrary.book.exception; | ||||
|  | ||||
| public class BookNotFoundException extends RuntimeException { | ||||
|     public static final String THE_BOOK_WITH_ISBN_DOES_NOT_EXIST_MESSAGE = "The book with isbn %s does not exist"; | ||||
|  | ||||
|     public BookNotFoundException(String message) { | ||||
|         super(message); | ||||
|     } | ||||
|  | ||||
|     public static BookNotFoundException forIsbn(String isbn) { | ||||
|         return new BookNotFoundException(String.format(THE_BOOK_WITH_ISBN_DOES_NOT_EXIST_MESSAGE, isbn)); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,7 @@ | ||||
| package fr.iut_fbleau.but3.dev62.mylibrary.book.exception; | ||||
|  | ||||
| public class NotValidBookException extends RuntimeException { | ||||
|     public NotValidBookException(String message) { | ||||
|         super(message); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,78 @@ | ||||
| package fr.iut_fbleau.but3.dev62.mylibrary.book.repository; | ||||
|  | ||||
| import fr.iut_fbleau.but3.dev62.mylibrary.book.entity.Book; | ||||
| import fr.iut_fbleau.but3.dev62.mylibrary.book.Category; | ||||
| import java.util.*; | ||||
| import java.util.stream.Collectors; | ||||
| import lombok.NoArgsConstructor; | ||||
|  | ||||
| @NoArgsConstructor | ||||
| public final class BookRepository { | ||||
|     private final List<Book> books = new ArrayList<>(); | ||||
|  | ||||
|     public List<Book> findAll() { | ||||
|         return books; | ||||
|     } | ||||
|  | ||||
|     public void deleteAll() { | ||||
|         books.clear(); | ||||
|     } | ||||
|  | ||||
|     public Book save(Book newBook) { | ||||
|         Optional<Book> optionalBookWithSameIsbn = this.findByIsbn(newBook.getIsbn()); | ||||
|         optionalBookWithSameIsbn.ifPresent(books::remove); | ||||
|         books.add(newBook); | ||||
|         return newBook; | ||||
|     } | ||||
|  | ||||
|     public Optional<Book> findByIsbn(String isbn) { | ||||
|         return books.stream() | ||||
|                 .filter(book -> Objects.equals(book.getIsbn(), isbn)) | ||||
|                 .findFirst(); | ||||
|     } | ||||
|  | ||||
|     public boolean existsByIsbn(String isbn) { | ||||
|         return books.stream() | ||||
|                 .anyMatch(book -> Objects.equals(book.getIsbn(), isbn)); | ||||
|     } | ||||
|  | ||||
|     public void delete(Book book) { | ||||
|         books.remove(book); | ||||
|     } | ||||
|  | ||||
|     public List<Book> findByTitleContaining(String titlePart) { | ||||
|         return books.stream() | ||||
|                 .filter(book -> book.getTitle() != null && book.getTitle().toLowerCase().contains(titlePart.toLowerCase())) | ||||
|                 .collect(Collectors.toList()); | ||||
|     } | ||||
|  | ||||
|     public List<Book> findByAuthor(String author) { | ||||
|         return books.stream() | ||||
|                 .filter(book -> book.getAuthor() != null && book.getAuthor().equalsIgnoreCase(author)) | ||||
|                 .collect(Collectors.toList()); | ||||
|     } | ||||
|  | ||||
|     public List<Book> findByPublisher(String publisher) { | ||||
|         return books.stream() | ||||
|                 .filter(book -> book.getPublisher() != null && book.getPublisher().equalsIgnoreCase(publisher)) | ||||
|                 .collect(Collectors.toList()); | ||||
|     } | ||||
|  | ||||
|     public List<Book> findByCategory(Category category) { | ||||
|         return books.stream() | ||||
|                 .filter(book -> book.getCategories() != null && book.getCategories().contains(category)) | ||||
|                 .collect(Collectors.toList()); | ||||
|     } | ||||
|  | ||||
|     public List<Book> findPublishedAfter(java.time.LocalDate date) { | ||||
|         return books.stream() | ||||
|                 .filter(book -> book.getPublicationDate() != null && book.getPublicationDate().isAfter(date)) | ||||
|                 .collect(Collectors.toList()); | ||||
|     } | ||||
|  | ||||
|     public List<Book> findByPriceRange(double min, double max) { | ||||
|         return books.stream() | ||||
|                 .filter(book -> book.getPrice() >= min && book.getPrice() <= max) | ||||
|                 .collect(Collectors.toList()); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,155 @@ | ||||
| package fr.iut_fbleau.but3.dev62.mylibrary.book.usecase; | ||||
|  | ||||
| import fr.iut_fbleau.but3.dev62.mylibrary.book.BookDTO; | ||||
| import fr.iut_fbleau.but3.dev62.mylibrary.book.BookInfo; | ||||
| import fr.iut_fbleau.but3.dev62.mylibrary.book.Category; | ||||
| import fr.iut_fbleau.but3.dev62.mylibrary.book.converter.BookConverter; | ||||
| import fr.iut_fbleau.but3.dev62.mylibrary.book.entity.Book; | ||||
| import fr.iut_fbleau.but3.dev62.mylibrary.book.exception.BookNotFoundException; | ||||
| import fr.iut_fbleau.but3.dev62.mylibrary.book.exception.NotValidBookException; | ||||
| import fr.iut_fbleau.but3.dev62.mylibrary.book.repository.BookRepository; | ||||
| import fr.iut_fbleau.but3.dev62.mylibrary.book.validator.BookValidator; | ||||
|  | ||||
| import java.time.LocalDate; | ||||
| import java.util.*; | ||||
| import java.util.stream.Collectors; | ||||
|  | ||||
| public final class BookUseCase { | ||||
|  | ||||
|     private final BookRepository bookRepository; | ||||
|  | ||||
|     public BookUseCase(BookRepository bookRepository) { | ||||
|         this.bookRepository = bookRepository; | ||||
|     } | ||||
|  | ||||
|     public String registerBook(BookInfo newBook) throws NotValidBookException { | ||||
|         BookValidator.validate(newBook); | ||||
|  | ||||
|         if (bookRepository.existsByIsbn(newBook.isbn())) { | ||||
|             throw new NotValidBookException("Un livre avec cet ISBN existe déjà"); | ||||
|         } | ||||
|  | ||||
|         Book bookToRegister = BookConverter.toDomain(newBook); | ||||
|         Book registeredBook = bookRepository.save(bookToRegister); | ||||
|         return registeredBook.getIsbn(); | ||||
|     } | ||||
|  | ||||
|     public BookDTO findBookByIsbn(String isbn) throws BookNotFoundException { | ||||
|         return bookRepository.findByIsbn(isbn) | ||||
|                 .map(BookConverter::toDTO) | ||||
|                 .orElseThrow(() -> new BookNotFoundException(isbn)); | ||||
|     } | ||||
|  | ||||
|     public BookDTO updateBook(String isbn, BookInfo bookInfo) | ||||
|             throws BookNotFoundException, NotValidBookException { | ||||
|         BookValidator.validate(bookInfo); | ||||
|         getBookIfDoesNotExistThrowBookNotFoundException(isbn); | ||||
|         Book updated = Book.builder() | ||||
|                 .isbn(isbn) | ||||
|                 .title(bookInfo.title()) | ||||
|                 .author(bookInfo.author()) | ||||
|                 .publisher(bookInfo.publisher()) | ||||
|                 .publicationDate(bookInfo.publicationDate()) | ||||
|                 .price(bookInfo.price()) | ||||
|                 .stock(bookInfo.stock()) | ||||
|                 .categories(bookInfo.categories()) | ||||
|                 .description(bookInfo.description()) | ||||
|                 .language(bookInfo.language()) | ||||
|                 .build(); | ||||
|         Book saved = bookRepository.save(updated); | ||||
|         return BookConverter.toDTO(saved); | ||||
|     } | ||||
|  | ||||
|     public void deleteBook(String isbn) throws BookNotFoundException { | ||||
|         Book bookToDelete = getBookIfDoesNotExistThrowBookNotFoundException(isbn); | ||||
|         this.bookRepository.delete(bookToDelete); | ||||
|     } | ||||
|  | ||||
|     public int addStock(String isbn, int quantity) throws BookNotFoundException { | ||||
|         Book book = getBookIfDoesNotExistThrowBookNotFoundException(isbn); | ||||
|         book.addStock(quantity); | ||||
|         bookRepository.save(book); | ||||
|         return book.getStock(); | ||||
|     } | ||||
|  | ||||
|     public int removeStock(String isbn, int quantity) throws BookNotFoundException { | ||||
|         Book book = getBookIfDoesNotExistThrowBookNotFoundException(isbn); | ||||
|         book.removeStock(quantity); | ||||
|         bookRepository.save(book); | ||||
|         return book.getStock(); | ||||
|     } | ||||
|  | ||||
|     public Map<String, Object> findBooksPageSorted(int page, int size, String sortBy) { | ||||
|         List<Book> allBooks = bookRepository.findAll(); | ||||
|  | ||||
|         allBooks.sort(Comparator.comparing( | ||||
|                 book -> { | ||||
|                     switch (sortBy.toLowerCase()) { | ||||
|                         case "titre": | ||||
|                         case "title": | ||||
|                             return book.getTitle() == null ? "" : book.getTitle().toLowerCase(); | ||||
|                         case "auteur": | ||||
|                         case "author": | ||||
|                             return book.getAuthor() == null ? "" : book.getAuthor().toLowerCase(); | ||||
|                         case "editeur": | ||||
|                         case "publisher": | ||||
|                             return book.getPublisher() == null ? "" : book.getPublisher().toLowerCase(); | ||||
|                         default: | ||||
|                             return book.getTitle() == null ? "" : book.getTitle().toLowerCase(); | ||||
|                     } | ||||
|                 }, | ||||
|                 Comparator.naturalOrder() | ||||
|         )); | ||||
|  | ||||
|         int totalElements = allBooks.size(); | ||||
|         int totalPages = (int) Math.ceil((double) totalElements / size); | ||||
|         int fromIndex = Math.min(page * size, totalElements); | ||||
|         int toIndex = Math.min(fromIndex + size, totalElements); | ||||
|  | ||||
|         List<BookDTO> content = allBooks.subList(fromIndex, toIndex) | ||||
|                 .stream() | ||||
|                 .map(BookConverter::toDTO) | ||||
|                 .collect(Collectors.toList()); | ||||
|  | ||||
|         Map<String, Object> result = new HashMap<>(); | ||||
|         result.put("content", content); | ||||
|         result.put("totalElements", totalElements); | ||||
|         result.put("totalPages", totalPages); | ||||
|         return result; | ||||
|     } | ||||
|  | ||||
|     public List<BookDTO> findBooksByTitleContaining(String title) { | ||||
|         List<Book> books = bookRepository.findByTitleContaining(title); | ||||
|         return books.stream().map(BookConverter::toDTO).collect(Collectors.toList()); | ||||
|     } | ||||
|  | ||||
|     public List<BookDTO> findBooksByAuthor(String author) { | ||||
|         List<Book> books = bookRepository.findByAuthor(author); | ||||
|         return books.stream().map(BookConverter::toDTO).collect(Collectors.toList()); | ||||
|     } | ||||
|  | ||||
|     public List<BookDTO> findBooksByPublisher(String publisher) { | ||||
|         List<Book> books = bookRepository.findByPublisher(publisher); | ||||
|         return books.stream().map(BookConverter::toDTO).collect(Collectors.toList()); | ||||
|     } | ||||
|  | ||||
|     public List<BookDTO> findBooksByCategory(Category category) { | ||||
|         List<Book> books = bookRepository.findByCategory(category); | ||||
|         return books.stream().map(BookConverter::toDTO).collect(Collectors.toList()); | ||||
|     } | ||||
|  | ||||
|     public List<BookDTO> findBooksPublishedAfter(LocalDate date) { | ||||
|         List<Book> books = bookRepository.findPublishedAfter(date); | ||||
|         return books.stream().map(BookConverter::toDTO).collect(Collectors.toList()); | ||||
|     } | ||||
|  | ||||
|     public List<BookDTO> findBooksByPriceRange(double min, double max) { | ||||
|         List<Book> books = bookRepository.findByPriceRange(min, max); | ||||
|         return books.stream().map(BookConverter::toDTO).collect(Collectors.toList()); | ||||
|     } | ||||
|  | ||||
|     private Book getBookIfDoesNotExistThrowBookNotFoundException(String isbn) { | ||||
|         return bookRepository.findByIsbn(isbn) | ||||
|                 .orElseThrow(() -> new BookNotFoundException(isbn)); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,62 @@ | ||||
| package fr.iut_fbleau.but3.dev62.mylibrary.book.validator; | ||||
|  | ||||
| import fr.iut_fbleau.but3.dev62.mylibrary.book.BookInfo; | ||||
| import fr.iut_fbleau.but3.dev62.mylibrary.book.exception.NotValidBookException; | ||||
|  | ||||
| public final class BookValidator { | ||||
|  | ||||
|     public static final String ISBN_CANNOT_BE_NULL = "L'ISBN ne peut pas être nul"; | ||||
|     public static final String TITLE_CANNOT_BE_BLANK = "Le titre ne peut pas être vide"; | ||||
|     public static final String AUTHOR_CANNOT_BE_BLANK = "L'auteur ne peut pas être vide"; | ||||
|     public static final String PRICE_MUST_BE_POSITIVE = "Le prix doit être positif"; | ||||
|     public static final String STOCK_CANNOT_BE_NEGATIVE = "Le stock ne peut pas être négatif"; | ||||
|     public static final String ISBN_IS_NOT_VALID = "L'ISBN n'est pas valide"; | ||||
|     public static final String PUBLISHER_CANNOT_BE_BLANK = "L'éditeur ne peut pas être vide"; | ||||
|     public static final String PUBLICATION_DATE_CANNOT_BE_NULL = "La date de publication ne peut pas être nulle"; | ||||
|     public static final String CATEGORIES_CANNOT_BE_EMPTY = "La liste des catégories ne peut pas être vide"; | ||||
|     public static final String DESCRIPTION_CANNOT_BE_BLANK = "La description ne peut pas être vide"; | ||||
|     public static final String LANGUAGE_CANNOT_BE_BLANK = "La langue ne peut pas être vide"; | ||||
|  | ||||
|     private BookValidator() {} | ||||
|  | ||||
|     public static void validate(BookInfo bookInfo) throws NotValidBookException { | ||||
|         validateIsbn(bookInfo); | ||||
|         validateTitle(bookInfo); | ||||
|         validateAuthor(bookInfo); | ||||
|         validatePrice(bookInfo); | ||||
|         validateStock(bookInfo); | ||||
|     } | ||||
|  | ||||
|     private static void validateIsbn(BookInfo bookInfo) throws NotValidBookException { | ||||
|         if (bookInfo.isbn() == null) { | ||||
|             throw new NotValidBookException(ISBN_CANNOT_BE_NULL); | ||||
|         } | ||||
|         if (!bookInfo.isbn().matches("\\d{13}")) { | ||||
|             throw new NotValidBookException(ISBN_IS_NOT_VALID); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private static void validateTitle(BookInfo bookInfo) throws NotValidBookException { | ||||
|         if (bookInfo.title() == null || bookInfo.title().isBlank()) { | ||||
|             throw new NotValidBookException(TITLE_CANNOT_BE_BLANK); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private static void validateAuthor(BookInfo bookInfo) throws NotValidBookException { | ||||
|         if (bookInfo.author() == null || bookInfo.author().isBlank()) { | ||||
|             throw new NotValidBookException(AUTHOR_CANNOT_BE_BLANK); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private static void validatePrice(BookInfo bookInfo) throws NotValidBookException { | ||||
|         if (bookInfo.price() < 0) { | ||||
|             throw new NotValidBookException(PRICE_MUST_BE_POSITIVE); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private static void validateStock(BookInfo bookInfo) throws NotValidBookException { | ||||
|         if (bookInfo.stock() < 0) { | ||||
|             throw new NotValidBookException(STOCK_CANNOT_BE_NEGATIVE); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -3,28 +3,30 @@ package fr.iut_fbleau.but3.dev62.mylibrary.customer.converter; | ||||
| import fr.iut_fbleau.but3.dev62.mylibrary.customer.CustomerDTO; | ||||
| import fr.iut_fbleau.but3.dev62.mylibrary.customer.CustomerInfo; | ||||
| import fr.iut_fbleau.but3.dev62.mylibrary.customer.entity.Customer; | ||||
| import java.util.UUID; | ||||
|  | ||||
| public final class CustomerConverter { | ||||
|   private CustomerConverter(){ | ||||
|  | ||||
|   private CustomerConverter() { | ||||
|     // util class | ||||
|   } | ||||
|  | ||||
|   public static Customer toDomain(CustomerInfo newCustomer) { | ||||
|     return Customer.builder() | ||||
|         .firstName(newCustomer.firstName()) | ||||
|         .lastName(newCustomer.lastName()) | ||||
|         .phoneNumber(newCustomer.phoneNumber()) | ||||
|         .loyaltyPoints(0) | ||||
|         .build(); | ||||
|             .id(UUID.randomUUID())                 // ← génération automatique | ||||
|             .firstName(newCustomer.firstName()) | ||||
|             .lastName(newCustomer.lastName()) | ||||
|             .phoneNumber(newCustomer.phoneNumber()) | ||||
|             .loyaltyPoints(0) | ||||
|             .build(); | ||||
|   } | ||||
|  | ||||
|   public static CustomerDTO toDTO(Customer customer) { | ||||
|     return CustomerDTO.builder() | ||||
|         .id(customer.getId()) | ||||
|         .firstName(customer.getFirstName()) | ||||
|         .lastName(customer.getLastName()) | ||||
|         .phoneNumber(customer.getPhoneNumber()) | ||||
|         .loyaltyPoints(customer.getLoyaltyPoints()) | ||||
|         .build(); | ||||
|             .id(customer.getId()) | ||||
|             .firstName(customer.getFirstName()) | ||||
|             .lastName(customer.getLastName()) | ||||
|             .phoneNumber(customer.getPhoneNumber()) | ||||
|             .loyaltyPoints(customer.getLoyaltyPoints()) | ||||
|             .build(); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -20,27 +20,35 @@ public final class CustomerRepository { | ||||
|   } | ||||
|  | ||||
|   public Customer save(Customer newCustomer) { | ||||
|     Optional<Customer> optionalCustomerWithSameId = this.findById(newCustomer.getId()); | ||||
|     optionalCustomerWithSameId.ifPresentOrElse(customers::remove, newCustomer::setRandomUUID); | ||||
|  | ||||
|     if (newCustomer.getId() == null) { | ||||
|       newCustomer.setRandomUUID(); | ||||
|     } | ||||
|  | ||||
|     this.findById(newCustomer.getId()).ifPresent(customers::remove); | ||||
|  | ||||
|     this.customers.add(newCustomer); | ||||
|     return newCustomer; | ||||
|   } | ||||
|  | ||||
|   public Optional<Customer> findById(UUID uuid) { | ||||
|     if (uuid == null) { | ||||
|       return Optional.empty(); | ||||
|     } | ||||
|     return this.customers.stream() | ||||
|         .filter(customer -> customer.getId().equals(uuid)) | ||||
|         .findFirst(); | ||||
|             .filter(c -> uuid.equals(c.getId())) | ||||
|             .findFirst(); | ||||
|   } | ||||
|  | ||||
|   public boolean existsById(UUID uuid) { | ||||
|     return this.customers.stream() | ||||
|         .anyMatch(customer -> customer.getId().equals(uuid)); | ||||
|     return uuid != null && this.customers.stream() | ||||
|             .anyMatch(c -> uuid.equals(c.getId())); | ||||
|   } | ||||
|  | ||||
|   public Optional<Customer> findByPhoneNumber(String phoneNumber) { | ||||
|     return this.customers.stream() | ||||
|         .filter(customer -> customer.getPhoneNumber().equals(phoneNumber)) | ||||
|         .findFirst(); | ||||
|             .filter(c -> c.getPhoneNumber().equals(phoneNumber)) | ||||
|             .findFirst(); | ||||
|   } | ||||
|  | ||||
|   public void delete(Customer customer) { | ||||
|   | ||||
| @@ -0,0 +1,14 @@ | ||||
| package fr.iut_fbleau.but3.dev62.mylibrary.order; | ||||
|  | ||||
| import lombok.Builder; | ||||
| import lombok.Getter; | ||||
|  | ||||
| @Builder | ||||
| @Getter | ||||
| public class AddressDTO { | ||||
|     private final String street; | ||||
|     private final String city; | ||||
|     private final String postalCode; | ||||
|     private final String country; | ||||
| } | ||||
|  | ||||
| @@ -0,0 +1,19 @@ | ||||
| package fr.iut_fbleau.but3.dev62.mylibrary.order; | ||||
|  | ||||
| import lombok.Builder; | ||||
| import lombok.Getter; | ||||
| import java.util.List; | ||||
| import java.util.UUID; | ||||
|  | ||||
| @Builder | ||||
| @Getter | ||||
| public class OrderDTO { | ||||
|     private final UUID id; | ||||
|     private final UUID customerId; | ||||
|     private final String paymentMethod; | ||||
|     private final List<OrderLineDTO> orderLines; | ||||
|     private final AddressDTO shippingAddress; | ||||
|     private final double totalPrice; | ||||
|     private final double totalPriceToPay; | ||||
| } | ||||
|  | ||||
| @@ -0,0 +1,16 @@ | ||||
| package fr.iut_fbleau.but3.dev62.mylibrary.order; | ||||
|  | ||||
| import lombok.Builder; | ||||
| import lombok.Getter; | ||||
| import lombok.Setter; | ||||
| import java.util.List; | ||||
|  | ||||
| @Getter | ||||
| @Setter | ||||
| @Builder | ||||
| public class OrderInfo { | ||||
|     private String customerId; | ||||
|     private String paymentMethod; | ||||
|     private List<OrderLineDTO> orderLines; | ||||
|     private AddressDTO address; | ||||
| } | ||||
| @@ -0,0 +1,12 @@ | ||||
| package fr.iut_fbleau.but3.dev62.mylibrary.order; | ||||
|  | ||||
| import lombok.Builder; | ||||
| import lombok.Getter; | ||||
|  | ||||
| @Builder | ||||
| @Getter | ||||
| public class OrderLineDTO { | ||||
|     private final String bookId; | ||||
|     private final int quantity; | ||||
| } | ||||
|  | ||||
| @@ -0,0 +1,6 @@ | ||||
| package fr.iut_fbleau.but3.dev62.mylibrary.order; | ||||
|  | ||||
| public enum PaymentMethod { | ||||
|     CREDIT_CARD, | ||||
|     LOYALTY_POINTS; | ||||
| } | ||||
| @@ -0,0 +1,96 @@ | ||||
| package fr.iut_fbleau.but3.dev62.mylibrary.order.converter; | ||||
|  | ||||
| import fr.iut_fbleau.but3.dev62.mylibrary.order.*; | ||||
| import fr.iut_fbleau.but3.dev62.mylibrary.order.entity.Address; | ||||
| import fr.iut_fbleau.but3.dev62.mylibrary.order.entity.Order; | ||||
| import fr.iut_fbleau.but3.dev62.mylibrary.order.entity.OrderLine; | ||||
| import java.util.UUID; | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
| import java.util.Objects; | ||||
| import java.util.Optional; | ||||
|  | ||||
| public final class OrderConverter { | ||||
|  | ||||
|     private OrderConverter() { | ||||
|         throw new AssertionError("Cette classe utilitaire ne doit pas être instanciée"); | ||||
|     } | ||||
|  | ||||
|     public static Order toDomain(OrderInfo info) { | ||||
|         if (info == null) return null; | ||||
|  | ||||
|         return Order.builder() | ||||
|                 .customerId(Optional.ofNullable(info.getCustomerId()) | ||||
|                         .map(UUID::fromString) | ||||
|                         .orElse(null)) | ||||
|                 .paymentMethod(Optional.ofNullable(info.getPaymentMethod()) | ||||
|                         .map(PaymentMethod::valueOf) | ||||
|                         .orElse(null)) | ||||
|                 .orderLines(convertOrderLines(info.getOrderLines())) | ||||
|                 .shippingAddress(convertAddress(info.getAddress())) | ||||
|                 .totalPrice(0.0) | ||||
|                 .build(); | ||||
|     } | ||||
|  | ||||
|     public static OrderDTO toDTO(Order order) { | ||||
|         if (order == null) return null; | ||||
|  | ||||
|         return OrderDTO.builder() | ||||
|                 .id(order.getId()) | ||||
|                 .customerId(order.getCustomerId()) | ||||
|                 .paymentMethod(Optional.ofNullable(order.getPaymentMethod()) | ||||
|                         .map(PaymentMethod::name) | ||||
|                         .orElse(null)) | ||||
|                 .orderLines(convertOrderLinesToDTO(order.getOrderLines())) | ||||
|                 .shippingAddress(convertAddressToDTO(order.getShippingAddress())) | ||||
|                 .totalPrice(order.getTotalPrice()) | ||||
|                 .totalPriceToPay(order.getTotalPriceToPay()) | ||||
|                 .build(); | ||||
|     } | ||||
|  | ||||
|     private static List<OrderLine> convertOrderLines(List<OrderLineDTO> orderLines) { | ||||
|         return Optional.ofNullable(orderLines) | ||||
|                 .map(lines -> lines.stream() | ||||
|                         .filter(Objects::nonNull) | ||||
|                         .map(line -> OrderLine.builder() | ||||
|                                 .bookId(line.getBookId()) | ||||
|                                 .quantity(line.getQuantity()) | ||||
|                                 .build()) | ||||
|                         .toList()) | ||||
|                 .orElse(new ArrayList<>()); | ||||
|     } | ||||
|  | ||||
|     private static List<OrderLineDTO> convertOrderLinesToDTO(List<OrderLine> orderLines) { | ||||
|         return Optional.ofNullable(orderLines) | ||||
|                 .map(lines -> lines.stream() | ||||
|                         .filter(Objects::nonNull) | ||||
|                         .map(line -> OrderLineDTO.builder() | ||||
|                                 .bookId(line.getBookId()) | ||||
|                                 .quantity(line.getQuantity()) | ||||
|                                 .build()) | ||||
|                         .toList()) | ||||
|                 .orElse(null); | ||||
|     } | ||||
|  | ||||
|     private static Address convertAddress(AddressDTO addressDTO) { | ||||
|         return Optional.ofNullable(addressDTO) | ||||
|                 .map(addr -> Address.builder() | ||||
|                         .street(addr.getStreet()) | ||||
|                         .city(addr.getCity()) | ||||
|                         .postalCode(addr.getPostalCode()) | ||||
|                         .country(addr.getCountry()) | ||||
|                         .build()) | ||||
|                 .orElse(null); | ||||
|     } | ||||
|  | ||||
|     private static AddressDTO convertAddressToDTO(Address address) { | ||||
|         return Optional.ofNullable(address) | ||||
|                 .map(addr -> AddressDTO.builder() | ||||
|                         .street(addr.getStreet()) | ||||
|                         .city(addr.getCity()) | ||||
|                         .postalCode(addr.getPostalCode()) | ||||
|                         .country(addr.getCountry()) | ||||
|                         .build()) | ||||
|                 .orElse(null); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,14 @@ | ||||
| package fr.iut_fbleau.but3.dev62.mylibrary.order.entity; | ||||
|  | ||||
| import lombok.Builder; | ||||
| import lombok.Getter; | ||||
| import lombok.AllArgsConstructor; | ||||
|  | ||||
| @Getter | ||||
| @Builder | ||||
| public class Address { | ||||
|     private final String street; | ||||
|     private final String city; | ||||
|     private final String postalCode; | ||||
|     private final String country; | ||||
| } | ||||
| @@ -0,0 +1,19 @@ | ||||
| package fr.iut_fbleau.but3.dev62.mylibrary.order.entity; | ||||
|  | ||||
| import fr.iut_fbleau.but3.dev62.mylibrary.order.PaymentMethod; | ||||
| import lombok.Builder; | ||||
| import lombok.Getter; | ||||
| import java.util.List; | ||||
| import java.util.UUID; | ||||
|  | ||||
| @Getter | ||||
| @Builder | ||||
| public class Order { | ||||
|     private final UUID id; | ||||
|     private final UUID customerId; | ||||
|     private final PaymentMethod paymentMethod; | ||||
|     private final List<OrderLine> orderLines; | ||||
|     private final Address shippingAddress; | ||||
|     private final double totalPrice; | ||||
|     private final double totalPriceToPay; | ||||
| } | ||||
| @@ -0,0 +1,12 @@ | ||||
| package fr.iut_fbleau.but3.dev62.mylibrary.order.entity; | ||||
|  | ||||
| import lombok.Builder; | ||||
| import lombok.Getter; | ||||
| import lombok.AllArgsConstructor; | ||||
| import lombok.NoArgsConstructor; | ||||
| @Getter | ||||
| @Builder | ||||
| public class OrderLine { | ||||
|     private final String bookId; | ||||
|     private final int quantity; | ||||
| } | ||||
| @@ -0,0 +1,7 @@ | ||||
| package fr.iut_fbleau.but3.dev62.mylibrary.order.exception; | ||||
|  | ||||
| public class IllegalOrderPointException extends RuntimeException { | ||||
|     public IllegalOrderPointException(String message) { | ||||
|         super(message); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,7 @@ | ||||
| package fr.iut_fbleau.but3.dev62.mylibrary.order.exception; | ||||
|  | ||||
| public class InvalidPaymentMethodException extends RuntimeException { | ||||
|     public InvalidPaymentMethodException(String message) { | ||||
|         super(message); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,7 @@ | ||||
| package fr.iut_fbleau.but3.dev62.mylibrary.order.exception; | ||||
|  | ||||
| public class NotValidOrderException extends RuntimeException { | ||||
|     public NotValidOrderException(String message) { | ||||
|         super(message); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,7 @@ | ||||
| package fr.iut_fbleau.but3.dev62.mylibrary.order.exception; | ||||
|  | ||||
| public class OrderNotFoundException extends RuntimeException { | ||||
|     public OrderNotFoundException(String message) { | ||||
|         super(message); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,7 @@ | ||||
| package fr.iut_fbleau.but3.dev62.mylibrary.order.exception; | ||||
|  | ||||
| public class OrderQuantityException extends RuntimeException { | ||||
|     public OrderQuantityException(String message) { | ||||
|         super(message); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,7 @@ | ||||
| package fr.iut_fbleau.but3.dev62.mylibrary.order.exception; | ||||
|  | ||||
| public class WrongAddressException extends RuntimeException { | ||||
|     public WrongAddressException(String message) { | ||||
|         super(message); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,60 @@ | ||||
| package fr.iut_fbleau.but3.dev62.mylibrary.order.repository; | ||||
|  | ||||
| import fr.iut_fbleau.but3.dev62.mylibrary.order.entity.Order; | ||||
| import java.util.*; | ||||
| import lombok.NoArgsConstructor; | ||||
|  | ||||
| @NoArgsConstructor | ||||
| public final class OrderRepository { | ||||
|     private final List<Order> orders = new ArrayList<>(); | ||||
|  | ||||
|     public Order save(Order newOrder) { | ||||
|         if (newOrder.getId() == null) { | ||||
|             Order orderWithId = Order.builder() | ||||
|                     .id(UUID.randomUUID()) | ||||
|                     .customerId(newOrder.getCustomerId()) | ||||
|                     .paymentMethod(newOrder.getPaymentMethod()) | ||||
|                     .orderLines(newOrder.getOrderLines()) | ||||
|                     .shippingAddress(newOrder.getShippingAddress()) | ||||
|                     .totalPrice(newOrder.getTotalPrice()) | ||||
|                     .totalPriceToPay(newOrder.getTotalPriceToPay()) | ||||
|                     .build(); | ||||
|             this.orders.add(orderWithId); | ||||
|             return orderWithId; | ||||
|         } | ||||
|  | ||||
|         Optional<Order> optionalOrderWithSameId = this.findById(newOrder.getId()); | ||||
|         optionalOrderWithSameId.ifPresent(orders::remove); | ||||
|         this.orders.add(newOrder); | ||||
|         return newOrder; | ||||
|     } | ||||
|  | ||||
|     public Optional<Order> findById(UUID id) { | ||||
|         return this.orders.stream() | ||||
|                 .filter(order -> Objects.equals(order.getId(), id)) | ||||
|                 .findFirst(); | ||||
|     } | ||||
|  | ||||
|     public boolean existsById(UUID id) { | ||||
|         return this.orders.stream() | ||||
|                 .anyMatch(order -> Objects.equals(order.getId(), id)); | ||||
|     } | ||||
|  | ||||
|     public List<Order> findByCustomerId(UUID customerId) { | ||||
|         return this.orders.stream() | ||||
|                 .filter(order -> Objects.equals(order.getCustomerId(), customerId)) | ||||
|                 .toList(); | ||||
|     } | ||||
|  | ||||
|     public List<Order> findAll() { | ||||
|         return orders; | ||||
|     } | ||||
|  | ||||
|     public void deleteAll() { | ||||
|         orders.clear(); | ||||
|     } | ||||
|  | ||||
|     public void delete(Order order) { | ||||
|         this.orders.remove(order); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,156 @@ | ||||
| package fr.iut_fbleau.but3.dev62.mylibrary.order.usecase; | ||||
|  | ||||
| import fr.iut_fbleau.but3.dev62.mylibrary.book.exception.BookNotFoundException; | ||||
| import fr.iut_fbleau.but3.dev62.mylibrary.book.repository.BookRepository; | ||||
| import fr.iut_fbleau.but3.dev62.mylibrary.customer.exception.CustomerNotFoundException; | ||||
| import fr.iut_fbleau.but3.dev62.mylibrary.customer.exception.IllegalCustomerPointException; | ||||
| import fr.iut_fbleau.but3.dev62.mylibrary.customer.repository.CustomerRepository; | ||||
| import fr.iut_fbleau.but3.dev62.mylibrary.order.OrderDTO; | ||||
| import fr.iut_fbleau.but3.dev62.mylibrary.order.OrderInfo; | ||||
| import fr.iut_fbleau.but3.dev62.mylibrary.order.PaymentMethod; | ||||
| import fr.iut_fbleau.but3.dev62.mylibrary.order.converter.OrderConverter; | ||||
| import fr.iut_fbleau.but3.dev62.mylibrary.order.entity.Order; | ||||
| import fr.iut_fbleau.but3.dev62.mylibrary.order.exception.*; | ||||
| import fr.iut_fbleau.but3.dev62.mylibrary.order.repository.OrderRepository; | ||||
| import fr.iut_fbleau.but3.dev62.mylibrary.customer.entity.Customer; | ||||
|  | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| import java.util.UUID; | ||||
| import java.util.stream.Collectors; | ||||
|  | ||||
| public final class OrderUseCase { | ||||
|     private final OrderRepository orderRepository; | ||||
|     private final CustomerRepository customerRepository; | ||||
|     private final BookRepository bookRepository; | ||||
|  | ||||
|     public OrderUseCase(OrderRepository orderRepository, CustomerRepository customerRepository, BookRepository bookRepository) { | ||||
|         this.orderRepository = orderRepository; | ||||
|         this.customerRepository = customerRepository; | ||||
|         this.bookRepository = bookRepository; | ||||
|     } | ||||
|  | ||||
|     public UUID createOrder(OrderInfo orderInfo) throws CustomerNotFoundException, IllegalCustomerPointException, NotValidOrderException { | ||||
|         validateOrderInfo(orderInfo); | ||||
|  | ||||
|         PaymentMethod paymentMethod = parsePaymentMethod(orderInfo.getPaymentMethod()); | ||||
|         UUID customerId = parseCustomerId(orderInfo.getCustomerId()); | ||||
|         Customer customer = customerRepository.findById(customerId) | ||||
|                 .orElseThrow(() -> new CustomerNotFoundException(customerId)); | ||||
|  | ||||
|         // Pré-chargement des livres pour éviter les appels multiples | ||||
|         Map<String, fr.iut_fbleau.but3.dev62.mylibrary.book.entity.Book> books = orderInfo.getOrderLines().stream() | ||||
|                 .map(line -> bookRepository.findByIsbn(line.getBookId()) | ||||
|                         .orElseThrow(() -> new BookNotFoundException("Le livre avec l'ISBN " + line.getBookId() + " n'a pas été trouvé"))) | ||||
|                 .collect(Collectors.toMap(fr.iut_fbleau.but3.dev62.mylibrary.book.entity.Book::getIsbn, b -> b)); | ||||
|  | ||||
|         validateOrderLines(orderInfo, books); | ||||
|  | ||||
|         double totalPrice = calculateTotalPrice(orderInfo, books); | ||||
|         double totalPriceToPay = calculateTotalPriceToPay(orderInfo, books); | ||||
|  | ||||
|         Order domainOrder = OrderConverter.toDomain(orderInfo); | ||||
|         Order order = Order.builder() | ||||
|                 .customerId(customerId) | ||||
|                 .paymentMethod(paymentMethod) | ||||
|                 .orderLines(domainOrder.getOrderLines()) | ||||
|                 .shippingAddress(domainOrder.getShippingAddress()) | ||||
|                 .totalPrice(totalPrice) | ||||
|                 .totalPriceToPay(totalPriceToPay) | ||||
|                 .build(); | ||||
|  | ||||
|         if (paymentMethod == PaymentMethod.LOYALTY_POINTS) { | ||||
|             handleLoyaltyPointsPayment(customer, totalPrice); | ||||
|         } | ||||
|  | ||||
|         return orderRepository.save(order).getId(); | ||||
|     } | ||||
|  | ||||
|     private void validateOrderInfo(OrderInfo orderInfo) throws NotValidOrderException { | ||||
|         if (orderInfo == null) { | ||||
|             throw new NotValidOrderException("La commande ne peut pas être nulle"); | ||||
|         } | ||||
|         if (orderInfo.getOrderLines() == null || orderInfo.getOrderLines().isEmpty()) { | ||||
|             throw new OrderQuantityException("La commande doit contenir au moins un livre"); | ||||
|         } | ||||
|         if (orderInfo.getAddress() == null) { | ||||
|             throw new NotValidOrderException("L'adresse de livraison est obligatoire"); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private PaymentMethod parsePaymentMethod(String paymentMethod) { | ||||
|         try { | ||||
|             return PaymentMethod.valueOf(paymentMethod); | ||||
|         } catch (IllegalArgumentException | NullPointerException e) { | ||||
|             throw new InvalidPaymentMethodException("Le mode de paiement " + paymentMethod + " n'est pas valide"); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private void validateOrderLines(OrderInfo orderInfo, Map<String, fr.iut_fbleau.but3.dev62.mylibrary.book.entity.Book> books) { | ||||
|         orderInfo.getOrderLines().forEach(line -> { | ||||
|             if (line.getQuantity() <= 0) { | ||||
|                 throw new OrderQuantityException("La quantité doit être supérieure à 0 pour le livre " + line.getBookId()); | ||||
|             } | ||||
|             var book = books.get(line.getBookId()); | ||||
|             if (book.getStock() < line.getQuantity()) { | ||||
|                 throw new OrderQuantityException("Stock insuffisant pour le livre " + line.getBookId()); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     private double calculateTotalPrice(OrderInfo orderInfo, Map<String, fr.iut_fbleau.but3.dev62.mylibrary.book.entity.Book> books) { | ||||
|         return orderInfo.getOrderLines().stream() | ||||
|                 .mapToDouble(line -> books.get(line.getBookId()).getPrice() * line.getQuantity()) | ||||
|                 .sum(); | ||||
|     } | ||||
|  | ||||
|     private double calculateTotalPriceToPay(OrderInfo orderInfo, Map<String, fr.iut_fbleau.but3.dev62.mylibrary.book.entity.Book> books) { | ||||
|         // Appliquer ici les éventuelles remises | ||||
|         return calculateTotalPrice(orderInfo, books); | ||||
|     } | ||||
|  | ||||
|     private UUID parseOrderId(String orderId) { | ||||
|         try { | ||||
|             return UUID.fromString(orderId); | ||||
|         } catch (IllegalArgumentException e) { | ||||
|             throw new OrderNotFoundException("La commande avec l'ID " + orderId + " n'a pas été trouvée"); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public OrderDTO findOrderById(String orderId) { | ||||
|         UUID uuid = parseOrderId(orderId); | ||||
|         return orderRepository.findById(uuid) | ||||
|                 .map(OrderConverter::toDTO) | ||||
|                 .orElseThrow(() -> new OrderNotFoundException("La commande n'a pas été trouvée")); | ||||
|     } | ||||
|  | ||||
|     public List<OrderDTO> findOrdersByCustomerId(String customerId) throws CustomerNotFoundException { | ||||
|         UUID uuid = parseCustomerId(customerId); | ||||
|         customerRepository.findById(uuid) | ||||
|                 .orElseThrow(() -> new CustomerNotFoundException(uuid)); | ||||
|         return orderRepository.findByCustomerId(uuid) | ||||
|                 .stream() | ||||
|                 .map(OrderConverter::toDTO) | ||||
|                 .toList(); | ||||
|     } | ||||
|  | ||||
|     private UUID parseCustomerId(String customerId) throws CustomerNotFoundException { | ||||
|         if (customerId == null || customerId.trim().isEmpty()) { | ||||
|             throw new CustomerNotFoundException(null); | ||||
|         } | ||||
|         try { | ||||
|             return UUID.fromString(customerId); | ||||
|         } catch (IllegalArgumentException e) { | ||||
|             throw new CustomerNotFoundException(null); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private void handleLoyaltyPointsPayment(Customer customer, double totalPrice) throws IllegalCustomerPointException { | ||||
|         int requiredPoints = (int) (totalPrice * 100); | ||||
|         if (customer.getLoyaltyPoints() < requiredPoints) { | ||||
|             throw new IllegalOrderPointException("Points de fidélité insuffisants pour payer la commande"); | ||||
|         } | ||||
|         customer.removeLoyaltyPoints(requiredPoints); | ||||
|         customerRepository.save(customer); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,66 @@ | ||||
| package fr.iut_fbleau.but3.dev62.mylibrary.order.validator; | ||||
|  | ||||
| import fr.iut_fbleau.but3.dev62.mylibrary.order.entity.Address; | ||||
| import fr.iut_fbleau.but3.dev62.mylibrary.order.entity.Order; | ||||
| import fr.iut_fbleau.but3.dev62.mylibrary.order.exception.NotValidOrderException; | ||||
|  | ||||
| public class OrderValidator { | ||||
|  | ||||
|     public static void validate(Order order) { | ||||
|         if (order == null) { | ||||
|             throw new NotValidOrderException("La commande ne peut pas être nulle"); | ||||
|         } | ||||
|         validateAddress(order); | ||||
|         validateOrderLines(order); | ||||
|         validatePrices(order); | ||||
|         validatePaymentMethod(order); | ||||
|     } | ||||
|  | ||||
|     private static void validateAddress(Order order) { | ||||
|         Address address = order.getShippingAddress(); | ||||
|         if (address == null) { | ||||
|             throw new NotValidOrderException("L'adresse de livraison est obligatoire"); | ||||
|         } | ||||
|  | ||||
|         if (isNullOrBlank(address.getStreet()) || | ||||
|             isNullOrBlank(address.getCity()) || | ||||
|             isNullOrBlank(address.getPostalCode()) || | ||||
|             isNullOrBlank(address.getCountry())) { | ||||
|             throw new NotValidOrderException("Tous les champs de l'adresse sont obligatoires"); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private static void validateOrderLines(Order order) { | ||||
|         if (order.getOrderLines() == null || order.getOrderLines().isEmpty()) { | ||||
|             throw new NotValidOrderException("La commande doit contenir au moins une ligne"); | ||||
|         } | ||||
|  | ||||
|         order.getOrderLines().forEach(line -> { | ||||
|             if (line.getQuantity() <= 0) { | ||||
|                 throw new NotValidOrderException("La quantité doit être positive"); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     private static void validatePrices(Order order) { | ||||
|         if (order.getTotalPrice() <= 0) { | ||||
|             throw new NotValidOrderException("Le prix total doit être positif"); | ||||
|         } | ||||
|         if (order.getTotalPriceToPay() <= 0) { | ||||
|             throw new NotValidOrderException("Le prix total à payer doit être positif"); | ||||
|         } | ||||
|         if (order.getTotalPriceToPay() > order.getTotalPrice()) { | ||||
|             throw new NotValidOrderException("Le prix à payer ne peut pas être supérieur au prix total"); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private static void validatePaymentMethod(Order order) { | ||||
|         if (order.getPaymentMethod() == null) { | ||||
|             throw new NotValidOrderException("Le mode de paiement ne peut pas être nul"); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private static boolean isNullOrBlank(String value) { | ||||
|         return value == null || value.trim().isEmpty(); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,19 @@ | ||||
| package fr.iut_fbleau.but3.dev62.mylibrary.review; | ||||
|  | ||||
| import lombok.AllArgsConstructor; | ||||
| import lombok.Builder; | ||||
| import lombok.Getter; | ||||
|  | ||||
| import java.util.UUID; | ||||
|  | ||||
| @Builder | ||||
| @Getter | ||||
| @AllArgsConstructor | ||||
| public class ReviewDto { | ||||
|     private final UUID reviewId; | ||||
|     private final long bookId; | ||||
|     private final String customerName; | ||||
|     private final String comment; | ||||
|     private final int rating; | ||||
| } | ||||
|  | ||||
| @@ -0,0 +1,16 @@ | ||||
| package fr.iut_fbleau.but3.dev62.mylibrary.review; | ||||
|  | ||||
| import lombok.AllArgsConstructor; | ||||
| import lombok.Builder; | ||||
| import lombok.Getter; | ||||
|  | ||||
| @Builder | ||||
| @Getter | ||||
| @AllArgsConstructor | ||||
| public class ReviewInfo { | ||||
|     private final String customerId; | ||||
|     private final long isbn; | ||||
|     private final int rating; | ||||
|     private final String comment; | ||||
| } | ||||
|  | ||||
| @@ -0,0 +1,29 @@ | ||||
| package fr.iut_fbleau.but3.dev62.mylibrary.review.converter; | ||||
|  | ||||
| import fr.iut_fbleau.but3.dev62.mylibrary.review.ReviewDto; | ||||
| import fr.iut_fbleau.but3.dev62.mylibrary.review.ReviewInfo; | ||||
| import fr.iut_fbleau.but3.dev62.mylibrary.review.entity.Review; | ||||
|  | ||||
| import java.util.UUID; | ||||
|  | ||||
| public class ReviewConverter { | ||||
|     public static ReviewDto toDto(Review review, UUID reviewId, String customerName) { | ||||
|         return ReviewDto.builder() | ||||
|                 .reviewId(reviewId) | ||||
|                 .bookId(review.getIsbn()) | ||||
|                 .customerName(customerName) | ||||
|                 .comment(review.getComment()) | ||||
|                 .rating(review.getRating()) | ||||
|                 .build(); | ||||
|     } | ||||
|  | ||||
|     public static Review toDomain(ReviewInfo info) { | ||||
|         return Review.builder() | ||||
|                 .customerId(UUID.fromString(info.getCustomerId())) | ||||
|                 .isbn(info.getIsbn()) | ||||
|                 .rating(info.getRating()) | ||||
|                 .comment(info.getComment()) | ||||
|                 .build(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -0,0 +1,19 @@ | ||||
| package fr.iut_fbleau.but3.dev62.mylibrary.review.entity; | ||||
|  | ||||
| import lombok.AllArgsConstructor; | ||||
| import lombok.Builder; | ||||
| import lombok.Getter; | ||||
| import lombok.NoArgsConstructor; | ||||
|  | ||||
| import java.util.UUID; | ||||
|  | ||||
| @Builder | ||||
| @Getter | ||||
| @AllArgsConstructor | ||||
| @NoArgsConstructor | ||||
| public class Review { | ||||
|     private UUID customerId; | ||||
|     private long isbn; | ||||
|     private int rating; | ||||
|     private String comment; | ||||
| } | ||||
| @@ -0,0 +1,8 @@ | ||||
| package fr.iut_fbleau.but3.dev62.mylibrary.review.exception; | ||||
|  | ||||
| public class InvalidReviewRatingException extends RuntimeException { | ||||
|     public InvalidReviewRatingException(String message) { | ||||
|         super(message); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -0,0 +1,8 @@ | ||||
| package fr.iut_fbleau.but3.dev62.mylibrary.review.exception; | ||||
|  | ||||
| public class ReviewAlreadyExistsException extends RuntimeException { | ||||
|     public ReviewAlreadyExistsException(String message) { | ||||
|         super(message); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -0,0 +1,8 @@ | ||||
| package fr.iut_fbleau.but3.dev62.mylibrary.review.exception; | ||||
|  | ||||
| public class ReviewNotFoundException extends RuntimeException { | ||||
|     public ReviewNotFoundException(String message) { | ||||
|         super(message); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -0,0 +1,44 @@ | ||||
| package fr.iut_fbleau.but3.dev62.mylibrary.review.repository; | ||||
|  | ||||
| import fr.iut_fbleau.but3.dev62.mylibrary.review.entity.Review; | ||||
|  | ||||
| import java.util.*; | ||||
| import java.util.stream.Collectors; | ||||
|  | ||||
| public class ReviewRepository { | ||||
|     private final List<Review> reviews = new ArrayList<>(); | ||||
|  | ||||
|     public void save(Review review) { | ||||
|         reviews.add(review); | ||||
|     } | ||||
|  | ||||
|     public List<Review> findAll() { | ||||
|         return new ArrayList<>(reviews); | ||||
|     } | ||||
|  | ||||
|     public List<Review> findByIsbn(long isbn) { | ||||
|         return reviews.stream().filter(r -> r.getIsbn() == isbn).collect(Collectors.toList()); | ||||
|     } | ||||
|  | ||||
|     public List<Review> findByCustomerId(UUID customerId) { | ||||
|         return reviews.stream().filter(r -> r.getCustomerId().equals(customerId)).collect(Collectors.toList()); | ||||
|     } | ||||
|  | ||||
|     public boolean existsByCustomerIdAndIsbn(UUID customerId, long isbn) { | ||||
|         return reviews.stream().anyMatch(r -> r.getCustomerId().equals(customerId) && r.getIsbn() == isbn); | ||||
|     } | ||||
|  | ||||
|     public void deleteByCustomerIdAndIsbn(UUID customerId, long isbn) { | ||||
|         reviews.removeIf(r -> r.getCustomerId().equals(customerId) && r.getIsbn() == isbn); | ||||
|     } | ||||
|  | ||||
|     public void update(Review review) { | ||||
|         deleteByCustomerIdAndIsbn(review.getCustomerId(), review.getIsbn()); | ||||
|         save(review); | ||||
|     } | ||||
|  | ||||
|     public void deleteAll() { | ||||
|         reviews.clear(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -0,0 +1,68 @@ | ||||
| package fr.iut_fbleau.but3.dev62.mylibrary.review.usecase; | ||||
|  | ||||
| import fr.iut_fbleau.but3.dev62.mylibrary.book.repository.BookRepository; | ||||
| import fr.iut_fbleau.but3.dev62.mylibrary.customer.repository.CustomerRepository; | ||||
| import fr.iut_fbleau.but3.dev62.mylibrary.review.entity.Review; | ||||
| import fr.iut_fbleau.but3.dev62.mylibrary.review.exception.InvalidReviewRatingException; | ||||
| import fr.iut_fbleau.but3.dev62.mylibrary.review.exception.ReviewAlreadyExistsException; | ||||
| import fr.iut_fbleau.but3.dev62.mylibrary.review.exception.ReviewNotFoundException; | ||||
| import fr.iut_fbleau.but3.dev62.mylibrary.book.exception.BookNotFoundException; | ||||
| import fr.iut_fbleau.but3.dev62.mylibrary.customer.exception.CustomerNotFoundException; | ||||
| import fr.iut_fbleau.but3.dev62.mylibrary.review.repository.ReviewRepository; | ||||
| import fr.iut_fbleau.but3.dev62.mylibrary.review.validator.ReviewValidator; | ||||
|  | ||||
| import java.util.List; | ||||
| import java.util.UUID; | ||||
|  | ||||
| public class ReviewUseCase { | ||||
|     private final ReviewRepository reviewRepository; | ||||
|     private final BookRepository bookRepository; | ||||
|     private final CustomerRepository customerRepository; | ||||
|  | ||||
|     public ReviewUseCase(ReviewRepository reviewRepository, BookRepository bookRepository, CustomerRepository customerRepository) { | ||||
|         this.reviewRepository = reviewRepository; | ||||
|         this.bookRepository = bookRepository; | ||||
|         this.customerRepository = customerRepository; | ||||
|     } | ||||
|  | ||||
|     public void submitReview(Review review) throws CustomerNotFoundException { | ||||
|         ReviewValidator.validate(review); | ||||
|  | ||||
|         if (!bookRepository.existsByIsbn(String.valueOf(review.getIsbn()))) { | ||||
|             throw new BookNotFoundException("Book not found: " + review.getIsbn()); | ||||
|         } | ||||
|  | ||||
|         if (!customerRepository.existsById(review.getCustomerId())) { | ||||
|             throw new CustomerNotFoundException(review.getCustomerId()); | ||||
|         } | ||||
|  | ||||
|         if (reviewRepository.existsByCustomerIdAndIsbn(review.getCustomerId(), review.getIsbn())) { | ||||
|             throw new ReviewAlreadyExistsException("Review already exists for this customer and book."); | ||||
|         } | ||||
|  | ||||
|         reviewRepository.save(review); | ||||
|     } | ||||
|  | ||||
|     public List<Review> getReviewsByBook(long isbn) { | ||||
|         return reviewRepository.findByIsbn(isbn); | ||||
|     } | ||||
|  | ||||
|     public List<Review> getReviewsByCustomer(UUID customerId) { | ||||
|         return reviewRepository.findByCustomerId(customerId); | ||||
|     } | ||||
|  | ||||
|     public void updateReview(Review review) { | ||||
|         ReviewValidator.validate(review); | ||||
|         if (!reviewRepository.existsByCustomerIdAndIsbn(review.getCustomerId(), review.getIsbn())) { | ||||
|             throw new ReviewNotFoundException("Review not found for this customer and book."); | ||||
|         } | ||||
|         reviewRepository.update(review); | ||||
|     } | ||||
|  | ||||
|     public void deleteReview(long isbn, UUID customerId) { | ||||
|         if (!reviewRepository.existsByCustomerIdAndIsbn(customerId, isbn)) { | ||||
|             throw new ReviewNotFoundException("Review not found for this customer and book."); | ||||
|         } | ||||
|         reviewRepository.deleteByCustomerIdAndIsbn(customerId, isbn); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,23 @@ | ||||
| package fr.iut_fbleau.but3.dev62.mylibrary.review.validator; | ||||
|  | ||||
| import fr.iut_fbleau.but3.dev62.mylibrary.review.entity.Review; | ||||
|  | ||||
| public class ReviewValidator { | ||||
|     public static void validate(Review review) { | ||||
|         if (review == null) { | ||||
|             throw new IllegalArgumentException("Review cannot be null"); | ||||
|         } | ||||
|         if (review.getRating() < 1 || review.getRating() > 5) { | ||||
|             throw new fr.iut_fbleau.but3.dev62.mylibrary.review.exception.InvalidReviewRatingException("Rating must be between 1 and 5"); | ||||
|         } | ||||
|         if (review.getComment() == null || review.getComment().trim().isEmpty()) { | ||||
|             throw new IllegalArgumentException("Comment cannot be empty"); | ||||
|         } | ||||
|         if (review.getCustomerId() == null) { | ||||
|             throw new IllegalArgumentException("CustomerId cannot be null"); | ||||
|         } | ||||
|         if (review.getIsbn() <= 0) { | ||||
|             throw new IllegalArgumentException("ISBN must be positive"); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,15 @@ | ||||
| package fr.iut_fbleau.but3.dev62.mylibrary.subscription; | ||||
|  | ||||
| import lombok.Builder; | ||||
| import lombok.Getter; | ||||
| import java.util.UUID; | ||||
|  | ||||
| @Builder | ||||
| @Getter | ||||
| public class SubscriptionDTO { | ||||
|     private final UUID id; | ||||
|     private final UUID customerId; | ||||
|     private final Integer duration; | ||||
|     private final String paymentMethod; | ||||
|     private final String debutDate; | ||||
| } | ||||
| @@ -0,0 +1,7 @@ | ||||
| package fr.iut_fbleau.but3.dev62.mylibrary.subscription; | ||||
|  | ||||
| import java.util.UUID; | ||||
|  | ||||
| public record SubscriptionInfo(UUID customerId, Integer duration, String paymentMethod) { | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,31 @@ | ||||
| package fr.iut_fbleau.but3.dev62.mylibrary.subscription.converter; | ||||
|  | ||||
|  | ||||
| import fr.iut_fbleau.but3.dev62.mylibrary.subscription.SubscriptionDTO; | ||||
| import fr.iut_fbleau.but3.dev62.mylibrary.subscription.SubscriptionInfo; | ||||
| import fr.iut_fbleau.but3.dev62.mylibrary.subscription.entity.Subscription; | ||||
|  | ||||
| public final class SubscriptionConverter { | ||||
|     private SubscriptionConverter() { | ||||
|  | ||||
|     } | ||||
|  | ||||
|     public static Subscription toDomain(SubscriptionInfo newSubscription) { | ||||
|         return Subscription.builder() | ||||
|                 .customerId(newSubscription.customerId()) | ||||
|                 .duration(newSubscription.duration()) | ||||
|                 .paymentMethod(newSubscription.paymentMethod()) | ||||
|                 .build(); | ||||
|     } | ||||
|  | ||||
|     public static SubscriptionDTO toDTO(Subscription subscription) { | ||||
|         return SubscriptionDTO.builder() | ||||
|                 .id(subscription.getId()) | ||||
|                 .customerId(subscription.getCustomerId()) | ||||
|                 .duration(subscription.getDuration()) | ||||
|                 .paymentMethod(subscription.getPaymentMethod()) | ||||
|                 .debutDate(subscription.getDebutDate()) | ||||
|                 .build(); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,24 @@ | ||||
| package fr.iut_fbleau.but3.dev62.mylibrary.subscription.entity; | ||||
|  | ||||
| import java.util.UUID; | ||||
| import lombok.Builder; | ||||
| import lombok.Getter; | ||||
| import java.time.LocalDate; | ||||
|  | ||||
| @Builder | ||||
| @Getter | ||||
| public class Subscription { | ||||
|     private  UUID id; | ||||
|     private  UUID customerId; | ||||
|     private  Integer duration; | ||||
|     private  String paymentMethod; | ||||
|     private  String debutDate; | ||||
|  | ||||
|     public void setRandomUUID() { | ||||
|         this.id = UUID.randomUUID(); | ||||
|     } | ||||
|  | ||||
|     public void setDebutDate(String debutDate) { | ||||
|         this.debutDate = LocalDate.now().toString(); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,8 @@ | ||||
| package fr.iut_fbleau.but3.dev62.mylibrary.subscription.exception; | ||||
|  | ||||
| public class NotValidSubscriptionException extends Exception { | ||||
|  | ||||
|     public NotValidSubscriptionException(String message) { | ||||
|         super(message); | ||||
|     } | ||||
| } | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user