Added task04
This commit is contained in:
parent
48fa43ea2d
commit
d586d1557c
26 changed files with 1163 additions and 0 deletions
33
task04/.gitignore
vendored
Normal file
33
task04/.gitignore
vendored
Normal 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
89
task04/pom.xml
Normal 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>
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package de.hststuttgart.vs.task04.bm.exceptions;
|
||||
|
||||
public class CreditNoteAlreadyExists extends Exception{
|
||||
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package de.hststuttgart.vs.task04.bm.exceptions;
|
||||
|
||||
public class CreditNoteNotFound extends Exception {
|
||||
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package de.hststuttgart.vs.task04.bm.exceptions;
|
||||
|
||||
public class InvalidOffset extends Exception{
|
||||
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package de.hststuttgart.vs.task04.bm.exceptions;
|
||||
|
||||
public class InvoiceNotFound extends Exception{
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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() {
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
||||
|
||||
|
||||
}
|
24
task04/src/test/resources/templates/all_invoices.adoc
Normal file
24
task04/src/test/resources/templates/all_invoices.adoc
Normal 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[]
|
9
task04/src/test/resources/templates/index.adoc
Normal file
9
task04/src/test/resources/templates/index.adoc
Normal file
|
@ -0,0 +1,9 @@
|
|||
= Invoice Manager
|
||||
:doctype: book
|
||||
:icons: font
|
||||
:source-highlighter: highlightjs
|
||||
:toc: left
|
||||
:toclevels: 4
|
||||
:sectlinks:
|
||||
|
||||
include::all_invoices.adoc[]
|
Loading…
Reference in a new issue