diff --git a/src/main/java/net/unir/missi/desarrollowebfullstack/bookabook/operador/config/LoanRequestToLoanConverter.java b/src/main/java/net/unir/missi/desarrollowebfullstack/bookabook/operador/config/LoanRequestToLoanConverter.java index 3a4a860..8964171 100644 --- a/src/main/java/net/unir/missi/desarrollowebfullstack/bookabook/operador/config/LoanRequestToLoanConverter.java +++ b/src/main/java/net/unir/missi/desarrollowebfullstack/bookabook/operador/config/LoanRequestToLoanConverter.java @@ -12,8 +12,6 @@ public Loan convert(LoanRequest source) { return Loan.builder() .bookId(source.getBookId()) .clientId(source.getClientId()) - .loanDate(source.getLoanDate()) - .dueDate(source.getDueDate()) .returnDate(source.getReturnDate()) .isReturned(source.getIsReturned()) .renewalCount(source.getRenewalCount()) diff --git a/src/main/java/net/unir/missi/desarrollowebfullstack/bookabook/operador/controller/LoansController.java b/src/main/java/net/unir/missi/desarrollowebfullstack/bookabook/operador/controller/LoansController.java index a596104..6aa492b 100644 --- a/src/main/java/net/unir/missi/desarrollowebfullstack/bookabook/operador/controller/LoansController.java +++ b/src/main/java/net/unir/missi/desarrollowebfullstack/bookabook/operador/controller/LoansController.java @@ -1,12 +1,18 @@ package net.unir.missi.desarrollowebfullstack.bookabook.operador.controller; +import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import net.unir.missi.desarrollowebfullstack.bookabook.operador.exceptions.BadParametersException; +import net.unir.missi.desarrollowebfullstack.bookabook.operador.exceptions.EntityInvalidOperationException; import net.unir.missi.desarrollowebfullstack.bookabook.operador.exceptions.EntityNotFoundException; import net.unir.missi.desarrollowebfullstack.bookabook.operador.model.api.LoanRequest; import net.unir.missi.desarrollowebfullstack.bookabook.operador.model.api.LoanResponse; +import net.unir.missi.desarrollowebfullstack.bookabook.operador.model.sql.Loan; import net.unir.missi.desarrollowebfullstack.bookabook.operador.service.LoanService; import org.springframework.beans.factory.annotation.Autowired; @@ -14,7 +20,9 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import java.time.LocalDate; import java.util.*; +import java.util.logging.Logger; @RestController @RequiredArgsConstructor @@ -24,20 +32,27 @@ public class LoansController { private final LoanService service; @GetMapping("/loans") + @Operation( + operationId = "Obtener préstamos", + description = "Operacion de lectura", + summary = "Se devuelve una lista de todos los préstamos almacenados en la base de datos.") + @ApiResponse( + responseCode = "200", + content = @Content(mediaType = "application/json", schema = @Schema(implementation = Loan.class))) public ResponseEntity> getLoans( - @Parameter(name="bookId", example = "") + @Parameter(name="bookId", description = "Id del Libro préstado") @RequestParam(required = false) Long bookId, - @Parameter(name="clientId", example = "") + @Parameter(name="clientId", description = "Id del Cliente al que se le realizó el préstamo") @RequestParam(required = false) Long clientId, - @Parameter(name="loanDate", example = "") - @RequestParam(required = false)Date loanDate, - @Parameter(name="returnDate", example = "") - @RequestParam(required = false)Date returnDate, - @Parameter(name="dueDate", example = "") - @RequestParam(required = false)Date dueDate, - @Parameter(name="isReturned", example = "") + @Parameter(name="loanDate", description = "Fecha de registro del préstamo") + @RequestParam(required = false) LocalDate loanDate, + @Parameter(name="returnDate", description = "Fecha en que el libro fue devuelto") + @RequestParam(required = false)LocalDate returnDate, + @Parameter(name="dueDate", description = "Fecha de vencimiento del préstamo") + @RequestParam(required = false)LocalDate dueDate, + @Parameter(name="isReturned", description = "Indica si el libro ha sido devuelto") @RequestParam(required = false)Boolean isReturned, - @Parameter(name="renewalCount", example = "") + @Parameter(name="renewalCount", description = "Cantidad de renovaciones del préstamo") @RequestParam(required = false)Integer renewalCount ) { @@ -61,6 +76,17 @@ public ResponseEntity> getLoans( } @GetMapping("/loans/{id}") + @Operation( + operationId = "Obtener un préstamo", + description = "Operacion de lectura", + summary = "Se devuelve un préstamo a partir de su identificador.") + @ApiResponse( + responseCode = "200", + content = @Content(mediaType = "application/json", schema = @Schema(implementation = Loan.class))) + @ApiResponse( + responseCode = "404", + content = @Content(mediaType = "application/json", schema = @Schema()), + description = "No se ha encontrado el préstamo con el identificador indicado.") public ResponseEntity getLoan(@PathVariable String id) { try { @@ -82,6 +108,25 @@ public ResponseEntity getLoan(@PathVariable String id) { } @PostMapping("/loans") + @Operation( + operationId = "Crear un préstamo", + description = "Operacion de escritura", + summary = "Se crea un préstamo a partir de sus datos.", + requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody( + description = "Datos del préstamo a crear.", + required = true, + content = @Content(mediaType = "application/json", schema = @Schema(implementation = LoanRequest.class)))) + @ApiResponse( + responseCode = "201", + content = @Content(mediaType = "application/json", schema = @Schema(implementation = LoanResponse.class))) + @ApiResponse( + responseCode = "400", + content = @Content(mediaType = "application/json", schema = @Schema()), + description = "Datos incorrectos introducidos.") + @ApiResponse( + responseCode = "404", + content = @Content(mediaType = "application/json", schema = @Schema()), + description = "Id del Libro O Id del Cliente no existen.") public ResponseEntity addLoan(@RequestBody LoanRequest loanRequest) { try { @@ -100,13 +145,28 @@ public ResponseEntity addLoan(@RequestBody LoanRequest loanRequest { return ResponseEntity.badRequest().build(); } + catch(EntityInvalidOperationException e) { + return ResponseEntity.badRequest().build(); + } catch (Exception e) { + Logger.getGlobal().warning("Error: " + e.getMessage()); return ResponseEntity.internalServerError().build(); } } @DeleteMapping("/loans/{id}") + @Operation( + operationId = "Eliminar un préstamo", + description = "Operacion de escritura", + summary = "Se elimina un préstamo a partir de su identificador.") + @ApiResponse( + responseCode = "200", + content = @Content(mediaType = "application/json", schema = @Schema(implementation = LoanResponse.class))) + @ApiResponse( + responseCode = "404", + content = @Content(mediaType = "application/json", schema = @Schema()), + description = "No se ha encontrado el préstamo con el identificador indicado.") public ResponseEntity deleteLoan(@PathVariable String id) { try { @@ -123,14 +183,30 @@ public ResponseEntity deleteLoan(@PathVariable String id) { } @PatchMapping("/loans/{id}") + @Operation( + operationId = "Modificar parcialmente un préstamo", + description = "RFC 7386. Operacion de escritura", + summary = "RFC 7386. Se modifica parcialmente un préstamo.", + requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody( + description = "Datos del préstamo a modificar.", + required = true, + content = @Content(mediaType = "application/merge-patch+json", schema = @Schema(implementation = LoanRequest.class)))) + @ApiResponse( + responseCode = "200", + content = @Content(mediaType = "application/json", schema = @Schema(implementation = LoanResponse.class))) + @ApiResponse( + responseCode = "400", + content = @Content(mediaType = "application/json", schema = @Schema()), + description = "Datos incorrectos introducidos.") public ResponseEntity patchLoan(@RequestBody LoanRequest loanRequest, @PathVariable String id) { try { + Logger.getGlobal().warning("Entra al controller" + loanRequest); if (loanRequest == null) { return ResponseEntity.badRequest().build(); } - LoanResponse newLoan = this.service.modifyLoan(loanRequest, Long.valueOf(id)); + LoanResponse newLoan = this.service.modifyLoan(loanRequest.getIsReturned(), Long.valueOf(id)); return ResponseEntity.status(HttpStatus.CREATED).body(newLoan); } catch (EntityNotFoundException e) @@ -147,7 +223,18 @@ public ResponseEntity patchLoan(@RequestBody LoanRequest loanReque } } - @GetMapping("/loans/client/{clientId}") + @GetMapping("/clients/{clientId}/loans") + @Operation( + operationId = "Obtener los préstamos de un cliente.", + description = "Operacion de lectura", + summary = "Se devuelve una lista de préstamos relacionados a un cliente.") + @ApiResponse( + responseCode = "200", + content = @Content(mediaType = "application/json", schema = @Schema(implementation = LoanResponse.class))) + @ApiResponse( + responseCode = "404", + content = @Content(mediaType = "application/json", schema = @Schema()), + description = "No se han encontrado préstamos con el identificador de clienteindicado.") public ResponseEntity> getLoanByClientId(@PathVariable String clientId) { try { diff --git a/src/main/java/net/unir/missi/desarrollowebfullstack/bookabook/operador/exceptions/EntityInvalidOperationException.java b/src/main/java/net/unir/missi/desarrollowebfullstack/bookabook/operador/exceptions/EntityInvalidOperationException.java new file mode 100644 index 0000000..e36cab1 --- /dev/null +++ b/src/main/java/net/unir/missi/desarrollowebfullstack/bookabook/operador/exceptions/EntityInvalidOperationException.java @@ -0,0 +1,7 @@ +package net.unir.missi.desarrollowebfullstack.bookabook.operador.exceptions; + +import jakarta.ws.rs.NotAllowedException; + +public class EntityInvalidOperationException extends RuntimeException { + public EntityInvalidOperationException(String errorMessage, Throwable err) { super(errorMessage, err); } +} diff --git a/src/main/java/net/unir/missi/desarrollowebfullstack/bookabook/operador/model/api/LoanDto.java b/src/main/java/net/unir/missi/desarrollowebfullstack/bookabook/operador/model/api/LoanDto.java index 24cf40c..20d9b2c 100644 --- a/src/main/java/net/unir/missi/desarrollowebfullstack/bookabook/operador/model/api/LoanDto.java +++ b/src/main/java/net/unir/missi/desarrollowebfullstack/bookabook/operador/model/api/LoanDto.java @@ -2,6 +2,7 @@ import lombok.*; +import java.time.LocalDate; import java.util.Date; @Getter @@ -13,9 +14,9 @@ public class LoanDto { private Long bookId; private Long clientId; - private Date loanDate; - private Date returnDate; - private Date dueDate; + private LocalDate loanDate; + private LocalDate returnDate; + private LocalDate dueDate; private Boolean isReturned; private Integer renewalCount; } diff --git a/src/main/java/net/unir/missi/desarrollowebfullstack/bookabook/operador/model/api/LoanRequest.java b/src/main/java/net/unir/missi/desarrollowebfullstack/bookabook/operador/model/api/LoanRequest.java index 3174e37..defea43 100644 --- a/src/main/java/net/unir/missi/desarrollowebfullstack/bookabook/operador/model/api/LoanRequest.java +++ b/src/main/java/net/unir/missi/desarrollowebfullstack/bookabook/operador/model/api/LoanRequest.java @@ -1,6 +1,7 @@ package net.unir.missi.desarrollowebfullstack.bookabook.operador.model.api; import lombok.*; +import java.time.LocalDate; import java.util.Date; @Getter @@ -12,9 +13,7 @@ public class LoanRequest { private Long bookId; private Long clientId; - private Date loanDate; - private Date returnDate; - private Date dueDate; - private Boolean isReturned; - private Integer renewalCount; + private LocalDate returnDate = null; + private Boolean isReturned = false; + private Integer renewalCount = 0; } diff --git a/src/main/java/net/unir/missi/desarrollowebfullstack/bookabook/operador/model/api/LoanResponse.java b/src/main/java/net/unir/missi/desarrollowebfullstack/bookabook/operador/model/api/LoanResponse.java index d40fc88..1121931 100644 --- a/src/main/java/net/unir/missi/desarrollowebfullstack/bookabook/operador/model/api/LoanResponse.java +++ b/src/main/java/net/unir/missi/desarrollowebfullstack/bookabook/operador/model/api/LoanResponse.java @@ -2,6 +2,7 @@ import lombok.*; +import java.time.LocalDate; import java.util.Date; @Getter @@ -14,9 +15,9 @@ public class LoanResponse { private Long id; private Long bookId; private Long clientId; - private Date loanDate; - private Date returnDate; - private Date dueDate; + private LocalDate loanDate; + private LocalDate returnDate; + private LocalDate dueDate; private Boolean isReturned; private Integer renewalCount; } diff --git a/src/main/java/net/unir/missi/desarrollowebfullstack/bookabook/operador/model/sql/Loan.java b/src/main/java/net/unir/missi/desarrollowebfullstack/bookabook/operador/model/sql/Loan.java index 9f15221..9962fbe 100644 --- a/src/main/java/net/unir/missi/desarrollowebfullstack/bookabook/operador/model/sql/Loan.java +++ b/src/main/java/net/unir/missi/desarrollowebfullstack/bookabook/operador/model/sql/Loan.java @@ -1,5 +1,6 @@ package net.unir.missi.desarrollowebfullstack.bookabook.operador.model.sql; +import java.time.LocalDate; import java.util.Date; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -32,11 +33,11 @@ public class Loan { @Column(name = "clientId") private Long clientId; @Column(name = "loanDate") - private Date loanDate; + private LocalDate loanDate; @Column(name = "returnDate") - private Date returnDate; + private LocalDate returnDate; @Column(name = "dueDate") - private Date dueDate; + private LocalDate dueDate; @Column(name = "isReturned") private Boolean isReturned; @Column(name = "renewalCount") diff --git a/src/main/java/net/unir/missi/desarrollowebfullstack/bookabook/operador/repository/LoanJpaRepository.java b/src/main/java/net/unir/missi/desarrollowebfullstack/bookabook/operador/repository/LoanJpaRepository.java index 646cbab..7feb005 100644 --- a/src/main/java/net/unir/missi/desarrollowebfullstack/bookabook/operador/repository/LoanJpaRepository.java +++ b/src/main/java/net/unir/missi/desarrollowebfullstack/bookabook/operador/repository/LoanJpaRepository.java @@ -10,4 +10,6 @@ @Repository public interface LoanJpaRepository extends JpaRepository, JpaSpecificationExecutor { List findByClientId(Long clientId); + List findByBookIdAndIsReturned(Long bookId, boolean isRturned); + } diff --git a/src/main/java/net/unir/missi/desarrollowebfullstack/bookabook/operador/repository/LoanRepository.java b/src/main/java/net/unir/missi/desarrollowebfullstack/bookabook/operador/repository/LoanRepository.java index 24a1fed..4f40afc 100644 --- a/src/main/java/net/unir/missi/desarrollowebfullstack/bookabook/operador/repository/LoanRepository.java +++ b/src/main/java/net/unir/missi/desarrollowebfullstack/bookabook/operador/repository/LoanRepository.java @@ -9,6 +9,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; +import java.time.LocalDate; import java.util.Date; import java.util.List; import java.util.Optional; @@ -26,6 +27,7 @@ public LoanRepository(LoanJpaRepository loanJpaRepository) { public List findAll() { return loanJpaRepository.findAll(); } public Loan findById(Long id) { return loanJpaRepository.findById(id).orElse(null); } public List findByClientId(Long clientId){ return loanJpaRepository.findByClientId(clientId); } + public List findByBookIdAndIsReturned(Long bookId, boolean isRturned){ return loanJpaRepository.findByBookIdAndIsReturned(bookId, isRturned); } public Loan save(Loan loan){ return loanJpaRepository.save(loan); } public Loan delete(Loan loan) { @@ -34,7 +36,7 @@ public Loan delete(Loan loan) return l.get(); } - public List search(Long bookId, Long clientId, Date loanDate, Date returnDate, Date dueDate, Boolean isReturned, Integer renewalCount) { + public List search(Long bookId, Long clientId, LocalDate loanDate, LocalDate returnDate, LocalDate dueDate, Boolean isReturned, Integer renewalCount) { SearchCriteria spec = new SearchCriteria<>(); if(bookId != null) { diff --git a/src/main/java/net/unir/missi/desarrollowebfullstack/bookabook/operador/service/ILoanService.java b/src/main/java/net/unir/missi/desarrollowebfullstack/bookabook/operador/service/ILoanService.java index 430fb72..a0976f8 100644 --- a/src/main/java/net/unir/missi/desarrollowebfullstack/bookabook/operador/service/ILoanService.java +++ b/src/main/java/net/unir/missi/desarrollowebfullstack/bookabook/operador/service/ILoanService.java @@ -3,16 +3,17 @@ import net.unir.missi.desarrollowebfullstack.bookabook.operador.model.api.LoanRequest; import net.unir.missi.desarrollowebfullstack.bookabook.operador.model.api.LoanResponse; +import java.time.LocalDate; import java.util.Date; import java.util.List; public interface ILoanService { // CRUD - List getAllLoans(Long bookId, Long clientId, Date loanDate, Date returnDate, Date dueDate, Boolean isReturned, Integer renewalCount); + List getAllLoans(Long bookId, Long clientId, LocalDate loanDate, LocalDate returnDate, LocalDate dueDate, Boolean isReturned, Integer renewalCount); LoanResponse createLoan(LoanRequest request); LoanResponse getLoanById(Long id); LoanResponse modifyAllLoanData(LoanRequest loan, Long id); - LoanResponse modifyLoan(LoanRequest loan, Long id); + LoanResponse modifyLoan(boolean isReturned, Long id); LoanResponse deleteLoan(String id); // SPECIALIZATIONS diff --git a/src/main/java/net/unir/missi/desarrollowebfullstack/bookabook/operador/service/LoanService.java b/src/main/java/net/unir/missi/desarrollowebfullstack/bookabook/operador/service/LoanService.java index 2bf495b..b96f39d 100644 --- a/src/main/java/net/unir/missi/desarrollowebfullstack/bookabook/operador/service/LoanService.java +++ b/src/main/java/net/unir/missi/desarrollowebfullstack/bookabook/operador/service/LoanService.java @@ -7,6 +7,7 @@ import net.unir.missi.desarrollowebfullstack.bookabook.operador.config.LoanRequestToLoanConverter; import net.unir.missi.desarrollowebfullstack.bookabook.operador.config.LoanToLoanResponseConverter; import net.unir.missi.desarrollowebfullstack.bookabook.operador.exceptions.BadParametersException; +import net.unir.missi.desarrollowebfullstack.bookabook.operador.exceptions.EntityInvalidOperationException; import net.unir.missi.desarrollowebfullstack.bookabook.operador.exceptions.EntityNotFoundException; import net.unir.missi.desarrollowebfullstack.bookabook.operador.model.api.BookResponse; import net.unir.missi.desarrollowebfullstack.bookabook.operador.model.api.ClientResponse; @@ -19,6 +20,7 @@ import org.springframework.stereotype.Service; import java.awt.print.Book; +import java.time.LocalDate; import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -51,7 +53,7 @@ public class LoanService implements ILoanService { // CRUD @Override - public List getAllLoans(Long bookId, Long clientId, Date loanDate, Date returnDate, Date dueDate, Boolean isReturned, Integer renewalCount) { + public List getAllLoans(Long bookId, Long clientId, LocalDate loanDate, LocalDate returnDate, LocalDate dueDate, Boolean isReturned, Integer renewalCount) { Loan loan = new Loan(null, bookId, clientId, loanDate, returnDate, dueDate, isReturned, renewalCount); List loanList; if (this.isValidSyntaxLoanForNulls(loan)) { @@ -75,25 +77,30 @@ public LoanResponse createLoan(LoanRequest request) { throw new BadParametersException("One or more parameters of the request are wrong", null); } - Logger.getGlobal().warning("hasta aki"); // If loan referencing non-existing books throw 404 if (! isExistingBook(loan.getBookId().toString())) { throw new EntityNotFoundException("Book id " + loan.getBookId() + " does not exist.", null); } - - Logger.getGlobal().warning("hasta aki existe book"); // If loan referencing non-existing clients throw 404 if(! isExistingClient(loan.getClientId().toString())) { throw new EntityNotFoundException("Client id " + loan.getClientId() + " does not exist.", null); } - Logger.getGlobal().warning("hasta aki existe client"); + List foundLoanedBook = loanRepository.findByBookIdAndIsReturned(request.getBookId(), false); + Logger.getGlobal().warning("Book Count: " + foundLoanedBook.stream().count()); + + if((long) foundLoanedBook.size() != 0) { + Logger.getGlobal().warning("Error: Book " + request.getBookId() + " cannot be loaned because it's already loaned to other client"); + throw new EntityInvalidOperationException("Error: Book " + request.getBookId() + " cannot be loaned because it's already loaned to other client", null); + } + + loan.setLoanDate(LocalDate.now()); + loan.setDueDate(loan.getLoanDate().plusDays(7)); // Implicit else: valid loan is saved in the DB Loan createdLoan = loanRepository.save(loan); - Logger.getGlobal().warning("hasta aki ya se ha guardao"); return this.loanToLoanResponseConverter.convert(createdLoan); } @@ -151,20 +158,12 @@ public LoanResponse modifyAllLoanData(LoanRequest loanRequest, Long id) { } @Override - public LoanResponse modifyLoan(LoanRequest loanRequest, Long id) { - Loan loan = this.loanRequestToLoanConverter.convert(loanRequest); - + public LoanResponse modifyLoan(boolean isReturned, Long id) { // If loan has null values or wrong values throw 400 - if ( - ! this.isValidSyntaxLoanForNulls(Objects.requireNonNull(loan)) - || ! this.isValidSyntaxLoanForZeroes(loan)) - { - throw new BadParametersException("One or more parameters of the request are wrong", null); - } // If loan does not exist throw 404 - Loan loanMatched = loanRepository.findById(id); - if (loanMatched == null) + Loan loan = loanRepository.findById(id); + if (loan == null) { throw new EntityNotFoundException("Loan with id " + id.toString() + " does not exist", null); } @@ -181,14 +180,20 @@ public LoanResponse modifyLoan(LoanRequest loanRequest, Long id) { throw new EntityNotFoundException("Client id " + loan.getClientId() + " does not exist.", null); } - // Update values of matched loan with values of received request - Loan mergedLoan = this.loanMergerNonEmpty.merge(loanMatched, loan); + if(isReturned) { + loan.setReturnDate(LocalDate.now()); + loan.setIsReturned(true); + } + else if(!loan.getIsReturned()) { + loan.setDueDate(LocalDate.now().plusDays(7)); + loan.setRenewalCount(loan.getRenewalCount() + 1); + } // Save updated loan - mergedLoan = this.loanRepository.save(mergedLoan); + this.loanRepository.save(loan); // Return response - return this.loanToLoanResponseConverter.convert(mergedLoan); + return this.loanToLoanResponseConverter.convert(loan); } @Override @@ -222,7 +227,6 @@ public List getLoansByClientId(Long clientId) { private boolean isExistingBook(String id) { try { ResponseEntity book = buscadorClient.getBook(id); - Logger.getGlobal().warning("MyText"); return book != null; } catch(Exception e) { @@ -230,10 +234,20 @@ private boolean isExistingBook(String id) { } } + private BookResponse getBook(String id) { + try { + ResponseEntity book = buscadorClient.getBook(id); + BookResponse response = book.getBody(); + return response; + } + catch(Exception e) { + return null; + } + } + private boolean isExistingClient(String id) { try { ResponseEntity client = buscadorClient.getClient(id); - Logger.getGlobal().warning("MyText"); return client != null; } catch(Exception e) { @@ -243,13 +257,7 @@ private boolean isExistingClient(String id) { private boolean isValidSyntaxLoanForNulls(Loan loan) { - return loan.getBookId() != null - && loan.getClientId() != null - && loan.getLoanDate() != null - && loan.getReturnDate() != null - && loan.getDueDate() != null - && loan.getIsReturned() != null - && loan.getRenewalCount() != null; + return loan.getBookId() != null && loan.getClientId() != null; } private boolean isValidSyntaxLoanForZeroes(Loan loan)