Validation & UI fixes.

This commit is contained in:
Alexey Zinchenko 2019-05-16 00:32:30 +03:00
parent af110658b6
commit 1b07e1d3fe
8 changed files with 60 additions and 31 deletions

View File

@ -15,7 +15,7 @@ public class OrderStatusFormatter implements Formatter<OrderStatus> {
private static final Logger logger = LogManager.getLogger(OrderStatusFormatter.class); private static final Logger logger = LogManager.getLogger(OrderStatusFormatter.class);
@Override @Override
public OrderStatus parse(String text, Locale locale) throws ParseException { public OrderStatus parse(String text, Locale locale) {
final OrderStatus parsedOrderStatus = OrderStatus.valueOf(text.toUpperCase()); final OrderStatus parsedOrderStatus = OrderStatus.valueOf(text.toUpperCase());
logger.trace("Parsing String[{}] to OrderStatus instance: {}.", () -> text, () -> parsedOrderStatus); logger.trace("Parsing String[{}] to OrderStatus instance: {}.", () -> text, () -> parsedOrderStatus);
return parsedOrderStatus; return parsedOrderStatus;

View File

@ -1,17 +1,21 @@
package com.github.prominence.carrepair.model.mapper; package com.github.prominence.carrepair.model.mapper;
import com.github.prominence.carrepair.enums.OrderStatus;
import com.github.prominence.carrepair.formatter.CustomDateTimeFormatter; import com.github.prominence.carrepair.formatter.CustomDateTimeFormatter;
import com.github.prominence.carrepair.formatter.OrderStatusFormatter;
import com.github.prominence.carrepair.model.domain.Order; import com.github.prominence.carrepair.model.domain.Order;
import com.github.prominence.carrepair.model.dto.OrderDto; import com.github.prominence.carrepair.model.dto.OrderDto;
import org.mapstruct.InheritInverseConfiguration; import org.apache.commons.lang3.StringUtils;
import org.mapstruct.Mapper; import org.mapstruct.*;
import org.mapstruct.Mapping; import org.springframework.beans.factory.annotation.Autowired;
import org.mapstruct.Mappings;
import java.util.List; import java.util.List;
@Mapper(componentModel = "spring") @Mapper(componentModel = "spring", injectionStrategy = InjectionStrategy.CONSTRUCTOR)
public interface OrderMapper { public abstract class OrderMapper {
@Autowired
protected OrderStatusFormatter orderStatusFormatter;
@Mappings({ @Mappings({
@Mapping(source = "id", target = "id"), @Mapping(source = "id", target = "id"),
@ -26,10 +30,29 @@ public interface OrderMapper {
@Mapping(source = "createdOn", target = "createdOnDate", dateFormat = CustomDateTimeFormatter.DATETIME_PATTERN), @Mapping(source = "createdOn", target = "createdOnDate", dateFormat = CustomDateTimeFormatter.DATETIME_PATTERN),
@Mapping(source = "finishedOn", target = "finishedOnDate", dateFormat = CustomDateTimeFormatter.DATETIME_PATTERN) @Mapping(source = "finishedOn", target = "finishedOnDate", dateFormat = CustomDateTimeFormatter.DATETIME_PATTERN)
}) })
OrderDto orderToOrderDto(Order order); abstract public OrderDto orderToOrderDto(Order order);
@InheritInverseConfiguration @InheritInverseConfiguration
Order orderDtoToOrder(OrderDto orderDto); abstract public Order orderDtoToOrder(OrderDto orderDto);
List<OrderDto> ordersToOrderDtoList(List<Order> orders); abstract public List<OrderDto> ordersToOrderDtoList(List<Order> orders);
public String orderStatusToString(OrderStatus orderStatus) {
return orderStatusFormatter.print(orderStatus, null);
}
public OrderStatus stringToOrderStatus(String status) {
return orderStatusFormatter.parse(status, null);
}
@BeforeMapping
protected void checkForEmptyStrings(OrderDto orderDto) {
if (StringUtils.isEmpty(orderDto.getCreatedOnDate())) {
orderDto.setCreatedOnDate(null);
}
if (StringUtils.isEmpty(orderDto.getFinishedOnDate())) {
orderDto.setFinishedOnDate(null);
}
}
} }

View File

@ -4,6 +4,7 @@ import com.github.prominence.carrepair.enums.OrderStatus;
import com.github.prominence.carrepair.formatter.CustomDateTimeFormatter; import com.github.prominence.carrepair.formatter.CustomDateTimeFormatter;
import com.github.prominence.carrepair.formatter.OrderStatusFormatter; import com.github.prominence.carrepair.formatter.OrderStatusFormatter;
import com.github.prominence.carrepair.model.dto.OrderDto; import com.github.prominence.carrepair.model.dto.OrderDto;
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.context.i18n.LocaleContextHolder; import org.springframework.context.i18n.LocaleContextHolder;
@ -11,7 +12,6 @@ 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; import java.util.Locale;
@ -37,25 +37,28 @@ public class OrderValidator implements Validator {
OrderDto order = (OrderDto) o; OrderDto order = (OrderDto) o;
Locale locale = LocaleContextHolder.getLocale(); Locale locale = LocaleContextHolder.getLocale();
OrderStatus orderStatus = orderStatusFormatter.parse(order.getOrderStatus(), locale);
if (order.getFinishedOnDate() != null) { if (StringUtils.isNotBlank(order.getFinishedOnDate())) {
try { LocalDateTime finishedOn = customDateTimeFormatter.parse(order.getFinishedOnDate(), locale);
LocalDateTime finishedOn = customDateTimeFormatter.parse(order.getFinishedOnDate(), locale); LocalDateTime createdOn = customDateTimeFormatter.parse(order.getCreatedOnDate(), locale);
LocalDateTime createdOn = customDateTimeFormatter.parse(order.getCreatedOnDate(), locale); if (createdOn != null && finishedOn != null && finishedOn.isBefore(createdOn)) {
if (createdOn != null && finishedOn != null && finishedOn.isBefore(createdOn)) { logger.debug("[{}] validation error: \"finishedOnDate\" value[{}] is before \"createdOn\"[{}].",
logger.debug("[{}] validation error: \"finishedOnDate\" value[{}] is before \"createdOn\"[{}].", () -> order, () -> finishedOn, () -> createdOn);
() -> order, () -> finishedOn, () -> createdOn); errors.rejectValue("finishedOnDate", "error.finishedOn.finishedBeforeStarted");
errors.rejectValue("finishedOnDate", "error.finishedOn.finishedBeforeStarted"); }
}
OrderStatus orderStatus = orderStatusFormatter.parse(order.getOrderStatus(), locale);
if (finishedOn != null && orderStatus != OrderStatus.ACCEPTED) { if (finishedOn != null && orderStatus != OrderStatus.ACCEPTED) {
logger.debug("[{}] validation error: \"finishedOn\" cannot be set for order in status differ from {}.", logger.debug("[{}] validation error: \"finishedOn\" cannot be set for order in status differ from {}.",
() -> order, () -> OrderStatus.ACCEPTED); () -> order, () -> OrderStatus.ACCEPTED);
errors.rejectValue("finishedOnDate", "error.finishedOn.incompatibleStatus"); errors.rejectValue("finishedOnDate", "error.finishedOn.incompatibleStatus");
} }
} catch (ParseException ex) { } else {
logger.debug("Cannot parse: {}", () -> ex); if (orderStatus == OrderStatus.ACCEPTED) {
logger.debug("[{}] validation error: \"orderStatus\" cannot be set to {} until \"finishedOn\" isn't set.",
() -> order, () -> OrderStatus.ACCEPTED);
errors.rejectValue("orderStatus", "error.orderStatus.acceptedButNotFinished");
} }
} }
} }

View File

@ -47,3 +47,4 @@ common.deleteConfirmation.label = Are you sure to delete?
# validation # validation
error.finishedOn.finishedBeforeStarted = value must be after starting date error.finishedOn.finishedBeforeStarted = value must be after starting date
error.finishedOn.incompatibleStatus = order cannot be finished until it isn't accepted by client error.finishedOn.incompatibleStatus = order cannot be finished until it isn't accepted by client
error.orderStatus.acceptedButNotFinished = order cannot be accepter until it isn't finished

View File

@ -47,3 +47,4 @@ common.deleteConfirmation.label = Are you sure to delete?
# validation # validation
error.finishedOn.finishedBeforeStarted = value must be after starting date error.finishedOn.finishedBeforeStarted = value must be after starting date
error.finishedOn.incompatibleStatus = order cannot be finished until it isn't accepted by client error.finishedOn.incompatibleStatus = order cannot be finished until it isn't accepted by client
error.orderStatus.acceptedButNotFinished = order cannot be accepter until it isn't finished

View File

@ -47,3 +47,4 @@ common.deleteConfirmation.label = Вы действительно хотите
# validation # validation
error.finishedOn.finishedBeforeStarted = дата окончания должна быть после даты начала error.finishedOn.finishedBeforeStarted = дата окончания должна быть после даты начала
error.finishedOn.incompatibleStatus = заказ не может быть закончен если клиент еще не принял его error.finishedOn.incompatibleStatus = заказ не может быть закончен если клиент еще не принял его
error.orderStatus.acceptedButNotFinished = заказ не может быть принят пока он не закончен

View File

@ -36,10 +36,10 @@
</ul> </ul>
</div> </div>
<div class="float-right"> <div class="float-right">
<a th:href="@{?lang=en}"> <a th:href="@{/?lang=en}">
<img th:src="@{/images/us.png}" th:alt="#{lang.en}"/> <img th:src="@{/images/us.png}" th:alt="#{lang.en}"/>
</a> </a>
<a th:href="@{?lang=ru}"> <a th:href="@{/?lang=ru}">
<img th:src="@{/images/ru.png}" th:alt="#{lang.ru}"/> <img th:src="@{/images/ru.png}" th:alt="#{lang.ru}"/>
</a> </a>
</div> </div>

View File

@ -68,7 +68,7 @@
<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="${orderDto.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}}" th:text="${{orderStatus}}"></option>
</select> </select>
<div th:replace="common::errors('orderStatus')"></div> <div th:replace="common::errors('orderStatus')"></div>
</div> </div>