Added CRUD for 'Mechanic' entity. Added translations.

This commit is contained in:
Alexey Zinchenko 2019-05-05 14:50:00 +03:00
parent b541e69885
commit 0124899525
15 changed files with 453 additions and 23 deletions

View File

@ -16,4 +16,7 @@ public class CarRepairApplication {
}
// TODO: logging
// TODO: DTO
// TODO: DTO
// TODO: i18n
// TODO: tests
// TODO: big data

View File

@ -0,0 +1,98 @@
package com.github.prominence.carrepair.controller;
import com.github.prominence.carrepair.enums.OrderStatus;
import com.github.prominence.carrepair.model.Mechanic;
import com.github.prominence.carrepair.service.MechanicService;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.ui.ModelMap;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.Map;
import java.util.Optional;
@Controller
@RequestMapping("/mechanic")
public class MechanicController {
private MechanicService mechanicService;
public MechanicController(MechanicService mechanicService) {
this.mechanicService = mechanicService;
}
@GetMapping
public String index(ModelMap modelMap) {
Page<Mechanic> clientList = mechanicService.findAll((Pageable) modelMap.get("pagination"));
modelMap.addAttribute("mechanicList", clientList.getContent());
modelMap.addAttribute("totalPages", clientList.getTotalPages());
return "mechanic/index";
}
@GetMapping(value = "/create")
public String create(Model model) {
model.addAttribute("mechanic", new Mechanic());
return "mechanic/edit";
}
@GetMapping(value = "/edit/{id}")
public String edit(@PathVariable Long id, Model model) {
Optional<Mechanic> clientOptional = mechanicService.findById(id);
if (clientOptional.isPresent()) {
model.addAttribute("mechanic", clientOptional.get());
return "mechanic/edit";
} else {
// TODO: need to show warning
return "redirect:/mechanic";
}
}
@PostMapping(value = "/update/{id}")
public String update(@Valid Mechanic client, BindingResult bindingResult, @PathVariable long id) {
if (bindingResult.hasErrors()) {
client.setId(id); // why should we do this?
return "mechanic/edit";
}
mechanicService.save(client);
return "redirect:/mechanic";
}
@PostMapping(value = "/create")
public String save(@Valid Mechanic client, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return "mechanic/edit";
}
mechanicService.save(client);
return "redirect:/mechanic";
}
@DeleteMapping(value = "/delete/{id}")
public ResponseEntity delete(@PathVariable Long id) {
boolean deleteSuccess = mechanicService.deleteMechanicById(id);
if (deleteSuccess) {
return ResponseEntity.ok().build();
} else {
return ResponseEntity.notFound().build();
}
}
@GetMapping(value = "/statistics/{id}")
public String statistics(@PathVariable Long id, Model model) {
Map<OrderStatus, Integer> mechanicStatistics = mechanicService.getOrderStatistics(id);
model.addAttribute("statistics", mechanicStatistics);
return "mechanic/statistics";
}
}

View File

@ -2,11 +2,14 @@ package com.github.prominence.carrepair.demo;
import com.github.javafaker.Faker;
import com.github.prominence.carrepair.model.Client;
import com.github.prominence.carrepair.repository.ClientRepository;
import com.github.prominence.carrepair.model.Mechanic;
import com.github.prominence.carrepair.service.ClientService;
import com.github.prominence.carrepair.service.MechanicService;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@ -15,10 +18,10 @@ import java.util.stream.Stream;
public class DemoDataPopulator {
private static final int COUNT = 10;
@Bean
public CommandLineRunner demoData(ClientRepository clientRepository) {
Faker faker = new Faker();
private Faker faker = new Faker();
@Bean
public CommandLineRunner clientDemoData(ClientService clientService) {
List<Client> demoClientList = Stream.generate(() -> {
Client client = new Client();
client.setFirstName(faker.name().firstName());
@ -30,7 +33,24 @@ public class DemoDataPopulator {
}).limit(COUNT).collect(Collectors.toList());
return args -> {
demoClientList.forEach(clientRepository::save);
demoClientList.forEach(clientService::save);
};
}
@Bean
public CommandLineRunner mechanicDemoData(MechanicService mechanicService) {
List<Mechanic> demoMechanicList = Stream.generate(() -> {
Mechanic mechanic = new Mechanic();
mechanic.setFirstName(faker.name().firstName());
mechanic.setLastName(faker.name().lastName());
mechanic.setMiddleName(faker.name().username());
mechanic.setHourlyPayment(BigDecimal.valueOf(faker.number().randomDouble(3, 100, 999)));
System.out.println(mechanic); // demo output
return mechanic;
}).limit(COUNT).collect(Collectors.toList());
return args -> {
demoMechanicList.forEach(mechanicService::save);
};
}

View File

@ -3,14 +3,17 @@ package com.github.prominence.carrepair.model;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import java.math.BigDecimal;
import java.util.Objects;
@Entity
@Table(name = "mechanic")
public class Mechanic extends Person {
@NotNull
@Min(value = 0)
@Column(name = "hourlyPayment")
private BigDecimal hourlyPayment;
@ -29,4 +32,29 @@ public class Mechanic extends Person {
public void setHourlyPayment(BigDecimal hourlyPayment) {
this.hourlyPayment = hourlyPayment;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Mechanic)) return false;
if (!super.equals(o)) return false;
Mechanic mechanic = (Mechanic) o;
return Objects.equals(hourlyPayment, mechanic.hourlyPayment);
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), hourlyPayment);
}
@Override
public String toString() {
return "Mechanic{" +
"hourlyPayment=" + hourlyPayment +
", id=" + id +
", firstName='" + firstName + '\'' +
", middleName='" + middleName + '\'' +
", lastName='" + lastName + '\'' +
'}';
}
}

View File

@ -4,6 +4,10 @@ import com.github.prominence.carrepair.model.Order;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
public List<Order> findAllByMechanic_Id(Long id);
}

View File

@ -0,0 +1,61 @@
package com.github.prominence.carrepair.service;
import com.github.prominence.carrepair.enums.OrderStatus;
import com.github.prominence.carrepair.model.Mechanic;
import com.github.prominence.carrepair.model.Order;
import com.github.prominence.carrepair.repository.MechanicRepository;
import com.github.prominence.carrepair.repository.OrderRepository;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@Service
public class MechanicService {
private MechanicRepository mechanicRepository;
private OrderRepository orderRepository;
public MechanicService(MechanicRepository mechanicRepository, OrderRepository orderRepository) {
this.mechanicRepository = mechanicRepository;
this.orderRepository = orderRepository;
}
public Page<Mechanic> findAll(Pageable pageable) {
return mechanicRepository.findAll(pageable);
}
public Optional<Mechanic> findById(Long id) {
return mechanicRepository.findById(id);
}
public Mechanic save(Mechanic client) {
return mechanicRepository.save(client);
}
public boolean deleteMechanicById(Long id) {
try {
mechanicRepository.deleteById(id);
return true;
} catch (Exception e) {
return false;
}
}
public Map<OrderStatus, Integer> getOrderStatistics(Long mechanicId) {
Map<OrderStatus, Integer> statistics = new HashMap<>();
statistics.put(OrderStatus.SCHEDULED, 0);
statistics.put(OrderStatus.ACCEPTED, 0);
statistics.put(OrderStatus.DONE, 0);
List<Order> mechanicOrders = orderRepository.findAllByMechanic_Id(mechanicId);
mechanicOrders.forEach(order -> statistics.merge(order.getOrderStatus(), 1, Integer::sum));
return statistics;
}
}

View File

@ -6,4 +6,25 @@ badge.orders = Orders
home.requirements.label = Requirements
client.title = Clients
client.title = Clients
mechanic.title = Mechanics
firstName.label = First Name
middleName.label = Middle Name
lastName.label = Last Name
phoneNo.label = Phone No
hourlyPayment.label = Hourly Payment
status.label = Status
amount.label = Amount
showStatistics.button = Statistics
status.scheduled.label = Scheduled
status.done.label = Done
status.accepted.label = Accepted by client
common.create.button = Create
common.delete.button = Delete
common.save.button = Save
common.edit.button = Edit
common.back.button = Back

View File

@ -6,4 +6,25 @@ badge.orders = Orders
home.requirements.label = Requirements
client.title = Clients
client.title = Clients
mechanic.title = Mechanics
firstName.label = First Name
middleName.label = Middle Name
lastName.label = Last Name
phoneNo.label = Phone No
hourlyPayment.label = Hourly Payment
status.label = Status
amount.label = Amount
showStatistics.button = Statistics
status.scheduled.label = Scheduled
status.done.label = Done
status.accepted.label = Accepted by client
common.create.button = Create
common.delete.button = Delete
common.save.button = Save
common.edit.button = Edit
common.back.button = Back

View File

@ -6,4 +6,25 @@ badge.orders = Заказы
home.requirements.label = Требования
client.title = Клиенты
client.title = Клиенты
mechanic.title = Механики
firstName.label = Имя
middleName.label = Отчество
lastName.label = Фамилия
phoneNo.label = Номер телефона
hourlyPayment.label = Почасовая оплата
status.label = Статус
amount.label = Количество
showStatistics.button = Статистика
status.scheduled.label = Запланировано
status.done.label = Выполнено
status.accepted.label = Принято клиентом
common.create.button = Создать
common.delete.button = Удалить
common.save.button = Сохранить
common.edit.button = Редактировать
common.back.button = Назад

View File

@ -10,28 +10,28 @@
<form th:action="'/client/' + ${client.id != null ? 'update/' + client.id : 'create'}" th:object="${client}" method="post">
<div class="form-group row">
<label for="clientFirstName" class="col-sm-2 col-form-label">First Name</label>
<label for="clientFirstName" class="col-sm-2 col-form-label" th:text="#{firstName.label}"></label>
<div class="col-sm-10">
<input type="text" name="firstName" class="form-control" id="clientFirstName" th:field="*{firstName}" th:errorclass="fieldError" th:value="${client.firstName}">
<div th:replace="common::errors('firstName')"></div>
</div>
</div>
<div class="form-group row">
<label for="clientMiddleName" class="col-sm-2 col-form-label">Middle Name</label>
<label for="clientMiddleName" class="col-sm-2 col-form-label" th:text="#{middleName.label}"></label>
<div class="col-sm-10">
<input type="text" name="middleName" class="form-control" id="clientMiddleName" th:field="*{middleName}" th:errorclass="fieldError" th:value="${client.middleName}">
<div th:replace="common::errors('middleName')"></div>
</div>
</div>
<div class="form-group row">
<label for="clientLastName" class="col-sm-2 col-form-label">Last Name</label>
<label for="clientLastName" class="col-sm-2 col-form-label" th:text="#{lastName.label}"></label>
<div class="col-sm-10">
<input type="text" name="lastName" class="form-control" id="clientLastName" th:field="*{lastName}" th:errorclass="fieldError" th:value="${client.lastName}">
<div th:replace="common::errors('lastName')"></div>
</div>
</div>
<div class="form-group row">
<label for="clientPhoneNo" class="col-sm-2 col-form-label">Phone No</label>
<label for="clientPhoneNo" class="col-sm-2 col-form-label" th:text="#{phoneNo.label}"></label>
<div class="col-sm-10">
<input type="text" name="phoneNo" class="form-control" id="clientPhoneNo" th:field="*{phoneNo}" th:errorclass="fieldError" th:value="${client.phoneNo}">
<div th:replace="common::errors('phoneNo')"></div>
@ -39,8 +39,8 @@
</div>
<div class="form-group row pull-right">
<div class="col-sm-12">
<a href="/client" class="btn btn-default">Back</a>
<button type="submit" class="btn btn-primary">Save</button>
<a href="/client" class="btn btn-default" th:text="#{common.back.button}"></a>
<button type="submit" class="btn btn-primary" th:text="#{common.save.button}"></button>
</div>
</div>
</form>

View File

@ -26,16 +26,16 @@
<h1 th:text="#{client.title}"></h1>
<div class="pull-right">
<a class="btn btn-success" th:href="@{/client/create}">Create</a>
<a class="btn btn-success" th:href="@{/client/create}" th:text="#{common.create.button}"></a>
</div>
<table class="table table-striped table-hover">
<thead>
<tr>
<th>First Name</th>
<th>Middle Name</th>
<th>Last Name</th>
<th>Phone No</th>
<th th:text="#{firstName.label}"></th>
<th th:text="#{middleName.label}"></th>
<th th:text="#{lastName.label}"></th>
<th th:text="#{phoneNo.label}"></th>
<th>&nbsp;</th>
</tr>
</thead>
@ -47,8 +47,8 @@
<td th:text="${client.phoneNo}"></td>
<td>
<div class="btn-group pull-right">
<a th:href="@{/client/edit/{id}(id=${client.id})}" class="btn btn-default">Edit</a>
<button type="button" class="btn btn-danger" th:onclick="'deleteClientById(' + ${client.id} + ')'">Delete</button>
<a th:href="@{/client/edit/{id}(id=${client.id})}" class="btn btn-default" th:text="#{common.edit.button}"></a>
<button type="button" class="btn btn-danger" th:onclick="'deleteClientById(' + ${client.id} + ')'" th:text="#{common.delete.button}"></button>
</div>
</td>
</tr>

View File

@ -20,7 +20,7 @@
<div id="navbar" class="collapse navbar-collapse">
<ul class="nav navbar-nav">
<li><a th:href="@{/client/}"><th:block th:text="#{badge.clients}"/> <span class="badge" th:text="${clientsCount}"></span></a></li>
<li><a href="#"><th:block th:text="#{badge.mechanics}"/> <span class="badge" th:text="${mechanicsCount}"></span></a></li>
<li><a th:href="@{/mechanic/}"><th:block th:text="#{badge.mechanics}"/> <span class="badge" th:text="${mechanicsCount}"></span></a></li>
<li><a href="#"><th:block th:text="#{badge.orders}"/> <span class="badge" th:text="${ordersCount}"></span></a></li>
</ul>
</div>

View File

@ -0,0 +1,50 @@
<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{main}">
<head>
<title th:text="#{default.title}"></title>
</head>
<body>
<div layout:fragment="content">
<h1 th:text="#{mechanic.title}"></h1>
<form th:action="'/mechanic/' + ${mechanic.id != null ? 'update/' + mechanic.id : 'create'}" th:object="${mechanic}" method="post">
<div class="form-group row">
<label for="mechanicFirstName" class="col-sm-2 col-form-label" th:text="#{firstName.label}"></label>
<div class="col-sm-10">
<input type="text" name="firstName" class="form-control" id="mechanicFirstName" th:field="*{firstName}" th:errorclass="fieldError" th:value="${mechanic.firstName}">
<div th:replace="common::errors('firstName')"></div>
</div>
</div>
<div class="form-group row">
<label for="mechanicMiddleName" class="col-sm-2 col-form-label" th:text="#{middleName.label}"></label>
<div class="col-sm-10">
<input type="text" name="middleName" class="form-control" id="mechanicMiddleName" th:field="*{middleName}" th:errorclass="fieldError" th:value="${mechanic.middleName}">
<div th:replace="common::errors('middleName')"></div>
</div>
</div>
<div class="form-group row">
<label for="mechanicLastName" class="col-sm-2 col-form-label" th:text="#{lastName.label}"></label>
<div class="col-sm-10">
<input type="text" name="lastName" class="form-control" id="mechanicLastName" th:field="*{lastName}" th:errorclass="fieldError" th:value="${mechanic.lastName}">
<div th:replace="common::errors('lastName')"></div>
</div>
</div>
<div class="form-group row">
<label for="mechanicHourlyPayment" class="col-sm-2 col-form-label" th:text="#{hourlyPayment.label}"></label>
<div class="col-sm-10">
<input type="number" name="phoneNo" class="form-control" id="mechanicHourlyPayment" th:field="*{hourlyPayment}" th:errorclass="fieldError" th:value="${mechanic.hourlyPayment}">
<div th:replace="common::errors('hourlyPayment')"></div>
</div>
</div>
<div class="form-group row pull-right">
<div class="col-sm-12">
<a href="/mechanic" class="btn btn-default" th:text="#{common.back.button}"></a>
<button type="submit" class="btn btn-primary" th:text="#{common.save.button}"></button>
</div>
</div>
</form>
</div>
</body>
</html>

View File

@ -0,0 +1,62 @@
<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{main}">
<head>
<title th:text="#{default.title}"></title>
<script th:inline="javascript">
function deleteMechanicById(id) {
const confirmationResult = confirm("Are you sure to delete?");
if (confirmationResult) {
const url = /*[[@{/mechanic/delete/}]]*/;
$.ajax({
url: url + id,
type: 'DELETE',
success: function () {
$('#mechanic-row-' + id).remove();
}
});
}
}
</script>
</head>
<body>
<div layout:fragment="content">
<h1 th:text="#{mechanic.title}"></h1>
<div class="pull-right">
<a class="btn btn-success" th:href="@{/mechanic/create}" th:text="#{common.create.button}"></a>
</div>
<table class="table table-striped table-hover">
<thead>
<tr>
<th th:text="#{firstName.label}"></th>
<th th:text="#{middleName.label}"></th>
<th th:text="#{lastName.label}"></th>
<th th:text="#{hourlyPayment.label}"></th>
<th>&nbsp;</th>
</tr>
</thead>
<tbody>
<tr th:each="mechanic : ${mechanicList}" th:id="'mechanic-row-' + ${mechanic.id}">
<td th:text="${mechanic.firstName}"></td>
<td th:text="${mechanic.middleName}"></td>
<td th:text="${mechanic.lastName}"></td>
<td th:text="${mechanic.hourlyPayment}"></td>
<td>
<div class="btn-group pull-right">
<a th:href="@{/mechanic/statistics/{id}(id=${mechanic.id})}" class="btn btn-info" th:text="#{showStatistics.button}"></a>
<a th:href="@{/mechanic/edit/{id}(id=${mechanic.id})}" class="btn btn-default" th:text="#{common.edit.button}"></a>
<button type="button" class="btn btn-danger" th:onclick="'deleteMechanicById(' + ${mechanic.id} + ')'" th:text="#{common.delete.button}"></button>
</div>
</td>
</tr>
</tbody>
</table>
<div th:replace="common::pagination(@{/mechanic/}, ${page}, ${totalPages})"></div>
</div>
</body>
</html>

View File

@ -0,0 +1,41 @@
<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{main}">
<head>
<title th:text="#{default.title}"></title>
</head>
<body>
<div layout:fragment="content">
<h1 th:text="#{mechanic.title}"></h1>
<table class="table table-striped table-hover">
<thead>
<tr>
<th th:text="#{status.label}"></th>
<th th:text="#{amount.label}"></th>
</tr>
</thead>
<tbody>
<tr>
<td th:text="#{status.scheduled.label}"></td>
<td th:text="${statistics.get(T(com.github.prominence.carrepair.enums.OrderStatus).SCHEDULED)}"></td>
</tr>
<tr>
<td th:text="#{status.done.label}"></td>
<td th:text="${statistics.get(T(com.github.prominence.carrepair.enums.OrderStatus).DONE)}"></td>
</tr>
<tr>
<td th:text="#{status.accepted.label}"></td>
<td th:text="${statistics.get(T(com.github.prominence.carrepair.enums.OrderStatus).ACCEPTED)}"></td>
</tr>
</tbody>
</table>
<div class="form-group row pull-right">
<div class="col-sm-12">
<a href="/mechanic" class="btn btn-default" th:text="#{common.back.button}"></a>
</div>
</div>
</div>
</body>
</html>