Added DTO classes.

This commit is contained in:
Alexey Zinchenko 2019-05-15 00:05:19 +03:00
parent fd4bbd66c5
commit 19196fbe05
31 changed files with 617 additions and 138 deletions

21
pom.xml
View File

@ -72,6 +72,11 @@
<artifactId>javafaker</artifactId> <artifactId>javafaker</artifactId>
<version>0.15</version> <version>0.15</version>
</dependency> </dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-jdk8</artifactId>
<version>1.3.0.Final</version>
</dependency>
<dependency> <dependency>
<groupId>org.webjars</groupId> <groupId>org.webjars</groupId>
@ -147,6 +152,22 @@
</dependency> </dependency>
</dependencies> </dependencies>
</plugin> </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.3.0.Final</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins> </plugins>
</build> </build>

View File

@ -15,7 +15,6 @@ public class CarRepairApplication {
} }
} }
// TODO: DTO
// TODO: i18n // TODO: i18n
// TODO: tests // TODO: tests
// TODO: big data // TODO: big data

View File

@ -1,6 +1,7 @@
package com.github.prominence.carrepair.controller; package com.github.prominence.carrepair.controller;
import com.github.prominence.carrepair.model.Client; import com.github.prominence.carrepair.model.domain.Client;
import com.github.prominence.carrepair.model.dto.ClientDto;
import com.github.prominence.carrepair.service.ClientService; import com.github.prominence.carrepair.service.ClientService;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
@ -31,7 +32,7 @@ public class ClientController {
@GetMapping @GetMapping
public String index(@PageableDefault Pageable pageable, ModelMap modelMap) { public String index(@PageableDefault Pageable pageable, ModelMap modelMap) {
Page<Client> clientList = clientService.findAll(pageable); Page<ClientDto> clientList = clientService.convertToDtoPage(clientService.findAll(pageable));
logger.trace("Request to open list page for Clients. Returning {} page.", () -> pageable.getPageNumber() + 1); logger.trace("Request to open list page for Clients. Returning {} page.", () -> pageable.getPageNumber() + 1);
modelMap.addAttribute("clientList", clientList.getContent()); modelMap.addAttribute("clientList", clientList.getContent());
@ -43,7 +44,7 @@ public class ClientController {
@GetMapping(value = "/create") @GetMapping(value = "/create")
public String create(Model model) { public String create(Model model) {
logger.trace("Request to open create page for Client."); logger.trace("Request to open create page for Client.");
model.addAttribute("client", new Client()); model.addAttribute("clientDto", new ClientDto());
return "client/edit"; return "client/edit";
} }
@ -53,7 +54,7 @@ public class ClientController {
logger.trace("Request to open edit page for Client[{}].", () -> id); logger.trace("Request to open edit page for Client[{}].", () -> id);
Optional<Client> clientOptional = clientService.findById(id); Optional<Client> clientOptional = clientService.findById(id);
if (clientOptional.isPresent()) { if (clientOptional.isPresent()) {
model.addAttribute("client", clientOptional.get()); model.addAttribute("clientDto", clientService.convertToDto(clientOptional.get()));
return "client/edit"; return "client/edit";
} else { } else {
return "redirect:/client"; return "redirect:/client";
@ -61,10 +62,10 @@ public class ClientController {
} }
@PostMapping(value = "/update/{id}") @PostMapping(value = "/update/{id}")
public String update(@Valid Client client, BindingResult bindingResult, @PathVariable long id) { public String update(@Valid ClientDto client, BindingResult bindingResult, @PathVariable long id) {
logger.trace("Request to save {}.", () -> client); logger.trace("Request to save {}.", () -> client);
if (bindingResult.hasErrors()) { if (bindingResult.hasErrors()) {
logger.trace("{} has validation {} errors and won't be saved.", () -> client, bindingResult::getErrorCount); logger.trace("{} has {} validation errors and won't be saved.", () -> client, bindingResult::getErrorCount);
client.setId(id); // why should we do this? client.setId(id); // why should we do this?
return "client/edit"; return "client/edit";
} }
@ -74,10 +75,10 @@ public class ClientController {
} }
@PostMapping(value = "/create") @PostMapping(value = "/create")
public String save(@Valid Client client, BindingResult bindingResult) { public String save(@Valid ClientDto client, BindingResult bindingResult) {
logger.trace("Request to create {}.", () -> client); logger.trace("Request to create {}.", () -> client);
if (bindingResult.hasErrors()) { if (bindingResult.hasErrors()) {
logger.trace("{} has validation {} errors and won't be created.", () -> client, bindingResult::getErrorCount); logger.trace("{} has {} validation errors and won't be created.", () -> client, bindingResult::getErrorCount);
return "client/edit"; return "client/edit";
} }

View File

@ -1,7 +1,8 @@
package com.github.prominence.carrepair.controller; package com.github.prominence.carrepair.controller;
import com.github.prominence.carrepair.enums.OrderStatus; import com.github.prominence.carrepair.enums.OrderStatus;
import com.github.prominence.carrepair.model.Mechanic; import com.github.prominence.carrepair.model.domain.Mechanic;
import com.github.prominence.carrepair.model.dto.MechanicDto;
import com.github.prominence.carrepair.service.MechanicService; import com.github.prominence.carrepair.service.MechanicService;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
@ -32,7 +33,7 @@ public class MechanicController {
@GetMapping @GetMapping
public String index(@PageableDefault Pageable pageable, ModelMap modelMap) { public String index(@PageableDefault Pageable pageable, ModelMap modelMap) {
Page<Mechanic> mechanicList = mechanicService.findAll(pageable); Page<MechanicDto> mechanicList = mechanicService.convertToDtoPage(mechanicService.findAll(pageable));
logger.trace("Request to open list page for Mechanics. Returning {} page.", () -> pageable.getPageNumber() + 1); logger.trace("Request to open list page for Mechanics. Returning {} page.", () -> pageable.getPageNumber() + 1);
modelMap.addAttribute("mechanicList", mechanicList.getContent()); modelMap.addAttribute("mechanicList", mechanicList.getContent());
@ -44,7 +45,7 @@ public class MechanicController {
@GetMapping(value = "/create") @GetMapping(value = "/create")
public String create(Model model) { public String create(Model model) {
logger.trace("Request to open create page for Mechanic."); logger.trace("Request to open create page for Mechanic.");
model.addAttribute("mechanic", new Mechanic()); model.addAttribute("mechanicDto", new MechanicDto());
return "mechanic/edit"; return "mechanic/edit";
} }
@ -54,7 +55,7 @@ public class MechanicController {
logger.trace("Request to open edit page for Mechanic[{}].", () -> id); logger.trace("Request to open edit page for Mechanic[{}].", () -> id);
Optional<Mechanic> mechanicOptional = mechanicService.findById(id); Optional<Mechanic> mechanicOptional = mechanicService.findById(id);
if (mechanicOptional.isPresent()) { if (mechanicOptional.isPresent()) {
model.addAttribute("mechanic", mechanicOptional.get()); model.addAttribute("mechanicDto", mechanicService.convertToDto(mechanicOptional.get()));
return "mechanic/edit"; return "mechanic/edit";
} else { } else {
return "redirect:/mechanic"; return "redirect:/mechanic";
@ -62,10 +63,10 @@ public class MechanicController {
} }
@PostMapping(value = "/update/{id}") @PostMapping(value = "/update/{id}")
public String update(@Valid Mechanic mechanic, BindingResult bindingResult, @PathVariable long id) { public String update(@Valid MechanicDto mechanic, BindingResult bindingResult, @PathVariable long id) {
logger.trace("Request to save {}.", () -> mechanic); logger.trace("Request to save {}.", () -> mechanic);
if (bindingResult.hasErrors()) { if (bindingResult.hasErrors()) {
logger.trace("{} has validation {} errors and won't be saved.", () -> mechanic, bindingResult::getErrorCount); logger.trace("{} has {} validation errors and won't be saved.", () -> mechanic, bindingResult::getErrorCount);
mechanic.setId(id); // why should we do this? mechanic.setId(id); // why should we do this?
return "mechanic/edit"; return "mechanic/edit";
} }
@ -75,10 +76,10 @@ public class MechanicController {
} }
@PostMapping(value = "/create") @PostMapping(value = "/create")
public String save(@Valid Mechanic mechanic, BindingResult bindingResult) { public String save(@Valid MechanicDto mechanic, BindingResult bindingResult) {
logger.trace("Request to create {}.", () -> mechanic); logger.trace("Request to create {}.", () -> mechanic);
if (bindingResult.hasErrors()) { if (bindingResult.hasErrors()) {
logger.trace("{} has validation {} errors and won't be created.", () -> mechanic, bindingResult::getErrorCount); logger.trace("{} has {} validation errors and won't be created.", () -> mechanic, bindingResult::getErrorCount);
return "mechanic/edit"; return "mechanic/edit";
} }

View File

@ -1,7 +1,8 @@
package com.github.prominence.carrepair.controller; package com.github.prominence.carrepair.controller;
import com.github.prominence.carrepair.enums.OrderStatus; import com.github.prominence.carrepair.enums.OrderStatus;
import com.github.prominence.carrepair.model.Order; import com.github.prominence.carrepair.model.domain.Order;
import com.github.prominence.carrepair.model.dto.OrderDto;
import com.github.prominence.carrepair.repository.spec.OrderSpecifications; import com.github.prominence.carrepair.repository.spec.OrderSpecifications;
import com.github.prominence.carrepair.service.OrderService; import com.github.prominence.carrepair.service.OrderService;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
@ -32,7 +33,9 @@ public class OrderController {
@GetMapping @GetMapping
public String index(@PageableDefault Pageable pageable, @RequestParam(required = false) String client, @RequestParam(required = false) String description, public String index(@PageableDefault Pageable pageable, @RequestParam(required = false) String client, @RequestParam(required = false) String description,
@RequestParam(required = false) OrderStatus orderStatus, ModelMap modelMap) { @RequestParam(required = false) OrderStatus orderStatus, ModelMap modelMap) {
Page<Order> orderList = orderService.findAll(OrderSpecifications.search(client, description, orderStatus), pageable); Page<OrderDto> orderList = orderService.convertToDtoPage(
orderService.findAll(OrderSpecifications.search(client, description, orderStatus), pageable)
);
logger.trace("Request to open list page for Orders. Returning {} page.", () -> pageable.getPageNumber() + 1); logger.trace("Request to open list page for Orders. Returning {} page.", () -> pageable.getPageNumber() + 1);
modelMap.addAttribute("orderList", orderList.getContent()); modelMap.addAttribute("orderList", orderList.getContent());
@ -45,7 +48,7 @@ public class OrderController {
@GetMapping(value = "/create") @GetMapping(value = "/create")
public String create(Model model) { public String create(Model model) {
logger.trace("Request to open create page for Order."); logger.trace("Request to open create page for Order.");
model.addAttribute("order", new Order()); model.addAttribute("orderDto", new OrderDto());
model.addAttribute("orderStatuses", OrderStatus.values()); model.addAttribute("orderStatuses", OrderStatus.values());
return "order/edit"; return "order/edit";
@ -56,7 +59,7 @@ public class OrderController {
logger.trace("Request to open edit page for Order[{}].", () -> id); logger.trace("Request to open edit page for Order[{}].", () -> id);
Optional<Order> orderOptional = orderService.findById(id); Optional<Order> orderOptional = orderService.findById(id);
if (orderOptional.isPresent()) { if (orderOptional.isPresent()) {
model.addAttribute("order", orderOptional.get()); model.addAttribute("orderDto", orderService.convertToDto(orderOptional.get()));
model.addAttribute("orderStatuses", OrderStatus.values()); model.addAttribute("orderStatuses", OrderStatus.values());
return "order/edit"; return "order/edit";
} else { } else {
@ -65,11 +68,11 @@ public class OrderController {
} }
@PostMapping(value = "/update/{id}") @PostMapping(value = "/update/{id}")
public String update(Order order, BindingResult bindingResult, @PathVariable long id, Long clientId, Long mechanicId, Model model) { public String update(OrderDto order, BindingResult bindingResult, @PathVariable long id, Long clientId, Long mechanicId, Model model) {
logger.trace("Request to save {}.", () -> order); logger.trace("Request to save {}.", () -> order);
orderService.fetchNestedObjectsAndValidate(order, clientId, mechanicId, bindingResult); orderService.fetchNestedObjectsAndValidate(order, clientId, mechanicId, bindingResult);
if (bindingResult.hasErrors()) { if (bindingResult.hasErrors()) {
logger.trace("{} has validation {} errors and won't be saved.", () -> order, bindingResult::getErrorCount); logger.trace("{} has {} validation errors and won't be saved.", () -> order, bindingResult::getErrorCount);
order.setId(id); // why should we do this? order.setId(id); // why should we do this?
model.addAttribute("orderStatuses", OrderStatus.values()); model.addAttribute("orderStatuses", OrderStatus.values());
return "order/edit"; return "order/edit";
@ -80,11 +83,11 @@ public class OrderController {
} }
@PostMapping(value = "/create") @PostMapping(value = "/create")
public String save(Order order, BindingResult bindingResult, Long clientId, Long mechanicId, Model model) { public String save(OrderDto order, BindingResult bindingResult, Long clientId, Long mechanicId, Model model) {
logger.trace("Request to create {}.", () -> order); logger.trace("Request to create {}.", () -> order);
orderService.fetchNestedObjectsAndValidate(order, clientId, mechanicId, bindingResult); orderService.fetchNestedObjectsAndValidate(order, clientId, mechanicId, bindingResult);
if (bindingResult.hasErrors()) { if (bindingResult.hasErrors()) {
logger.trace("{} has validation {} errors and won't be created.", () -> order, bindingResult::getErrorCount); logger.trace("{} has {} validation errors and won't be created.", () -> order, bindingResult::getErrorCount);
model.addAttribute("orderStatuses", OrderStatus.values()); model.addAttribute("orderStatuses", OrderStatus.values());
return "order/edit"; return "order/edit";
} }

View File

@ -1,7 +1,7 @@
package com.github.prominence.carrepair.controller.api; package com.github.prominence.carrepair.controller.api;
import com.github.prominence.carrepair.model.Client; import com.github.prominence.carrepair.model.dto.ClientDto;
import com.github.prominence.carrepair.model.Mechanic; import com.github.prominence.carrepair.model.dto.MechanicDto;
import com.github.prominence.carrepair.service.ClientService; import com.github.prominence.carrepair.service.ClientService;
import com.github.prominence.carrepair.service.MechanicService; import com.github.prominence.carrepair.service.MechanicService;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
@ -25,12 +25,12 @@ public class AutocompleteApiController {
} }
@GetMapping(value = "/client", produces = MediaType.APPLICATION_JSON_VALUE) @GetMapping(value = "/client", produces = MediaType.APPLICATION_JSON_VALUE)
public List<Client> clientAutocomplete(@RequestParam String query) { public List<ClientDto> clientAutocomplete(@RequestParam String query) {
return clientService.searchByInitials(query); return clientService.convertToDtoList(clientService.searchByInitials(query));
} }
@GetMapping(value = "/mechanic", produces = MediaType.APPLICATION_JSON_VALUE) @GetMapping(value = "/mechanic", produces = MediaType.APPLICATION_JSON_VALUE)
public List<Mechanic> mechanicAutocomplete(@RequestParam String query) { public List<MechanicDto> mechanicAutocomplete(@RequestParam String query) {
return mechanicService.searchByInitials(query); return mechanicService.convertToDtoList(mechanicService.searchByInitials(query));
} }
} }

View File

@ -2,9 +2,9 @@ package com.github.prominence.carrepair.demo;
import com.github.javafaker.Faker; import com.github.javafaker.Faker;
import com.github.prominence.carrepair.enums.OrderStatus; import com.github.prominence.carrepair.enums.OrderStatus;
import com.github.prominence.carrepair.model.Client; import com.github.prominence.carrepair.model.domain.Client;
import com.github.prominence.carrepair.model.Mechanic; import com.github.prominence.carrepair.model.domain.Mechanic;
import com.github.prominence.carrepair.model.Order; import com.github.prominence.carrepair.model.domain.Order;
import com.github.prominence.carrepair.service.ClientService; import com.github.prominence.carrepair.service.ClientService;
import com.github.prominence.carrepair.service.MechanicService; import com.github.prominence.carrepair.service.MechanicService;
import com.github.prominence.carrepair.service.OrderService; import com.github.prominence.carrepair.service.OrderService;

View File

@ -1,11 +1,11 @@
package com.github.prominence.carrepair.formatter; package com.github.prominence.carrepair.formatter;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.springframework.format.Formatter; import org.springframework.format.Formatter;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.text.ParseException;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.Locale; import java.util.Locale;
@ -13,16 +13,19 @@ import java.util.Locale;
@Component @Component
public class CustomDateTimeFormatter implements Formatter<LocalDateTime> { public class CustomDateTimeFormatter implements Formatter<LocalDateTime> {
private static final Logger logger = LogManager.getLogger(CustomDateTimeFormatter.class); private static final Logger logger = LogManager.getLogger(CustomDateTimeFormatter.class);
public static final String DATETIME_PATTERN = "yyyy-MM-dd HH:mm:ss";
@Override @Override
public LocalDateTime parse(String text, Locale locale) throws ParseException { public LocalDateTime parse(String text, Locale locale) {
final LocalDateTime parsedDateTime = LocalDateTime.parse(text, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); if (StringUtils.isEmpty(text)) return null;
final LocalDateTime parsedDateTime = LocalDateTime.parse(text, DateTimeFormatter.ofPattern(DATETIME_PATTERN));
logger.trace("Parsing String[{}] to LocalDateTime instance: {}.", () -> text, () -> parsedDateTime); logger.trace("Parsing String[{}] to LocalDateTime instance: {}.", () -> text, () -> parsedDateTime);
return parsedDateTime; return parsedDateTime;
} }
@Override @Override
public String print(LocalDateTime object, Locale locale) { public String print(LocalDateTime object, Locale locale) {
if (object == null) return null;
final String formattedString = object.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); final String formattedString = object.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
logger.trace("Formatting LocalDateTime[{}] to String instance: {}.", () -> object, () -> formattedString); logger.trace("Formatting LocalDateTime[{}] to String instance: {}.", () -> object, () -> formattedString);
return formattedString; return formattedString;

View File

@ -1,17 +1,15 @@
package com.github.prominence.carrepair.model; package com.github.prominence.carrepair.model.domain;
import javax.persistence.Column; import javax.persistence.Column;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.Table; import javax.persistence.Table;
import javax.validation.constraints.Size;
import java.util.Objects; import java.util.Objects;
@Entity @Entity
@Table(name = "client") @Table(name = "client")
public class Client extends Person { public class Client extends Person {
@Size(max = 32) @Column(name = "phoneNo", length = 32)
@Column(name = "phoneNo")
private String phoneNo; private String phoneNo;
public Client(String firstName, String middleName, String lastName, String phoneNo) { public Client(String firstName, String middleName, String lastName, String phoneNo) {

View File

@ -1,10 +1,8 @@
package com.github.prominence.carrepair.model; package com.github.prominence.carrepair.model.domain;
import javax.persistence.Column; import javax.persistence.Column;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.Table; import javax.persistence.Table;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.Objects; import java.util.Objects;
@ -12,9 +10,7 @@ import java.util.Objects;
@Table(name = "mechanic") @Table(name = "mechanic")
public class Mechanic extends Person { public class Mechanic extends Person {
@NotNull @Column(name = "hourlyPayment", nullable = false)
@Min(value = 0)
@Column(name = "hourlyPayment")
private BigDecimal hourlyPayment; private BigDecimal hourlyPayment;
public Mechanic(String firstName, String middleName, String lastName, BigDecimal hourlyPayment) { public Mechanic(String firstName, String middleName, String lastName, BigDecimal hourlyPayment) {

View File

@ -1,11 +1,8 @@
package com.github.prominence.carrepair.model; package com.github.prominence.carrepair.model.domain;
import com.github.prominence.carrepair.enums.OrderStatus; import com.github.prominence.carrepair.enums.OrderStatus;
import javax.persistence.*; import javax.persistence.*;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.Objects; import java.util.Objects;
@ -18,33 +15,27 @@ public class Order {
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id; private Long id;
@NotNull @Column(nullable = false)
@Size(max = 1024)
private String description; private String description;
@NotNull @ManyToOne(optional = false)
@ManyToOne(fetch = FetchType.EAGER, optional = false) // change to LAZY after DTO implementation
@JoinColumn(name = "client_id", nullable = false) @JoinColumn(name = "client_id", nullable = false)
private Client client; private Client client;
@NotNull @ManyToOne(optional = false)
@ManyToOne(fetch = FetchType.EAGER, optional = false) // change to LAZY after DTO implementation
@JoinColumn(name = "mechanic_id", nullable = false) @JoinColumn(name = "mechanic_id", nullable = false)
private Mechanic mechanic; private Mechanic mechanic;
@NotNull @Column(name = "createdOn", nullable = false)
@Column(name = "createdOn")
private LocalDateTime createdOn; private LocalDateTime createdOn;
@Column(name = "finishedOn") @Column(name = "finishedOn")
private LocalDateTime finishedOn; private LocalDateTime finishedOn;
@Min(value = 0) @Column(name = "totalPrice", nullable = false)
@Column(name = "totalPrice")
private BigDecimal totalPrice; private BigDecimal totalPrice;
@NotNull @Column(name = "orderStatus", nullable = false)
@Column(name = "orderStatus")
private String orderStatus = OrderStatus.SCHEDULED.toString(); private String orderStatus = OrderStatus.SCHEDULED.toString();
public Order(String description, Client client, Mechanic mechanic, LocalDateTime createdOn, LocalDateTime finishedOn, BigDecimal totalPrice, String orderStatus) { public Order(String description, Client client, Mechanic mechanic, LocalDateTime createdOn, LocalDateTime finishedOn, BigDecimal totalPrice, String orderStatus) {

View File

@ -1,8 +1,6 @@
package com.github.prominence.carrepair.model; package com.github.prominence.carrepair.model.domain;
import javax.persistence.*; import javax.persistence.*;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
import java.util.Objects; import java.util.Objects;
@MappedSuperclass @MappedSuperclass
@ -12,18 +10,13 @@ abstract public class Person {
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)
protected Long id; protected Long id;
@NotBlank @Column(name = "firstName", length = 64, nullable = false)
@Size(max = 64)
@Column(name = "firstName")
protected String firstName; protected String firstName;
@Size(max = 64) @Column(name = "middleName", length = 64)
@Column(name = "middleName")
protected String middleName; protected String middleName;
@NotBlank @Column(name = "lastName", length = 64, nullable = false)
@Size(max = 64)
@Column(name = "lastName")
protected String lastName; protected String lastName;
public Person(String firstName, String middleName, String lastName) { public Person(String firstName, String middleName, String lastName) {

View File

@ -0,0 +1,66 @@
package com.github.prominence.carrepair.model.dto;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
public class ClientDto {
private Long id;
@NotBlank
@Size(max = 64)
private String firstName;
@Size(max = 64)
private String middleName;
@NotBlank
@Size(max = 64)
private String lastName;
@Size(max = 32)
private String phone;
public ClientDto() {
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getMiddleName() {
return middleName;
}
public void setMiddleName(String middleName) {
this.middleName = middleName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
}

View File

@ -0,0 +1,69 @@
package com.github.prominence.carrepair.model.dto;
import javax.validation.constraints.*;
import java.math.BigDecimal;
public class MechanicDto {
private Long id;
@NotBlank
@Size(max = 64)
private String firstName;
@Size(max = 64)
private String middleName;
@NotBlank
@Size(max = 64)
private String lastName;
@NotNull
@PositiveOrZero
private BigDecimal hourlyPayment;
public MechanicDto() {
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getMiddleName() {
return middleName;
}
public void setMiddleName(String middleName) {
this.middleName = middleName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public BigDecimal getHourlyPayment() {
return hourlyPayment;
}
public void setHourlyPayment(BigDecimal hourlyPayment) {
this.hourlyPayment = hourlyPayment;
}
}

View File

@ -0,0 +1,155 @@
package com.github.prominence.carrepair.model.dto;
import javax.validation.constraints.*;
import java.math.BigDecimal;
public class OrderDto {
private Long id;
@NotNull
@Positive
private Long clientId;
private String clientFirstName;
private String clientMiddleName;
private String clientLastName;
@NotNull
@Positive
private Long mechanicId;
private String mechanicFirstName;
private String mechanicMiddleName;
private String mechanicLastName;
@Size(min = 1, max = 1024)
private String description;
@NotBlank
private String orderStatus;
@NotBlank
private String createdOnDate;
private String finishedOnDate;
@NotNull
@PositiveOrZero
private BigDecimal totalPrice;
public OrderDto() {
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getClientId() {
return clientId;
}
public void setClientId(Long clientId) {
this.clientId = clientId;
}
public String getClientFirstName() {
return clientFirstName;
}
public void setClientFirstName(String clientFirstName) {
this.clientFirstName = clientFirstName;
}
public String getClientMiddleName() {
return clientMiddleName;
}
public void setClientMiddleName(String clientMiddleName) {
this.clientMiddleName = clientMiddleName;
}
public String getClientLastName() {
return clientLastName;
}
public void setClientLastName(String clientLastName) {
this.clientLastName = clientLastName;
}
public Long getMechanicId() {
return mechanicId;
}
public void setMechanicId(Long mechanicId) {
this.mechanicId = mechanicId;
}
public String getMechanicFirstName() {
return mechanicFirstName;
}
public void setMechanicFirstName(String mechanicFirstName) {
this.mechanicFirstName = mechanicFirstName;
}
public String getMechanicMiddleName() {
return mechanicMiddleName;
}
public void setMechanicMiddleName(String mechanicMiddleName) {
this.mechanicMiddleName = mechanicMiddleName;
}
public String getMechanicLastName() {
return mechanicLastName;
}
public void setMechanicLastName(String mechanicLastName) {
this.mechanicLastName = mechanicLastName;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getOrderStatus() {
return orderStatus;
}
public void setOrderStatus(String orderStatus) {
this.orderStatus = orderStatus;
}
public String getCreatedOnDate() {
return createdOnDate;
}
public void setCreatedOnDate(String createdOnDate) {
this.createdOnDate = createdOnDate;
}
public String getFinishedOnDate() {
return finishedOnDate;
}
public void setFinishedOnDate(String finishedOnDate) {
this.finishedOnDate = finishedOnDate;
}
public BigDecimal getTotalPrice() {
return totalPrice;
}
public void setTotalPrice(BigDecimal totalPrice) {
this.totalPrice = totalPrice;
}
}

View File

@ -0,0 +1,25 @@
package com.github.prominence.carrepair.model.mapper;
import com.github.prominence.carrepair.model.domain.Client;
import com.github.prominence.carrepair.model.dto.ClientDto;
import org.mapstruct.InheritInverseConfiguration;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import java.util.List;
@Mapper(componentModel = "spring")
public interface ClientMapper {
@Mappings({
@Mapping(source = "id", target = "id"),
@Mapping(source = "phoneNo", target = "phone")
})
ClientDto clientToClientDto(Client client);
@InheritInverseConfiguration
Client clientDtoToClient(ClientDto clientDto);
List<ClientDto> clientsToClientDtoList(List<Client> clients);
}

View File

@ -0,0 +1,24 @@
package com.github.prominence.carrepair.model.mapper;
import com.github.prominence.carrepair.model.domain.Mechanic;
import com.github.prominence.carrepair.model.dto.MechanicDto;
import org.mapstruct.InheritInverseConfiguration;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import java.util.List;
@Mapper(componentModel = "spring")
public interface MechanicMapper {
@Mappings({
@Mapping(source = "id", target = "id"),
})
MechanicDto mechanicToMechanicDto(Mechanic mechanic);
@InheritInverseConfiguration
Mechanic mechanicDtoToMechanic(MechanicDto mechanicDto);
List<MechanicDto> mechanicsToMechanicDtoList(List<Mechanic> mechanics);
}

View File

@ -0,0 +1,35 @@
package com.github.prominence.carrepair.model.mapper;
import com.github.prominence.carrepair.formatter.CustomDateTimeFormatter;
import com.github.prominence.carrepair.model.domain.Order;
import com.github.prominence.carrepair.model.dto.OrderDto;
import org.mapstruct.InheritInverseConfiguration;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import java.util.List;
@Mapper(componentModel = "spring")
public interface OrderMapper {
@Mappings({
@Mapping(source = "id", target = "id"),
@Mapping(source = "client.id", target = "clientId"),
@Mapping(source = "client.firstName", target = "clientFirstName"),
@Mapping(source = "client.middleName", target = "clientMiddleName"),
@Mapping(source = "client.lastName", target = "clientLastName"),
@Mapping(source = "mechanic.id", target = "mechanicId"),
@Mapping(source = "mechanic.firstName", target = "mechanicFirstName"),
@Mapping(source = "mechanic.middleName", target = "mechanicMiddleName"),
@Mapping(source = "mechanic.lastName", target = "mechanicLastName"),
@Mapping(source = "createdOn", target = "createdOnDate", dateFormat = CustomDateTimeFormatter.DATETIME_PATTERN),
@Mapping(source = "finishedOn", target = "finishedOnDate", dateFormat = CustomDateTimeFormatter.DATETIME_PATTERN)
})
OrderDto orderToOrderDto(Order order);
@InheritInverseConfiguration
Order orderDtoToOrder(OrderDto orderDto);
List<OrderDto> ordersToOrderDtoList(List<Order> orders);
}

View File

@ -1,6 +1,6 @@
package com.github.prominence.carrepair.repository; package com.github.prominence.carrepair.repository;
import com.github.prominence.carrepair.model.Client; import com.github.prominence.carrepair.model.domain.Client;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query; import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param; import org.springframework.data.repository.query.Param;

View File

@ -1,6 +1,6 @@
package com.github.prominence.carrepair.repository; package com.github.prominence.carrepair.repository;
import com.github.prominence.carrepair.model.Mechanic; import com.github.prominence.carrepair.model.domain.Mechanic;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query; import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param; import org.springframework.data.repository.query.Param;

View File

@ -1,6 +1,6 @@
package com.github.prominence.carrepair.repository; package com.github.prominence.carrepair.repository;
import com.github.prominence.carrepair.model.Order; import com.github.prominence.carrepair.model.domain.Order;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;

View File

@ -1,8 +1,8 @@
package com.github.prominence.carrepair.repository.spec; package com.github.prominence.carrepair.repository.spec;
import com.github.prominence.carrepair.enums.OrderStatus; import com.github.prominence.carrepair.enums.OrderStatus;
import com.github.prominence.carrepair.model.Client; import com.github.prominence.carrepair.model.domain.Client;
import com.github.prominence.carrepair.model.Order; import com.github.prominence.carrepair.model.domain.Order;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;

View File

@ -1,10 +1,13 @@
package com.github.prominence.carrepair.service; package com.github.prominence.carrepair.service;
import com.github.prominence.carrepair.model.Client; import com.github.prominence.carrepair.model.domain.Client;
import com.github.prominence.carrepair.model.dto.ClientDto;
import com.github.prominence.carrepair.model.mapper.ClientMapper;
import com.github.prominence.carrepair.repository.ClientRepository; import com.github.prominence.carrepair.repository.ClientRepository;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -16,14 +19,16 @@ public class ClientService {
private static final Logger logger = LogManager.getLogger(ClientService.class); private static final Logger logger = LogManager.getLogger(ClientService.class);
private ClientRepository clientRepository; private ClientRepository clientRepository;
private ClientMapper clientMapper;
public ClientService(ClientRepository clientRepository) { public ClientService(ClientRepository clientRepository, ClientMapper clientMapper) {
this.clientRepository = clientRepository; this.clientRepository = clientRepository;
this.clientMapper = clientMapper;
} }
public Page<Client> findAll(Pageable pageable) { public Page<Client> findAll(Pageable pageable) {
final Page<Client> clientPage = clientRepository.findAll(pageable); final Page<Client> clientPage = clientRepository.findAll(pageable);
logger.trace(clientPage); logger.trace("Original page: {}.", () -> clientPage);
return clientPage; return clientPage;
} }
@ -39,6 +44,10 @@ public class ClientService {
return clientToSave; return clientToSave;
} }
public Client save(ClientDto clientDto) {
return save(clientMapper.clientDtoToClient(clientDto));
}
public boolean deleteClientById(Long id) { public boolean deleteClientById(Long id) {
try { try {
clientRepository.deleteById(id); clientRepository.deleteById(id);
@ -61,4 +70,19 @@ public class ClientService {
logger.debug("Found {} clients by initials: {}.", allByInitials::size, () -> query); logger.debug("Found {} clients by initials: {}.", allByInitials::size, () -> query);
return allByInitials; return allByInitials;
} }
public Page<ClientDto> convertToDtoPage(Page<Client> clientPage) {
final Page<ClientDto> clientDtoPage = new PageImpl<> (clientMapper.clientsToClientDtoList(clientPage.getContent()),
clientPage.getPageable(), clientPage.getTotalElements());
logger.trace("Dto page: {}.", () -> clientDtoPage);
return clientDtoPage;
}
public ClientDto convertToDto(Client client) {
return clientMapper.clientToClientDto(client);
}
public List<ClientDto> convertToDtoList(List<Client> clientList) {
return clientMapper.clientsToClientDtoList(clientList);
}
} }

View File

@ -1,13 +1,16 @@
package com.github.prominence.carrepair.service; package com.github.prominence.carrepair.service;
import com.github.prominence.carrepair.enums.OrderStatus; import com.github.prominence.carrepair.enums.OrderStatus;
import com.github.prominence.carrepair.model.Mechanic; import com.github.prominence.carrepair.model.domain.Mechanic;
import com.github.prominence.carrepair.model.Order; import com.github.prominence.carrepair.model.domain.Order;
import com.github.prominence.carrepair.model.dto.MechanicDto;
import com.github.prominence.carrepair.model.mapper.MechanicMapper;
import com.github.prominence.carrepair.repository.MechanicRepository; import com.github.prominence.carrepair.repository.MechanicRepository;
import com.github.prominence.carrepair.repository.OrderRepository; import com.github.prominence.carrepair.repository.OrderRepository;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -22,10 +25,12 @@ public class MechanicService {
private MechanicRepository mechanicRepository; private MechanicRepository mechanicRepository;
private OrderRepository orderRepository; private OrderRepository orderRepository;
private MechanicMapper mechanicMapper;
public MechanicService(MechanicRepository mechanicRepository, OrderRepository orderRepository) { public MechanicService(MechanicRepository mechanicRepository, OrderRepository orderRepository, MechanicMapper mechanicMapper) {
this.mechanicRepository = mechanicRepository; this.mechanicRepository = mechanicRepository;
this.orderRepository = orderRepository; this.orderRepository = orderRepository;
this.mechanicMapper = mechanicMapper;
} }
public Page<Mechanic> findAll(Pageable pageable) { public Page<Mechanic> findAll(Pageable pageable) {
@ -46,6 +51,10 @@ public class MechanicService {
return mechanicToSave; return mechanicToSave;
} }
public Mechanic save(MechanicDto mechanicDto) {
return save(mechanicMapper.mechanicDtoToMechanic(mechanicDto));
}
public boolean deleteMechanicById(Long id) { public boolean deleteMechanicById(Long id) {
try { try {
mechanicRepository.deleteById(id); mechanicRepository.deleteById(id);
@ -82,4 +91,19 @@ public class MechanicService {
logger.debug("Found {} mechanics by initials: {}.", allByInitials::size, () -> query); logger.debug("Found {} mechanics by initials: {}.", allByInitials::size, () -> query);
return allByInitials; return allByInitials;
} }
public Page<MechanicDto> convertToDtoPage(Page<Mechanic> mechanicPage) {
final Page<MechanicDto> mechanicDtoPage = new PageImpl<>(mechanicMapper.mechanicsToMechanicDtoList(mechanicPage.getContent()),
mechanicPage.getPageable(), mechanicPage.getTotalElements());
logger.trace("Dto page: {}.", () -> mechanicDtoPage);
return mechanicDtoPage;
}
public MechanicDto convertToDto(Mechanic mechanic) {
return mechanicMapper.mechanicToMechanicDto(mechanic);
}
public List<MechanicDto> convertToDtoList(List<Mechanic> mechanicList) {
return mechanicMapper.mechanicsToMechanicDtoList(mechanicList);
}
} }

View File

@ -1,11 +1,14 @@
package com.github.prominence.carrepair.service; package com.github.prominence.carrepair.service;
import com.github.prominence.carrepair.model.Order; import com.github.prominence.carrepair.model.domain.Order;
import com.github.prominence.carrepair.model.dto.OrderDto;
import com.github.prominence.carrepair.model.mapper.OrderMapper;
import com.github.prominence.carrepair.repository.OrderRepository; import com.github.prominence.carrepair.repository.OrderRepository;
import com.github.prominence.carrepair.validation.OrderValidator; import com.github.prominence.carrepair.validation.OrderValidator;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification; import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -19,18 +22,20 @@ public class OrderService {
private static final Logger logger = LogManager.getLogger(OrderService.class); private static final Logger logger = LogManager.getLogger(OrderService.class);
private OrderRepository orderRepository; private OrderRepository orderRepository;
private ClientService clientService; private ClientService clientService;
private MechanicService mechanicService; private MechanicService mechanicService;
private SmartValidator smartValidator; private SmartValidator smartValidator;
private OrderValidator orderValidator; private OrderValidator orderValidator;
private OrderMapper orderMapper;
public OrderService(OrderRepository orderRepository, ClientService clientService, MechanicService mechanicService, SmartValidator smartValidator, OrderValidator orderValidator) { public OrderService(OrderRepository orderRepository, ClientService clientService, MechanicService mechanicService, SmartValidator smartValidator,
OrderValidator orderValidator, OrderMapper orderMapper) {
this.orderRepository = orderRepository; this.orderRepository = orderRepository;
this.clientService = clientService; this.clientService = clientService;
this.mechanicService = mechanicService; this.mechanicService = mechanicService;
this.smartValidator = smartValidator; this.smartValidator = smartValidator;
this.orderValidator = orderValidator; this.orderValidator = orderValidator;
this.orderMapper = orderMapper;
} }
public Page<Order> findAll(Specification<Order> specification, Pageable pageable) { public Page<Order> findAll(Specification<Order> specification, Pageable pageable) {
@ -51,6 +56,10 @@ public class OrderService {
return orderToSave; return orderToSave;
} }
public Order save(OrderDto orderDto) {
return save(orderMapper.orderDtoToOrder(orderDto));
}
public boolean deleteOrderById(Long id) { public boolean deleteOrderById(Long id) {
try { try {
orderRepository.deleteById(id); orderRepository.deleteById(id);
@ -68,16 +77,37 @@ public class OrderService {
return orderCount; return orderCount;
} }
public void fetchNestedObjectsAndValidate(Order order, Long clientId, Long mechanicId, BindingResult bindingResult) { public void fetchNestedObjectsAndValidate(OrderDto order, Long clientId, Long mechanicId, BindingResult bindingResult) {
if (clientId != null) { if (clientId != null) {
logger.trace("Fetching Client[{}] for {}.", () -> clientId, () -> order); logger.trace("Fetching Client[{}] for {}.", () -> clientId, () -> order);
clientService.findById(clientId).ifPresent(order::setClient); clientService.findById(clientId).ifPresent(client -> {
order.setClientId(client.getId());
order.setClientFirstName(client.getFirstName());
order.setClientMiddleName(client.getMiddleName());
order.setClientLastName(client.getLastName());
});
} }
if (mechanicId != null) { if (mechanicId != null) {
logger.trace("Fetching Mechanic[{}] for {}.", () -> clientId, () -> order); logger.trace("Fetching Mechanic[{}] for {}.", () -> clientId, () -> order);
mechanicService.findById(mechanicId).ifPresent(order::setMechanic); mechanicService.findById(mechanicId).ifPresent(mechanic -> {
order.setMechanicId(mechanic.getId());
order.setMechanicFirstName(mechanic.getFirstName());
order.setClientMiddleName(mechanic.getMiddleName());
order.setMechanicLastName(mechanic.getLastName());
});
} }
smartValidator.validate(order, bindingResult); smartValidator.validate(order, bindingResult);
orderValidator.validate(order, bindingResult); orderValidator.validate(order, bindingResult);
} }
public Page<OrderDto> convertToDtoPage(Page<Order> orderPage) {
final Page<OrderDto> orderDtoPage = new PageImpl<>(orderMapper.ordersToOrderDtoList(orderPage.getContent()),
orderPage.getPageable(), orderPage.getTotalElements());
logger.trace("Dto page: {}.", () -> orderDtoPage);
return orderDtoPage;
}
public OrderDto convertToDto(Order order) {
return orderMapper.orderToOrderDto(order);
}
} }

View File

@ -1,40 +1,61 @@
package com.github.prominence.carrepair.validation; package com.github.prominence.carrepair.validation;
import com.github.prominence.carrepair.enums.OrderStatus; import com.github.prominence.carrepair.enums.OrderStatus;
import com.github.prominence.carrepair.model.Order; import com.github.prominence.carrepair.formatter.CustomDateTimeFormatter;
import com.github.prominence.carrepair.formatter.OrderStatusFormatter;
import com.github.prominence.carrepair.model.dto.OrderDto;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.validation.Errors; import org.springframework.validation.Errors;
import org.springframework.validation.Validator; import org.springframework.validation.Validator;
import java.text.ParseException;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.Locale;
@Component @Component
public class OrderValidator implements Validator { public class OrderValidator implements Validator {
private static final Logger logger = LogManager.getLogger(OrderValidator.class); private static final Logger logger = LogManager.getLogger(OrderValidator.class);
private CustomDateTimeFormatter customDateTimeFormatter;
private OrderStatusFormatter orderStatusFormatter;
public OrderValidator(CustomDateTimeFormatter customDateTimeFormatter, OrderStatusFormatter orderStatusFormatter) {
this.customDateTimeFormatter = customDateTimeFormatter;
this.orderStatusFormatter = orderStatusFormatter;
}
@Override @Override
public boolean supports(Class<?> aClass) { public boolean supports(Class<?> aClass) {
return Order.class.isAssignableFrom(aClass); return OrderDto.class.isAssignableFrom(aClass);
} }
@Override @Override
public void validate(Object o, Errors errors) { public void validate(Object o, Errors errors) {
Order order = (Order) o; OrderDto order = (OrderDto) o;
LocalDateTime finishedOn = order.getFinishedOn();
if (finishedOn != null) {
LocalDateTime createdOn = order.getCreatedOn();
if (createdOn != null && order.getFinishedOn().isBefore(order.getCreatedOn())) {
logger.debug("[{}] validation error: \"finishedOn\" value[{}] is before \"createdOn\"[{}].",
() -> order, () -> finishedOn, () -> createdOn);
errors.rejectValue("finishedOn", "error.finishedOn.finishedBeforeStarted");
}
if (order.getOrderStatus() != OrderStatus.ACCEPTED) { Locale locale = LocaleContextHolder.getLocale();
logger.debug("[{}] validation error: \"finishedOn\" cannot be set for order in status differ from {}.",
() -> order, () -> OrderStatus.ACCEPTED); if (order.getFinishedOnDate() != null) {
errors.rejectValue("finishedOn", "error.finishedOn.incompatibleStatus"); try {
LocalDateTime finishedOn = customDateTimeFormatter.parse(order.getFinishedOnDate(), locale);
LocalDateTime createdOn = customDateTimeFormatter.parse(order.getCreatedOnDate(), locale);
if (createdOn != null && finishedOn != null && finishedOn.isBefore(createdOn)) {
logger.debug("[{}] validation error: \"finishedOnDate\" value[{}] is before \"createdOn\"[{}].",
() -> order, () -> finishedOn, () -> createdOn);
errors.rejectValue("finishedOnDate", "error.finishedOn.finishedBeforeStarted");
}
OrderStatus orderStatus = orderStatusFormatter.parse(order.getOrderStatus(), locale);
if (finishedOn != null && orderStatus != OrderStatus.ACCEPTED) {
logger.debug("[{}] validation error: \"finishedOn\" cannot be set for order in status differ from {}.",
() -> order, () -> OrderStatus.ACCEPTED);
errors.rejectValue("finishedOnDate", "error.finishedOn.incompatibleStatus");
}
} catch (ParseException ex) {
logger.debug("Cannot parse: {}", () -> ex);
} }
} }
} }

View File

@ -9,33 +9,33 @@
<h1 th:text="#{client.title}"></h1> <h1 th:text="#{client.title}"></h1>
</div> </div>
<div layout:fragment="content"> <div layout:fragment="content">
<form th:action="'/client/' + ${client.id != null ? 'update/' + client.id : 'create'}" th:object="${client}" method="post"> <form th:action="'/client/' + ${clientDto.id != null ? 'update/' + clientDto.id : 'create'}" th:object="${clientDto}" method="post">
<div class="form-group row"> <div class="form-group row">
<label for="clientFirstName" class="col-sm-2 col-form-label" th:text="#{firstName.label}"></label> <label for="clientFirstName" class="col-sm-2 col-form-label" th:text="#{firstName.label}"></label>
<div class="col-sm-10"> <div class="col-sm-10">
<input type="text" name="firstName" class="form-control" id="clientFirstName" th:field="*{firstName}" th:errorclass="is-invalid" th:value="${client.firstName}"> <input type="text" name="firstName" class="form-control" id="clientFirstName" th:field="*{firstName}" th:errorclass="is-invalid" th:value="${clientDto.firstName}">
<div th:replace="common::errors('firstName')"></div> <div th:replace="common::errors('firstName')"></div>
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label for="clientMiddleName" class="col-sm-2 col-form-label" th:text="#{middleName.label}"></label> <label for="clientMiddleName" class="col-sm-2 col-form-label" th:text="#{middleName.label}"></label>
<div class="col-sm-10"> <div class="col-sm-10">
<input type="text" name="middleName" class="form-control" id="clientMiddleName" th:field="*{middleName}" th:errorclass="is-invalid" th:value="${client.middleName}"> <input type="text" name="middleName" class="form-control" id="clientMiddleName" th:field="*{middleName}" th:errorclass="is-invalid" th:value="${clientDto.middleName}">
<div th:replace="common::errors('middleName')"></div> <div th:replace="common::errors('middleName')"></div>
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label for="clientLastName" class="col-sm-2 col-form-label" th:text="#{lastName.label}"></label> <label for="clientLastName" class="col-sm-2 col-form-label" th:text="#{lastName.label}"></label>
<div class="col-sm-10"> <div class="col-sm-10">
<input type="text" name="lastName" class="form-control" id="clientLastName" th:field="*{lastName}" th:errorclass="is-invalid" th:value="${client.lastName}"> <input type="text" name="lastName" class="form-control" id="clientLastName" th:field="*{lastName}" th:errorclass="is-invalid" th:value="${clientDto.lastName}">
<div th:replace="common::errors('lastName')"></div> <div th:replace="common::errors('lastName')"></div>
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label for="clientPhoneNo" class="col-sm-2 col-form-label" th:text="#{phoneNo.label}"></label> <label for="clientPhoneNo" class="col-sm-2 col-form-label" th:text="#{phoneNo.label}"></label>
<div class="col-sm-10"> <div class="col-sm-10">
<input type="text" name="phoneNo" class="form-control" id="clientPhoneNo" th:field="*{phoneNo}" th:errorclass="is-invalid" th:value="${client.phoneNo}"> <input type="text" name="phone" class="form-control" id="clientPhoneNo" th:field="*{phone}" th:errorclass="is-invalid" th:value="${clientDto.phone}">
<div th:replace="common::errors('phoneNo')"></div> <div th:replace="common::errors('phone')"></div>
</div> </div>
</div> </div>
<div class="form-group row float-right"> <div class="form-group row float-right">

View File

@ -45,7 +45,7 @@
<td th:text="${client.firstName}"></td> <td th:text="${client.firstName}"></td>
<td th:text="${client.middleName}"></td> <td th:text="${client.middleName}"></td>
<td th:text="${client.lastName}"></td> <td th:text="${client.lastName}"></td>
<td th:text="${client.phoneNo}"></td> <td th:text="${client.phone}"></td>
<td> <td>
<div class="btn-group float-right"> <div class="btn-group float-right">
<a th:href="@{/client/edit/{id}(id=${client.id})}" class="btn btn-secondary" th:text="#{common.edit.button}"></a> <a th:href="@{/client/edit/{id}(id=${client.id})}" class="btn btn-secondary" th:text="#{common.edit.button}"></a>

View File

@ -9,32 +9,32 @@
<h1 th:text="#{mechanic.title}"></h1> <h1 th:text="#{mechanic.title}"></h1>
</div> </div>
<div layout:fragment="content"> <div layout:fragment="content">
<form th:action="'/mechanic/' + ${mechanic.id != null ? 'update/' + mechanic.id : 'create'}" th:object="${mechanic}" method="post"> <form th:action="'/mechanic/' + ${mechanicDto.id != null ? 'update/' + mechanicDto.id : 'create'}" th:object="${mechanicDto}" method="post">
<div class="form-group row"> <div class="form-group row">
<label for="mechanicFirstName" class="col-sm-2 col-form-label" th:text="#{firstName.label}"></label> <label for="mechanicFirstName" class="col-sm-2 col-form-label" th:text="#{firstName.label}"></label>
<div class="col-sm-10"> <div class="col-sm-10">
<input type="text" name="firstName" class="form-control" id="mechanicFirstName" th:field="*{firstName}" th:errorclass="is-invalid" th:value="${mechanic.firstName}"> <input type="text" name="firstName" class="form-control" id="mechanicFirstName" th:field="*{firstName}" th:errorclass="is-invalid" th:value="${mechanicDto.firstName}">
<div th:replace="common::errors('firstName')"></div> <div th:replace="common::errors('firstName')"></div>
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label for="mechanicMiddleName" class="col-sm-2 col-form-label" th:text="#{middleName.label}"></label> <label for="mechanicMiddleName" class="col-sm-2 col-form-label" th:text="#{middleName.label}"></label>
<div class="col-sm-10"> <div class="col-sm-10">
<input type="text" name="middleName" class="form-control" id="mechanicMiddleName" th:field="*{middleName}" th:errorclass="is-invalid" th:value="${mechanic.middleName}"> <input type="text" name="middleName" class="form-control" id="mechanicMiddleName" th:field="*{middleName}" th:errorclass="is-invalid" th:value="${mechanicDto.middleName}">
<div th:replace="common::errors('middleName')"></div> <div th:replace="common::errors('middleName')"></div>
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label for="mechanicLastName" class="col-sm-2 col-form-label" th:text="#{lastName.label}"></label> <label for="mechanicLastName" class="col-sm-2 col-form-label" th:text="#{lastName.label}"></label>
<div class="col-sm-10"> <div class="col-sm-10">
<input type="text" name="lastName" class="form-control" id="mechanicLastName" th:field="*{lastName}" th:errorclass="is-invalid" th:value="${mechanic.lastName}"> <input type="text" name="lastName" class="form-control" id="mechanicLastName" th:field="*{lastName}" th:errorclass="is-invalid" th:value="${mechanicDto.lastName}">
<div th:replace="common::errors('lastName')"></div> <div th:replace="common::errors('lastName')"></div>
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label for="mechanicHourlyPayment" class="col-sm-2 col-form-label" th:text="#{hourlyPayment.label}"></label> <label for="mechanicHourlyPayment" class="col-sm-2 col-form-label" th:text="#{hourlyPayment.label}"></label>
<div class="col-sm-10"> <div class="col-sm-10">
<input type="number" name="hourlyPayment" class="form-control" id="mechanicHourlyPayment" th:field="*{hourlyPayment}" th:errorclass="is-invalid" th:value="${mechanic.hourlyPayment}"> <input type="number" name="hourlyPayment" class="form-control" id="mechanicHourlyPayment" th:field="*{hourlyPayment}" th:errorclass="is-invalid" th:value="${mechanicDto.hourlyPayment}">
<div th:replace="common::errors('hourlyPayment')"></div> <div th:replace="common::errors('hourlyPayment')"></div>
</div> </div>
</div> </div>

View File

@ -10,56 +10,56 @@
<h1 th:text="#{order.title}"></h1> <h1 th:text="#{order.title}"></h1>
</div> </div>
<div layout:fragment="content"> <div layout:fragment="content">
<form th:action="'/order/' + ${order.id != null ? 'update/' + order.id : 'create'}" th:object="${order}" method="post"> <form th:action="'/order/' + ${orderDto.id != null ? 'update/' + orderDto.id : 'create'}" th:object="${orderDto}" method="post">
<div class="form-group row"> <div class="form-group row">
<label for="orderDescription" class="col-sm-2 col-form-label" th:text="#{description.label}"></label> <label for="orderDescription" class="col-sm-2 col-form-label" th:text="#{description.label}"></label>
<div class="col-sm-10"> <div class="col-sm-10">
<textarea name="description" class="form-control" id="orderDescription" th:field="*{description}" th:errorclass="is-invalid" th:value="${order.description}"></textarea> <textarea name="description" class="form-control" id="orderDescription" th:field="*{description}" th:errorclass="is-invalid" th:value="${orderDto.description}"></textarea>
<div th:replace="common::errors('description')"></div> <div th:replace="common::errors('description')"></div>
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label for="orderClient_tmp" class="col-sm-2 col-form-label" th:text="#{client.label}"></label> <label for="orderClient_tmp" class="col-sm-2 col-form-label" th:text="#{client.label}"></label>
<div class="input-group col-sm-10" th:with="client=${order.client}, clientInitials=${(client != null && client.id != null) ? (client.firstName+ ' ' + client.lastName) : null}"> <div class="input-group col-sm-10" th:with="clientInitials=${(orderDto.clientId != null) ? (orderDto.clientFirstName + ' ' + orderDto.clientLastName) : null}">
<span class="input-group-prepend input-group-text" id="orderClientPlaceholder" th:text="${clientInitials}"></span> <span class="input-group-prepend input-group-text" id="orderClientPlaceholder" th:text="${clientInitials}"></span>
<input type="text" name="clientId_tmp" class="form-control" id="orderClient_tmp" th:classappend="${#fields.hasErrors('client') ? 'is-invalid' : null}"> <input type="text" name="clientId_tmp" class="form-control" id="orderClient_tmp" th:classappend="${#fields.hasErrors('clientId') ? 'is-invalid' : null}">
<input type="hidden" name="clientId" id="orderClient" th:value="${client != null && client.id != null ? client.id : ''}"> <input type="hidden" name="clientId" id="orderClient" th:value="${orderDto.clientId}">
<div th:replace="common::errors('client')"></div> <div th:replace="common::errors('clientId')"></div>
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label for="orderMechanic" class="col-sm-2 col-form-label" th:text="#{mechanic.label}"></label> <label for="orderMechanic" class="col-sm-2 col-form-label" th:text="#{mechanic.label}"></label>
<div class="input-group col-sm-10" th:with="mechanic=${order.mechanic}, mechanicInitials=${(mechanic != null && mechanic.id != null) ? (mechanic.firstName+ ' ' + mechanic.lastName) : null}"> <div class="input-group col-sm-10" th:with="mechanicInitials=${(orderDto.mechanicId != null) ? (orderDto.mechanicFirstName + ' ' + orderDto.mechanicLastName) : null}">
<span class="input-group-prepend input-group-text" id="orderMechanicPlaceholder" th:text="${mechanicInitials}"></span> <span class="input-group-prepend input-group-text" id="orderMechanicPlaceholder" th:text="${mechanicInitials}"></span>
<input type="text" name="mechanicId_tmp" class="form-control" id="orderMechanic_tmp" th:classappend="${#fields.hasErrors('mechanic') ? 'is-invalid' : null}"> <input type="text" name="mechanicId_tmp" class="form-control" id="orderMechanic_tmp" th:classappend="${#fields.hasErrors('mechanicId') ? 'is-invalid' : null}">
<input type="hidden" name="mechanicId" id="orderMechanic" th:value="${mechanic != null && mechanic.id != null ? mechanic.id : ''}"> <input type="hidden" name="mechanicId" id="orderMechanic" th:value="${orderDto.mechanicId}">
<div th:replace="common::errors('mechanic')"></div> <div th:replace="common::errors('mechanicId')"></div>
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label for="orderCreatedOn" class="col-sm-2 col-form-label" th:text="#{createdOn.label}"></label> <label for="orderCreatedOn" class="col-sm-2 col-form-label" th:text="#{createdOn.label}"></label>
<div class="input-group col-sm-10"> <div class="input-group col-sm-10">
<input type="datetime-local" name="createdOn" class="form-control" id="orderCreatedOn" th:field="*{createdOn}" th:errorclass="is-invalid" th:value="${order.createdOn}"> <input type="datetime-local" name="createdOnDate" class="form-control" id="orderCreatedOn" th:field="*{createdOnDate}" th:errorclass="is-invalid" th:value="${orderDto.createdOnDate}">
<span class="input-group-append input-group-text"> <span class="input-group-append input-group-text">
<i class="far fa-calendar"></i> <i class="far fa-calendar"></i>
</span> </span>
<div th:replace="common::errors('createdOn')"></div> <div th:replace="common::errors('createdOnDate')"></div>
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label for="orderFinishedOn" class="col-sm-2 col-form-label" th:text="#{finishedOn.label}"></label> <label for="orderFinishedOn" class="col-sm-2 col-form-label" th:text="#{finishedOn.label}"></label>
<div class="input-group col-sm-10"> <div class="input-group col-sm-10">
<input type="datetime-local" name="finishedOn" class="form-control" id="orderFinishedOn" th:field="*{finishedOn}" th:errorclass="is-invalid" th:value="${order.finishedOn}"> <input type="datetime-local" name="finishedOnDate" class="form-control" id="orderFinishedOn" th:field="*{finishedOnDate}" th:errorclass="is-invalid" th:value="${orderDto.finishedOnDate}">
<span class="input-group-append input-group-text"> <span class="input-group-append input-group-text">
<i class="far fa-calendar"></i> <i class="far fa-calendar"></i>
</span> </span>
<div th:replace="common::errors('finishedOn')"></div> <div th:replace="common::errors('finishedOnDate')"></div>
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<label for="orderTotalPrice" class="col-sm-2 col-form-label" th:text="#{totalPrice.label}"></label> <label for="orderTotalPrice" class="col-sm-2 col-form-label" th:text="#{totalPrice.label}"></label>
<div class="input-group col-sm-10"> <div class="input-group col-sm-10">
<input type="number" name="totalPrice" class="form-control" id="orderTotalPrice" th:field="*{totalPrice}" th:errorclass="is-invalid" th:value="${order.totalPrice}"> <input type="number" name="totalPrice" class="form-control" id="orderTotalPrice" th:field="*{totalPrice}" th:errorclass="is-invalid" th:value="${orderDto.totalPrice}">
<span class="input-group-append input-group-text">EUR</span> <span class="input-group-append input-group-text">EUR</span>
<div th:replace="common::errors('totalPrice')"></div> <div th:replace="common::errors('totalPrice')"></div>
</div> </div>
@ -67,7 +67,7 @@
<div class="form-group row"> <div class="form-group row">
<label for="orderStatus" class="col-sm-2 col-form-label" th:text="#{status.label}"></label> <label for="orderStatus" class="col-sm-2 col-form-label" th:text="#{status.label}"></label>
<div class="col-sm-10"> <div class="col-sm-10">
<select name="orderStatus" class="form-control custom-select" id="orderStatus" th:field="*{orderStatus}" th:errorclass="is-invalid" th:value="${order.orderStatus}"> <select name="orderStatus" class="form-control custom-select" id="orderStatus" th:field="*{orderStatus}" th:errorclass="is-invalid" th:value="${orderDto.orderStatus}">
<option th:each="orderStatus : ${orderStatuses}" th:value="${orderStatus.toString()}" th:text="${{orderStatus}}"></option> <option th:each="orderStatus : ${orderStatuses}" th:value="${orderStatus.toString()}" th:text="${{orderStatus}}"></option>
</select> </select>
<div th:replace="common::errors('orderStatus')"></div> <div th:replace="common::errors('orderStatus')"></div>

View File

@ -74,11 +74,11 @@
</thead> </thead>
<tbody> <tbody>
<tr th:each="order : ${orderList}" th:id="'order-row-' + ${order.id}"> <tr th:each="order : ${orderList}" th:id="'order-row-' + ${order.id}">
<td th:text="${order.client.firstName + ' ' + order.client.lastName + '(' + order.client.id + ')'}"></td> <td th:text="${order.clientFirstName + ' ' + order.clientLastName + '(' + order.clientId + ')'}"></td>
<td th:text="${order.mechanic.firstName + ' ' + order.mechanic.lastName + '(' + order.mechanic.id + ')'}"></td> <td th:text="${order.mechanicFirstName + ' ' + order.mechanicLastName + '(' + order.mechanicId + ')'}"></td>
<td class="word-breakable" th:text="${T(org.apache.commons.lang3.StringUtils).abbreviate(order.description, 128)}"></td> <td class="word-breakable" th:text="${T(org.apache.commons.lang3.StringUtils).abbreviate(order.description, 128)}"></td>
<td th:text="${{order.createdOn}}"></td> <td th:text="${{order.createdOnDate}}"></td>
<td th:text="${{order.finishedOn}}"></td> <td th:text="${{order.finishedOnDate}}"></td>
<td th:text="${{order.orderStatus}}"></td> <td th:text="${{order.orderStatus}}"></td>
<td th:text="${{order.totalPrice}}"></td> <td th:text="${{order.totalPrice}}"></td>
<td style="break-inside: avoid"> <td style="break-inside: avoid">