Added task04

This commit is contained in:
magoeke 2023-04-17 01:30:56 +02:00
parent 48fa43ea2d
commit d586d1557c
26 changed files with 1163 additions and 0 deletions

33
task04/.gitignore vendored Normal file
View file

@ -0,0 +1,33 @@
README.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/

89
task04/pom.xml Normal file
View file

@ -0,0 +1,89 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>de.hststuttgart.vs</groupId>
<artifactId>task04</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>task04</name>
<description>task04</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-hateoas</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>webjars-locator</artifactId>
<version>0.45</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>hal-explorer</artifactId>
<version>1.2.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.restdocs</groupId>
<artifactId>spring-restdocs-mockmvc</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.asciidoctor</groupId>
<artifactId>asciidoctor-maven-plugin</artifactId>
<version>2.2.1</version>
<executions>
<execution>
<id>generate-docs</id>
<phase>prepare-package</phase>
<goals>
<goal>process-asciidoc</goal>
</goals>
<configuration>
<backend>html</backend>
<doctype>book</doctype>
<sourceDirectory>src/test/resources/templates</sourceDirectory>
<sourceDocumentName>index.adoc</sourceDocumentName>
<outputDirectory>target/generated-docs</outputDirectory>
</configuration>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.springframework.restdocs</groupId>
<artifactId>spring-restdocs-asciidoctor</artifactId>
<version>${spring-restdocs.version}</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View file

@ -0,0 +1,13 @@
package de.hststuttgart.vs.task04;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Task04Application {
public static void main(String[] args) {
SpringApplication.run(Task04Application.class, args);
}
}

View file

@ -0,0 +1,34 @@
package de.hststuttgart.vs.task04.api.v1;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import de.hststuttgart.vs.task04.api.v1.mapper.CreditNoteMapper;
import de.hststuttgart.vs.task04.bm.InvoiceController;
import de.hststuttgart.vs.task04.bm.exceptions.CreditNoteNotFound;
@RestController
@RequestMapping("/credit-notes")
public class CreditNotesAPI {
private final InvoiceController invoiceController;
public CreditNotesAPI(final InvoiceController invoiceController) {
this.invoiceController = invoiceController;
}
@GetMapping("/{creditNoteId}")
public ResponseEntity getCreditNote(@PathVariable final String creditNoteId) {
try {
final var creditNote = invoiceController.getCreditNote(creditNoteId);
final var mappedCreditNote = CreditNoteMapper.map(creditNote);
return ResponseEntity.ok(mappedCreditNote);
} catch (CreditNoteNotFound e) {
return ResponseEntity.notFound().build();
}
}
}

View file

@ -0,0 +1,104 @@
package de.hststuttgart.vs.task04.api.v1;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn;
import java.util.stream.Collectors;
import org.springframework.http.ResponseEntity;
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.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import de.hststuttgart.vs.task04.api.v1.mapper.CreditNoteMapper;
import de.hststuttgart.vs.task04.api.v1.mapper.InvoiceMapper;
import de.hststuttgart.vs.task04.api.v1.models.FullCreditNoteRequest;
import de.hststuttgart.vs.task04.api.v1.models.Invoices;
import de.hststuttgart.vs.task04.bm.InvoiceController;
import de.hststuttgart.vs.task04.bm.exceptions.CreditNoteAlreadyExists;
import de.hststuttgart.vs.task04.bm.exceptions.InvalidOffset;
import de.hststuttgart.vs.task04.bm.exceptions.InvoiceNotFound;
import de.hststuttgart.vs.task04.bm.model.PagedInvoices;
@RestController
@RequestMapping("/invoices")
public class InvoiceAPI {
private final InvoiceController invoiceController;
public InvoiceAPI(final InvoiceController invoiceController) {
this.invoiceController = invoiceController;
}
@GetMapping
public ResponseEntity getInvoices(
@RequestParam(defaultValue = "1") final int offset,
@RequestParam(defaultValue = "5") final int limit
) {
try {
final PagedInvoices pagedInvoices = invoiceController.getInvoices(offset, limit);
final var mappedInvoices =
pagedInvoices.getInvoices().stream().map(InvoiceMapper::map).collect(Collectors.toList());
final var invoices = new Invoices();
invoices.setInvoices(mappedInvoices);
// TODO 01: Add self, next and previous link
// next should only be displayed if there is a next page
// previous should only be displayed if it isn't the first page
return ResponseEntity.ok(invoices);
} catch (final InvalidOffset e) {
System.out.println("Offset was invalid");
return ResponseEntity.notFound().build();
}
}
@GetMapping("/{invoiceId}")
public ResponseEntity getInvoice(@PathVariable("invoiceId") final String invoiceId) {
try {
final var invoice = invoiceController.getInvoice(invoiceId);
final var mappedInvoice = InvoiceMapper.map(invoice);
return ResponseEntity.ok(mappedInvoice);
} catch (final InvoiceNotFound e) {
System.out.println("Invoice " + invoiceId + " not found");
return ResponseEntity.notFound().build();
}
}
@PostMapping("/{invoiceId}/full-credit-notes")
public ResponseEntity createCreditNote(
@PathVariable("invoiceId") final String invoiceId,
@RequestBody final FullCreditNoteRequest fullCreditNoteRequest) {
try {
final var creditNote = invoiceController.createCreditNotes(invoiceId, fullCreditNoteRequest.getReason());
final var mappedCreditNote = CreditNoteMapper.map(creditNote);
return ResponseEntity.ok(mappedCreditNote);
} catch (final InvoiceNotFound e) {
System.out.println("Invoice " + invoiceId + " not found");
return ResponseEntity.notFound().build();
} catch (final CreditNoteAlreadyExists e) {
System.out.println("Credit Note already exists");
return ResponseEntity.status(409).build();
}
}
@GetMapping("/{invoiceId}/credit-notes")
public ResponseEntity getCreditNotes(@PathVariable("invoiceId") final String invoiceId) {
try {
final var creditNotes = invoiceController.getCreditNotes(invoiceId);
final var mappedCreditNotes = creditNotes.stream().map(CreditNoteMapper::map).toList();
return ResponseEntity.ok(mappedCreditNotes);
} catch (final InvoiceNotFound e) {
System.out.println("Invoice " + invoiceId + " not found");
return ResponseEntity.notFound().build();
}
}
}

View file

@ -0,0 +1,33 @@
package de.hststuttgart.vs.task04.api.v1.mapper;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn;
import org.springframework.hateoas.server.mvc.WebMvcLinkBuilder;
import de.hststuttgart.vs.task04.api.v1.CreditNotesAPI;
import de.hststuttgart.vs.task04.api.v1.InvoiceAPI;
import de.hststuttgart.vs.task04.api.v1.models.CreditNoteDO;
import de.hststuttgart.vs.task04.bm.model.CreditNote;
public class CreditNoteMapper {
public static CreditNoteDO map(final CreditNote creditNote) {
final var creditNoteDO = new CreditNoteDO();
creditNoteDO.setCreditNoteId(creditNote.getCreditNoteId());
creditNoteDO.setInvoiceId(creditNote.getInvoiceId());
creditNoteDO.setOrderId(creditNote.getOrderId());
creditNoteDO.setTotalNetAmount(creditNote.getTotalNetAmount());
creditNoteDO.setTotalTaxAmount(creditNote.getTotalTaxAmount());
creditNoteDO.setTotalGrossAmount(creditNote.getTotalGrossAmount());
creditNoteDO.setCustomer(creditNote.getCustomer());
creditNoteDO.setReason(creditNote.getReason());
// TODO 02: Add a self rel and one that links to the original invoice
return creditNoteDO;
}
}

View file

@ -0,0 +1,29 @@
package de.hststuttgart.vs.task04.api.v1.mapper;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn;
import org.springframework.hateoas.server.mvc.WebMvcLinkBuilder;
import de.hststuttgart.vs.task04.api.v1.InvoiceAPI;
import de.hststuttgart.vs.task04.api.v1.models.InvoiceDO;
import de.hststuttgart.vs.task04.bm.model.Invoice;
public class InvoiceMapper {
public static InvoiceDO map(final Invoice invoice) {
final var invoiceDO = new InvoiceDO();
invoiceDO.setInvoiceId(invoice.getInvoiceId());
invoiceDO.setOrderId(invoice.getOrderId());
invoiceDO.setTotalNetAmount(invoice.getTotalNetAmount());
invoiceDO.setTotalTaxAmount(invoice.getTotalTaxAmount());
invoiceDO.setTotalGrossAmount(invoice.getTotalGrossAmount());
invoiceDO.setCustomer(invoice.getCustomer());
// TODO 03: Add self rel and one rel to either create a credit note or return a list of credit notes
return invoiceDO;
}
}

View file

@ -0,0 +1,82 @@
package de.hststuttgart.vs.task04.api.v1.models;
import java.math.BigDecimal;
import org.springframework.hateoas.RepresentationModel;
public class CreditNoteDO extends RepresentationModel<CreditNoteDO> {
private String creditNoteId;
private String invoiceId;
private String orderId;
private BigDecimal totalNetAmount;
private BigDecimal totalTaxAmount;
private BigDecimal totalGrossAmount;
private Customer customer;
private String reason;
public String getCreditNoteId() {
return creditNoteId;
}
public void setCreditNoteId(final String creditNoteId) {
this.creditNoteId = creditNoteId;
}
public String getInvoiceId() {
return invoiceId;
}
public void setInvoiceId(final String invoiceId) {
this.invoiceId = invoiceId;
}
public String getOrderId() {
return orderId;
}
public void setOrderId(final String orderId) {
this.orderId = orderId;
}
public BigDecimal getTotalNetAmount() {
return totalNetAmount;
}
public void setTotalNetAmount(final BigDecimal totalNetAmount) {
this.totalNetAmount = totalNetAmount;
}
public BigDecimal getTotalTaxAmount() {
return totalTaxAmount;
}
public void setTotalTaxAmount(final BigDecimal totalTaxAmount) {
this.totalTaxAmount = totalTaxAmount;
}
public BigDecimal getTotalGrossAmount() {
return totalGrossAmount;
}
public void setTotalGrossAmount(final BigDecimal totalGrossAmount) {
this.totalGrossAmount = totalGrossAmount;
}
public Customer getCustomer() {
return customer;
}
public void setCustomer(final Customer customer) {
this.customer = customer;
}
public String getReason() {
return reason;
}
public void setReason(final String reason) {
this.reason = reason;
}
}

View file

@ -0,0 +1,34 @@
package de.hststuttgart.vs.task04.api.v1.models;
public class Customer {
private String firstname;
private String lastname;
public String getFirstname() {
return firstname;
}
public void setFirstname(final String firstname) {
this.firstname = firstname;
}
public String getLastname() {
return lastname;
}
public void setLastname(final String lastname) {
this.lastname = lastname;
}
public Customer firstname(final String firstname) {
this.firstname = firstname;
return this;
}
public Customer lastname(final String lastname) {
this.lastname = lastname;
return this;
}
}

View file

@ -0,0 +1,15 @@
package de.hststuttgart.vs.task04.api.v1.models;
public class FullCreditNoteRequest {
private String reason;
public String getReason() {
return reason;
}
public void setReason(final String reason) {
this.reason = reason;
}
}

View file

@ -0,0 +1,64 @@
package de.hststuttgart.vs.task04.api.v1.models;
import java.math.BigDecimal;
import org.springframework.hateoas.RepresentationModel;
public class InvoiceDO extends RepresentationModel<InvoiceDO> {
private String invoiceId;
private String orderId;
private BigDecimal totalNetAmount;
private BigDecimal totalTaxAmount;
private BigDecimal totalGrossAmount;
private Customer customer;
public String getInvoiceId() {
return invoiceId;
}
public void setInvoiceId(final String invoiceId) {
this.invoiceId = invoiceId;
}
public String getOrderId() {
return orderId;
}
public void setOrderId(final String orderId) {
this.orderId = orderId;
}
public BigDecimal getTotalNetAmount() {
return totalNetAmount;
}
public void setTotalNetAmount(final BigDecimal totalNetAmount) {
this.totalNetAmount = totalNetAmount;
}
public BigDecimal getTotalTaxAmount() {
return totalTaxAmount;
}
public void setTotalTaxAmount(final BigDecimal totalTaxAmount) {
this.totalTaxAmount = totalTaxAmount;
}
public BigDecimal getTotalGrossAmount() {
return totalGrossAmount;
}
public void setTotalGrossAmount(final BigDecimal totalGrossAmount) {
this.totalGrossAmount = totalGrossAmount;
}
public Customer getCustomer() {
return customer;
}
public void setCustomer(final Customer customer) {
this.customer = customer;
}
}

View file

@ -0,0 +1,19 @@
package de.hststuttgart.vs.task04.api.v1.models;
import java.util.List;
import org.springframework.hateoas.RepresentationModel;
public class Invoices extends RepresentationModel<Invoices> {
private List<InvoiceDO> invoices;
public List<InvoiceDO> getInvoices() {
return invoices;
}
public void setInvoices(final List<InvoiceDO> invoices) {
this.invoices = invoices;
}
}

View file

@ -0,0 +1,19 @@
package de.hststuttgart.vs.task04.bm;
import java.util.List;
import de.hststuttgart.vs.task04.bm.exceptions.CreditNoteAlreadyExists;
import de.hststuttgart.vs.task04.bm.exceptions.CreditNoteNotFound;
import de.hststuttgart.vs.task04.bm.exceptions.InvoiceNotFound;
import de.hststuttgart.vs.task04.bm.model.CreditNote;
public interface CreditNoteRepository {
public CreditNote getCreditNote(final String creditNoteId) throws CreditNoteNotFound;
public List<CreditNote> getCreditNotes(final String invoiceId) throws InvoiceNotFound;
public CreditNote createCreditNotes(final String invoiceId, final String reason) throws InvoiceNotFound,
CreditNoteAlreadyExists;
}

View file

@ -0,0 +1,119 @@
package de.hststuttgart.vs.task04.bm;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.stereotype.Service;
import de.hststuttgart.vs.task04.api.v1.models.Customer;
import de.hststuttgart.vs.task04.bm.exceptions.CreditNoteAlreadyExists;
import de.hststuttgart.vs.task04.bm.exceptions.CreditNoteNotFound;
import de.hststuttgart.vs.task04.bm.exceptions.InvalidOffset;
import de.hststuttgart.vs.task04.bm.exceptions.InvoiceNotFound;
import de.hststuttgart.vs.task04.bm.model.CreditNote;
import de.hststuttgart.vs.task04.bm.model.Invoice;
import de.hststuttgart.vs.task04.bm.model.PagedInvoices;
@Service
public class InvoiceController implements InvoiceRepository, CreditNoteRepository {
private final List<Invoice> invoices;
private final List<CreditNote> creditNotes;
public InvoiceController() {
// fake some invoices
final var johnDoe = new Customer().firstname("John").lastname("Doe");
final var invoice = new Invoice()
.invoiceId("2023-04-16-0001")
.orderId("c082bcbf-550a-4b10-baeb-4047957f70a2")
.totalNetAmount(BigDecimal.valueOf(9.34))
.totalTaxAmount(BigDecimal.valueOf(0.65))
.totalGrossAmount(BigDecimal.valueOf(9.99))
.customer(johnDoe)
.creditNotePossible(true)
.creditNotes(new ArrayList<>());
invoices = new ArrayList<>();
invoices.add(invoice);
creditNotes = new ArrayList<>();
}
@Override
public PagedInvoices getInvoices(final int offset, final int limit) throws InvalidOffset {
if (offset < 1 || ((offset - 1) * limit) > invoices.size()) {
throw new InvalidOffset();
}
final var invoicePage = invoices
.stream()
.skip((offset - 1) * limit)
.limit(limit)
.collect(Collectors.toList());
final var hasNext = invoices.size() > (offset * limit);
final var hasPrevious = offset > 1;
final var pagedInvoices = new PagedInvoices();
pagedInvoices.setPrevious(hasPrevious);
pagedInvoices.setNext(hasNext);
pagedInvoices.setInvoices(invoicePage);
return pagedInvoices;
}
@Override
public Invoice getInvoice(final String invoiceId) throws InvoiceNotFound {
return invoices
.stream()
.filter(inv -> inv.getInvoiceId().equals(invoiceId))
.findFirst()
.orElseThrow(InvoiceNotFound::new);
}
@Override
public CreditNote createCreditNotes(final String invoiceId, final String reason)
throws InvoiceNotFound, CreditNoteAlreadyExists {
final var invoice = getInvoice(invoiceId);
if (!invoice.isCreditNotePossible()) {
throw new CreditNoteAlreadyExists();
}
final var creditNote = new CreditNote();
creditNote.setCreditNoteId("CREDITNOTE-" + invoiceId);
creditNote.setInvoiceId(invoiceId);
creditNote.setOrderId(invoice.getOrderId());
creditNote.setTotalNetAmount(invoice.getTotalNetAmount());
creditNote.setTotalTaxAmount(invoice.getTotalTaxAmount());
creditNote.setTotalGrossAmount(invoice.getTotalGrossAmount());
creditNote.setCustomer(invoice.getCustomer());
creditNote.setReason(reason);
creditNotes.add(creditNote);
invoice.setCreditNotePossible(false);
invoice.setCreditNotes(List.of(creditNote));
return creditNote;
}
@Override
public List<CreditNote> getCreditNotes(final String invoiceId) throws InvoiceNotFound {
final var invoice = getInvoice(invoiceId);
return invoice.getCreditNotes();
}
@Override
public CreditNote getCreditNote(final String creditNoteId) throws CreditNoteNotFound {
return creditNotes
.stream()
.filter(creditNote -> creditNote.getCreditNoteId().equals(creditNoteId))
.findFirst()
.orElseThrow(CreditNoteNotFound::new);
}
}

View file

@ -0,0 +1,15 @@
package de.hststuttgart.vs.task04.bm;
import de.hststuttgart.vs.task04.bm.exceptions.InvalidOffset;
import de.hststuttgart.vs.task04.bm.exceptions.InvoiceNotFound;
import de.hststuttgart.vs.task04.bm.model.Invoice;
import de.hststuttgart.vs.task04.bm.model.PagedInvoices;
public interface InvoiceRepository {
public PagedInvoices getInvoices(final int offset, final int limit) throws InvalidOffset;
public Invoice getInvoice(final String invoiceId) throws InvoiceNotFound;
}

View file

@ -0,0 +1,5 @@
package de.hststuttgart.vs.task04.bm.exceptions;
public class CreditNoteAlreadyExists extends Exception{
}

View file

@ -0,0 +1,5 @@
package de.hststuttgart.vs.task04.bm.exceptions;
public class CreditNoteNotFound extends Exception {
}

View file

@ -0,0 +1,5 @@
package de.hststuttgart.vs.task04.bm.exceptions;
public class InvalidOffset extends Exception{
}

View file

@ -0,0 +1,5 @@
package de.hststuttgart.vs.task04.bm.exceptions;
public class InvoiceNotFound extends Exception{
}

View file

@ -0,0 +1,82 @@
package de.hststuttgart.vs.task04.bm.model;
import java.math.BigDecimal;
import de.hststuttgart.vs.task04.api.v1.models.Customer;
public class CreditNote {
private String creditNoteId;
private String invoiceId;
private String orderId;
private BigDecimal totalNetAmount;
private BigDecimal totalTaxAmount;
private BigDecimal totalGrossAmount;
private Customer customer;
private String reason;
public String getCreditNoteId() {
return creditNoteId;
}
public void setCreditNoteId(final String creditNoteId) {
this.creditNoteId = creditNoteId;
}
public String getInvoiceId() {
return invoiceId;
}
public void setInvoiceId(final String invoiceId) {
this.invoiceId = invoiceId;
}
public String getOrderId() {
return orderId;
}
public void setOrderId(final String orderId) {
this.orderId = orderId;
}
public BigDecimal getTotalNetAmount() {
return totalNetAmount;
}
public void setTotalNetAmount(final BigDecimal totalNetAmount) {
this.totalNetAmount = totalNetAmount;
}
public BigDecimal getTotalTaxAmount() {
return totalTaxAmount;
}
public void setTotalTaxAmount(final BigDecimal totalTaxAmount) {
this.totalTaxAmount = totalTaxAmount;
}
public BigDecimal getTotalGrossAmount() {
return totalGrossAmount;
}
public void setTotalGrossAmount(final BigDecimal totalGrossAmount) {
this.totalGrossAmount = totalGrossAmount;
}
public Customer getCustomer() {
return customer;
}
public void setCustomer(final Customer customer) {
this.customer = customer;
}
public String getReason() {
return reason;
}
public void setReason(final String reason) {
this.reason = reason;
}
}

View file

@ -0,0 +1,125 @@
package de.hststuttgart.vs.task04.bm.model;
import java.math.BigDecimal;
import java.util.List;
import de.hststuttgart.vs.task04.api.v1.models.Customer;
public class Invoice {
private String invoiceId;
private String orderId;
private BigDecimal totalNetAmount;
private BigDecimal totalTaxAmount;
private BigDecimal totalGrossAmount;
private Customer customer;
private boolean isCreditNotePossible;
private List<CreditNote> creditNotes;
public List<CreditNote> getCreditNotes() {
return creditNotes;
}
public void setCreditNotes(final List<CreditNote> creditNotes) {
this.creditNotes = creditNotes;
}
public boolean isCreditNotePossible() {
return isCreditNotePossible;
}
public void setCreditNotePossible(final boolean creditNotePossible) {
isCreditNotePossible = creditNotePossible;
}
public String getInvoiceId() {
return invoiceId;
}
public void setInvoiceId(final String invoiceId) {
this.invoiceId = invoiceId;
}
public String getOrderId() {
return orderId;
}
public void setOrderId(final String orderId) {
this.orderId = orderId;
}
public BigDecimal getTotalNetAmount() {
return totalNetAmount;
}
public void setTotalNetAmount(final BigDecimal totalNetAmount) {
this.totalNetAmount = totalNetAmount;
}
public BigDecimal getTotalTaxAmount() {
return totalTaxAmount;
}
public void setTotalTaxAmount(final BigDecimal totalTaxAmount) {
this.totalTaxAmount = totalTaxAmount;
}
public BigDecimal getTotalGrossAmount() {
return totalGrossAmount;
}
public void setTotalGrossAmount(final BigDecimal totalGrossAmount) {
this.totalGrossAmount = totalGrossAmount;
}
public Customer getCustomer() {
return customer;
}
public void setCustomer(final Customer customer) {
this.customer = customer;
}
public Invoice invoiceId(final String invoiceId) {
this.invoiceId = invoiceId;
return this;
}
public Invoice orderId(final String orderId) {
this.orderId = orderId;
return this;
}
public Invoice totalNetAmount(final BigDecimal totalNetAmount) {
this.totalNetAmount = totalNetAmount;
return this;
}
public Invoice totalTaxAmount(final BigDecimal totalTaxAmount) {
this.totalTaxAmount = totalTaxAmount;
return this;
}
public Invoice totalGrossAmount(final BigDecimal totalGrossAmount) {
this.totalGrossAmount = totalGrossAmount;
return this;
}
public Invoice customer(final Customer customer) {
this.customer = customer;
return this;
}
public Invoice creditNotePossible(final boolean creditNotePossible) {
this.isCreditNotePossible = creditNotePossible;
return this;
}
public Invoice creditNotes(final List<CreditNote> creditNotes) {
this.creditNotes = creditNotes;
return this;
}
}

View file

@ -0,0 +1,36 @@
package de.hststuttgart.vs.task04.bm.model;
import java.util.List;
public class PagedInvoices {
private boolean previous;
private boolean next;
private List<Invoice> invoices;
public boolean hasPrevious() {
return previous;
}
public void setPrevious(final boolean previous) {
this.previous = previous;
}
public boolean hasNext() {
return next;
}
public void setNext(final boolean next) {
this.next = next;
}
public List<Invoice> getInvoices() {
return invoices;
}
public void setInvoices(final List<Invoice> invoices) {
this.invoices = invoices;
}
}

View file

@ -0,0 +1,13 @@
package de.hststuttgart.vs.task04;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class Task04ApplicationTests {
@Test
void contextLoads() {
}
}

View file

@ -0,0 +1,152 @@
package de.hststuttgart.vs.task04.api.v1;
import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.halLinks;
import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.linkWithRel;
import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.links;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessRequest;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessResponse;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
import static org.springframework.restdocs.payload.PayloadDocumentation.subsectionWithPath;
import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
import static org.springframework.restdocs.request.RequestDocumentation.queryParameters;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayNameGeneration;
import org.junit.jupiter.api.DisplayNameGenerator;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mockito;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.restdocs.RestDocumentationContextProvider;
import org.springframework.restdocs.RestDocumentationExtension;
import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import de.hststuttgart.vs.task04.api.v1.models.Customer;
import de.hststuttgart.vs.task04.bm.InvoiceController;
import de.hststuttgart.vs.task04.bm.model.Invoice;
import de.hststuttgart.vs.task04.bm.model.PagedInvoices;
@ExtendWith(RestDocumentationExtension.class)
@WebMvcTest(InvoiceAPI.class)
@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
class InvoiceAPITest {
private MockMvc mockMvc;
@MockBean
private InvoiceController invoiceController;
private RestDocumentationResultHandler documentationHandler;
@BeforeEach
void setUp(final WebApplicationContext webApplicationContext,
final RestDocumentationContextProvider restDocumentationContextProvider) {
documentationHandler =
document("{method-name}", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()));
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
.apply(documentationConfiguration(restDocumentationContextProvider).uris()
.withScheme("https")
.withPort(443))
.alwaysDo(documentationHandler)
.build();
}
@Test
void should_return_invoices() throws Exception {
// We mock the Object so that we can test it easier
final var pagedInvoices = new PagedInvoices();
pagedInvoices.setInvoices(List.of());
pagedInvoices.setNext(false);
pagedInvoices.setPrevious(false);
Mockito.doReturn(pagedInvoices).when(invoiceController).getInvoices(Mockito.anyInt(), Mockito.anyInt());
this.mockMvc.perform(get("/invoices"))
.andDo(print())
.andExpect(status().isOk())
.andDo(documentationHandler.document(
links(
halLinks(),
linkWithRel("self").description("Link to this resource")
),
responseFields(
fieldWithPath("invoices").description("Array that contains all invoices"),
subsectionWithPath("_links").description("Link to resources")
)
));
}
// TODO 04: test /invoices which should return a list with a next and previous link
@Test
void should_return_invoices_with_links() throws Exception {
// We mock the Object so that we can test it easier
final var johnDoe = new Customer().firstname("John").lastname("Doe");
final var invoice = new Invoice()
.invoiceId("2023-04-16-0001")
.orderId("c082bcbf-550a-4b10-baeb-4047957f70a2")
.totalNetAmount(BigDecimal.valueOf(9.34))
.totalTaxAmount(BigDecimal.valueOf(0.65))
.totalGrossAmount(BigDecimal.valueOf(9.99))
.customer(johnDoe)
.creditNotePossible(true)
.creditNotes(new ArrayList<>());
final var pagedInvoices = new PagedInvoices();
pagedInvoices.setInvoices(List.of(invoice));
pagedInvoices.setNext(true);
pagedInvoices.setPrevious(true);
Mockito.doReturn(pagedInvoices).when(invoiceController).getInvoices(Mockito.anyInt(), Mockito.anyInt());
this.mockMvc.perform(get("/invoices")
.queryParam("offset", "2")
.queryParam("limit", "1"))
.andDo(print())
.andExpect(status().isOk())
.andDo(documentationHandler.document(
links(
halLinks(),
linkWithRel("self").description("Link to this resource"),
linkWithRel("next").description("Link to next page"),
linkWithRel("previous").description("Link to previous page")
),
queryParameters(
parameterWithName("offset").description("Indicates the page. Starts with 1").optional(),
parameterWithName("limit").description("Limits the amount of invoices per page").optional()
),
responseFields(
fieldWithPath("invoices").description("Array that contains all invoices"),
subsectionWithPath("_links").description("Link to resources")
)
));
}
// TODO 05 add a test for /invoices/[invoiceId}
// The endpoint should return an invoice that can be refunded (credit note creation is possible)
// TODO 06 add a test for /invoices/[invoiceId}
// The endpoint should return an invoice that can't be refunded (credit note already exists)
// TODO 07 add a test for /invoices/[invoiceId}
// The endpoint should return no invoce (not found)
}

View file

@ -0,0 +1,24 @@
This endpoint returns all invoices. There is no way to change the order of the elements.
It uses pagination by default. The default values are `1` for `offset` and `5` for `limit`. This gives you the first page which contains 5 invoices.
To navigate through the invoices you can use the provided HATEOAS links.
=== Request structure
include::{snippets}/should_return_invoices/http-request.adoc[]
=== Response fields
include::{snippets}/should_return_invoices/response-fields.adoc[]
=== Response links
include::{snippets}/should_return_invoices/links.adoc[]
=== Example response
include::{snippets}/should_return_invoices/http-response.adoc[]
=== CURL request
include::{snippets}/should_return_invoices/curl-request.adoc[]

View file

@ -0,0 +1,9 @@
= Invoice Manager
:doctype: book
:icons: font
:source-highlighter: highlightjs
:toc: left
:toclevels: 4
:sectlinks:
include::all_invoices.adoc[]