Commit 2a31c172 authored by Vladimir Trubachoff's avatar Vladimir Trubachoff

Merge remote-tracking branch 'origin/fixes' into develop

parents fd540771 a1c8b5b1
package com.example.testj.domain;
import jakarta.persistence.*;
import jakarta.validation.constraints.Digits;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import jakarta.validation.constraints.NotEmpty;
import java.util.List;
@Entity
@Table(name = "goods")
public class Goods {
......@@ -18,13 +19,22 @@ public class Goods {
@Column(name = "name")
private String name;
@Digits
@NotEmpty
@Column(name = "price")
private Number price;
private double price;
public Goods() {
}
public Goods(Long id, String name, Double price) {
this.id = id;
this.name = name;
this.price = price;
}
@OneToMany(mappedBy = "goods")
List<OrderLine> orderLines;
public Goods(Goods goods) {
this.name = goods.getName();
this.price = goods.getPrice();
}
public Long getId() {
return id;
......@@ -42,11 +52,11 @@ public class Goods {
this.name = name;
}
public Number getPrice() {
public Double getPrice() {
return price;
}
public void setPrice(Number price) {
public void setPrice(Double price) {
this.price = price;
}
}
package com.example.testj.domain;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotBlank;
import java.util.List;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.List;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
import jakarta.validation.constraints.NotBlank;
@Entity
@Table(name = "orders")
......@@ -26,7 +34,7 @@ public class Order {
@Column(name = "address")
private String address;
@OneToMany(mappedBy = "order", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@OneToMany(mappedBy = "order", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
List<OrderLine> orderLines;
public Order() {
......@@ -38,10 +46,23 @@ public class Order {
this.date = date;
}
public void addOrderLine(OrderLine orderLine) {
orderLines.add(orderLine);
orderLine.setOrder(this);
}
public void removeOrderLine(OrderLine orderLine) {
orderLines.remove(orderLine);
}
public List<OrderLine> getOrderLines() {
return orderLines;
}
public void setOrderLines(List<OrderLine> orderLines) {
this.orderLines = orderLines;
}
public String getDate() {
return date;
}
......
package com.example.testj.domain;
import jakarta.persistence.*;
import org.springframework.format.annotation.NumberFormat;
import java.util.Set;
import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import jakarta.validation.constraints.Min;
@Entity
@Table(name = "order_line")
......@@ -13,20 +21,20 @@ public class OrderLine {
@Column(name = "id")
private Long id;
@JsonIgnore
@ManyToOne
@JoinColumn(name = "order_id", nullable = false)
@JoinColumn(name = "order_id")
private Order order;
@ManyToOne
@JoinColumn(name = "goods_id", nullable = false)
@JoinColumn(name = "goods_id")
private Goods goods;
@Column(name = "count")
@NumberFormat
@Min(1)
private int count;
public Long getId() {
return id;
}
......@@ -46,4 +54,17 @@ public class OrderLine {
public Goods getGoods() {
return goods;
}
public void setGoods(Goods goods) {
this.goods = goods;
}
public Order getOrder() {
return order;
}
public void setOrder(Order order) {
this.order = order;
}
}
......@@ -2,8 +2,9 @@ package com.example.testj.repository;
import com.example.testj.domain.Goods;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface GoodsRepository extends CrudRepository<Goods, Long> {
public interface GoodsRepository extends PagingAndSortingRepository<Goods, Long>, CrudRepository<Goods, Long> {
}
\ No newline at end of file
package com.example.testj.service;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.PagedModel;
import org.springframework.stereotype.Service;
import com.example.testj.domain.Goods;
import com.example.testj.exception.ResourceNotFoundException;
import com.example.testj.repository.GoodsRepository;
import com.example.testj.service.dto.GoodsDto;
import com.example.testj.utils.MappingUtils;
@Service
public class GoodsService {
private final GoodsRepository goodsRepository;
private final MappingUtils mappingUtils;
public GoodsService(GoodsRepository goodsRepository, MappingUtils mappingUtils) {
this.goodsRepository = goodsRepository;
this.mappingUtils = mappingUtils;
}
public PagedModel<GoodsDto> getAllGoods(Integer page, Integer size, String sortBy, String sortDir) {
return new PagedModel<>(goodsRepository
.findAll(PageRequest.of(page, size, Sort.by(Sort.Direction.fromString(sortDir), sortBy)))
.map(mappingUtils::toGoodsDto));
}
public GoodsDto getGoods(Long id) {
return goodsRepository.findById(id).map(mappingUtils::toGoodsDto).orElse(null);
}
public GoodsDto createOrUpdate(Goods goods) {
return mappingUtils.toGoodsDto(this.goodsRepository.save(goods));
}
public void deleteGoods(Long id) {
this.goodsRepository.findById(id).orElseThrow(ResourceNotFoundException::new);
this.goodsRepository.deleteById(id);
}
}
package com.example.testj.service;
import org.springframework.stereotype.Service;
import com.example.testj.domain.OrderLine;
import com.example.testj.exception.ResourceNotFoundException;
import com.example.testj.repository.OrderLineRepository;
import com.example.testj.service.dto.GoodsDto;
import com.example.testj.service.dto.OrderDto;
import com.example.testj.service.dto.OrderLineDto;
import com.example.testj.service.dto.OrderLineRequestDto;
import com.example.testj.utils.MappingUtils;
@Service
public class OrderLineService {
private final MappingUtils mappingUtils;
private final OrderLineRepository orderLineRepository;
private final OrderService orderService;
private final GoodsService goodsService;
public OrderLineService(OrderLineRepository orderLineRepository, MappingUtils mappingUtils,
OrderService orderService, GoodsService goodsService) {
this.orderLineRepository = orderLineRepository;
this.mappingUtils = mappingUtils;
this.orderService = orderService;
this.goodsService = goodsService;
}
public OrderLineDto createOrderLine(OrderLineRequestDto orderLineRequest) {
OrderDto order = orderService.getOrder(orderLineRequest.getOrderId());
GoodsDto goods = goodsService.getGoods(orderLineRequest.getGoodsId());
OrderLine orderLine = new OrderLine();
orderLine.setOrder(mappingUtils.toOrder(order));
orderLine.setGoods(mappingUtils.toGoods(goods));
orderLine.setCount(orderLineRequest.getCount());
return mappingUtils.toOrderLineDto(orderLineRepository.save(orderLine));
}
public OrderLineDto updateOrderLine(OrderLine orderLine, Long id) {
OrderLine updatedOrderLine = orderLineRepository.findById(id).orElseThrow(ResourceNotFoundException::new);
updatedOrderLine.setCount(orderLine.getCount());
return mappingUtils.toOrderLineDto(orderLineRepository.save(updatedOrderLine));
}
public OrderLineDto getOrderLine(Long id) throws ResourceNotFoundException {
return mappingUtils
.toOrderLineDto(orderLineRepository.findById(id).orElseThrow(ResourceNotFoundException::new));
}
public void deleteOrderLine(Long id) {
orderLineRepository.findById(id).orElseThrow(ResourceNotFoundException::new);
orderLineRepository.deleteById(id);
}
}
package com.example.testj.service;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.PagedModel;
import org.springframework.stereotype.Service;
import com.example.testj.domain.Order;
import com.example.testj.dto.OrderDTO;
import com.example.testj.exception.ResourceNotFoundException;
import com.example.testj.repository.OrderRepository;
import com.example.testj.service.dto.OrderDto;
import com.example.testj.utils.MappingUtils;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.PagedModel;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
private final OrderRepository orderRepository;
private final MappingUtils mappingUtils;
public OrderService(OrderRepository orderRepository) {
public OrderService(OrderRepository orderRepository, MappingUtils mappingUtils) {
this.orderRepository = orderRepository;
this.mappingUtils = mappingUtils;
}
public PagedModel<Order> getOrders(Integer page, Integer size, String sortBy, String sortDir) {
return new PagedModel<Order>(orderRepository.findAll(PageRequest.of(page, size, Sort.by(Sort.Direction.fromString(sortDir), sortBy))));
public PagedModel<OrderDto> getOrders(Integer page, Integer size, String sortBy, String sortDir) {
return new PagedModel<>(orderRepository
.findAll(PageRequest.of(page, size, Sort.by(Sort.Direction.fromString(sortDir), sortBy)))
.map(mappingUtils::toOrderDto));
}
public Order getOrder(Long id) throws ResourceNotFoundException {
return orderRepository.findById(id).orElseThrow(ResourceNotFoundException::new);
public OrderDto getOrder(Long id) throws ResourceNotFoundException {
return mappingUtils.toOrderDto(orderRepository.findById(id).orElseThrow(ResourceNotFoundException::new));
}
public Order saveOrUpdateOrder(Order order) {
return orderRepository.save(order);
public void deleteOrder(Long id) throws ResourceNotFoundException {
orderRepository.findById(id).orElseThrow(ResourceNotFoundException::new);
orderRepository.deleteById(id);
}
public Order patchOrder(Long id, JsonNode patch) throws JsonMappingException {
Order orderData = orderRepository.findById(id).orElseThrow(ResourceNotFoundException::new);
Order patchedOrder = new ObjectMapper().updateValue(orderData, patch);
return orderRepository.save(patchedOrder);
public OrderDto createOrUpdateOrder(Order order) {
return mappingUtils.toOrderDto(orderRepository.save(order));
}
public void deleteOrder(Long id) throws ResourceNotFoundException {
orderRepository.findById(id).orElseThrow(ResourceNotFoundException::new);
orderRepository.deleteById(id);
public OrderDto patchOrder(Long id, JsonNode patch) throws JsonMappingException {
Order orderData = orderRepository.findById(id).orElseThrow(ResourceNotFoundException::new);
Order patchedOrder = new ObjectMapper().updateValue(orderData, patch);
return mappingUtils.toOrderDto(orderRepository.save(patchedOrder));
}
}
package com.example.testj.dto;
package com.example.testj.service.dto;
import java.io.Serializable;
import com.example.testj.domain.Goods;
/**
* DTO for {@link com.example.testj.domain.Goods}
*/
public class GoodsDto implements Serializable {
private final Long id;
private final String name;
private final Number price;
private Long id;
private String name;
private double price;
public GoodsDto(Long id, String name, Number price) {
this.id = id;
this.name = name;
this.price = price;
public GoodsDto() {
}
public GoodsDto(Goods goods) {
this.id = goods.getId();
this.name = goods.getName();
this.price = goods.getPrice();
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public Number getPrice() {
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
@Override
public String toString() {
return getClass().getSimpleName() + "(" +
"id = " + id + ", " +
"name = " + name + ", " +
"price = " + price + ")";
public void setPrice(double price) {
this.price = price;
}
}
\ No newline at end of file
package com.example.testj.dto;
package com.example.testj.service.dto;
import java.io.Serializable;
import java.util.List;
import com.example.testj.domain.Order;
import com.example.testj.domain.OrderLine;
import java.io.Serializable;
public class OrderDto implements Serializable {
private Long id;
private String client;
private String date;
private String address;
public class OrderDTO implements Serializable {
public OrderDTO(Order order) {
private List<OrderLine> orderLines;
public OrderDto() {
}
public OrderDto(Order order) {
this.id = order.getId();
this.client = order.getClient();
this.address = order.getAddress();
this.date = order.getDate();
this.orderLines = order.getOrderLines();
}
private Long id;
private String client;
private String date;
private String address;
public String getDate() {
return date;
}
......@@ -45,7 +53,11 @@ public class OrderDTO implements Serializable {
return id;
}
public void setId(Long id) {
this.id = id;
public List<OrderLine> getOrderLines() {
return orderLines;
}
public void setOrderLines(List<OrderLine> orderLines) {
this.orderLines = orderLines;
}
}
package com.example.testj.service.dto;
import java.io.Serializable;
import com.example.testj.domain.Goods;
import com.example.testj.domain.Order;
import com.example.testj.domain.OrderLine;
public class OrderLineDto implements Serializable {
private Long id;
private Goods goods;
private Order order;
private int count;
public OrderLineDto() {
}
public OrderLineDto(OrderLine orderLine) {
this.id = orderLine.getId();
this.count = orderLine.getCount();
this.goods = orderLine.getGoods();
this.order = orderLine.getOrder();
}
public Goods getGoods() {
return this.goods;
}
public void setGoods(Goods goods) {
this.goods = goods;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
public Long getId() {
return id;
}
public Order getOrder() {
return order;
}
}
\ No newline at end of file
package com.example.testj.service.dto;
public class OrderLineRequestDto {
private Long orderId;
private Long goodsId;
private int count;
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
public Long getOrderId() {
return orderId;
}
public void setOrderId(Long orderId) {
this.orderId = orderId;
}
public Long getGoodsId() {
return goodsId;
}
public void setGoodsId(Long goodsId) {
this.goodsId = goodsId;
}
}
package com.example.testj.utils;
import org.springframework.stereotype.Service;
import com.example.testj.domain.Goods;
import com.example.testj.domain.Order;
import com.example.testj.domain.OrderLine;
import com.example.testj.service.dto.GoodsDto;
import com.example.testj.service.dto.OrderDto;
import com.example.testj.service.dto.OrderLineDto;
@Service
public class MappingUtils {
public OrderDto toOrderDto(Order order) {
return new OrderDto(order);
}
public Order toOrder(OrderDto orderDto) {
Order order = new Order();
order.setId(orderDto.getId());
order.setClient(orderDto.getClient());
order.setDate(orderDto.getDate());
order.setAddress(orderDto.getAddress());
order.setOrderLines(orderDto.getOrderLines());
return order;
}
public OrderLineDto toOrderLineDto(OrderLine orderLine) {
return new OrderLineDto(orderLine);
}
public OrderLine toOrderLine(OrderLineDto orderLineDto) {
OrderLine orderLine = new OrderLine();
orderLine.setGoods(orderLineDto.getGoods());
orderLine.setOrder(orderLineDto.getOrder());
orderLine.setCount(orderLineDto.getCount());
return orderLine;
}
public GoodsDto toGoodsDto(Goods goods) {
return new GoodsDto(goods);
}
public Goods toGoods(GoodsDto goodsDto) {
Goods goods = new Goods();
goods.setId(goodsDto.getId());
goods.setName(goodsDto.getName());
goods.setPrice(goodsDto.getPrice());
return goods;
}
}
package com.example.testj.web.rest;
import com.example.testj.domain.Goods;
import com.example.testj.repository.GoodsRepository;
import org.springframework.data.web.PagedModel;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import com.example.testj.exception.ResourceNotFoundException;
import com.example.testj.service.GoodsService;
import com.example.testj.service.dto.GoodsDto;
import com.example.testj.utils.MappingUtils;
@RestController
@RequestMapping("/api/goods")
@CrossOrigin(origins = "http://localhost:4200")
public class GoodsController {
private final GoodsRepository goodsRepository;
private final GoodsService goodsService;
private final MappingUtils mappingUtils;
public GoodsController(GoodsRepository goodsRepository) {
this.goodsRepository = goodsRepository;
public GoodsController(GoodsService goodsService, MappingUtils mappingUtils) {
this.goodsService = goodsService;
this.mappingUtils = mappingUtils;
}
@GetMapping("/goods")
public List<Goods> goods() {
return (List<Goods>) goodsRepository.findAll();
@GetMapping
public PagedModel<GoodsDto> goods(
@RequestHeader(name = "X-Page-Current", defaultValue = "0") Integer page,
@RequestHeader(name = "X-Page-Size", defaultValue = "10") Integer size,
@RequestHeader(name = "X-Sort-By", defaultValue = "id") String sortBy,
@RequestHeader(name = "X-Sort-Direction", defaultValue = "ASC") String sortDir) {
return goodsService.getAllGoods(page, size, sortBy, sortDir);
}
@GetMapping("/{id}")
public GoodsDto goods(@PathVariable Long id) {
return goodsService.getGoods(id);
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public GoodsDto createGoods(@RequestBody GoodsDto goods) {
return this.goodsService.createOrUpdate(mappingUtils.toGoods(goods));
}
@PutMapping("/{id}")
public GoodsDto updateGoods(@PathVariable Long id, @RequestBody GoodsDto goods) {
GoodsDto updatedGoods = goodsService.getGoods(id);
updatedGoods.setName(goods.getName());
updatedGoods.setPrice(goods.getPrice());
return this.goodsService.createOrUpdate(mappingUtils.toGoods(updatedGoods));
}
@DeleteMapping("/{id}")
public ResponseEntity<HttpStatus> deleteGoods(@PathVariable Long id) {
try {
goodsService.deleteGoods(id);
return new ResponseEntity<>(HttpStatus.OK);
} catch (ResourceNotFoundException e) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}
}
package com.example.testj.web.rest;
import org.springframework.data.web.PagedModel;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import com.example.testj.domain.Order;
import com.example.testj.domain.RestErrorInfo;
import com.example.testj.dto.OrderDTO;
import com.example.testj.exception.ResourceNotFoundException;
import com.example.testj.repository.OrderRepository;
import com.example.testj.service.OrderService;
import com.example.testj.service.dto.OrderDto;
import com.example.testj.utils.MappingUtils;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode;
import jakarta.validation.Valid;
import org.springframework.data.web.PagedModel;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.Optional;
import jakarta.validation.Valid;
@RestController
@RequestMapping("/api")
@RequestMapping("/api/orders")
@CrossOrigin(origins = "http://localhost:4200")
public class OrderController {
private final OrderRepository orderRepository;
private final OrderService orderService;
private final MappingUtils mappingUtils;
public OrderController(OrderRepository orderRepository, OrderService orderService) {
this.orderRepository = orderRepository;
public OrderController(OrderService orderService, MappingUtils mappingUtils) {
this.orderService = orderService;
this.mappingUtils = mappingUtils;
}
@GetMapping("/orders")
public PagedModel<Order> getOrders(@RequestHeader(name = "X-Current-Page", defaultValue = "0") Integer page,
@GetMapping
public PagedModel<OrderDto> getOrders(
@RequestHeader(name = "X-Page-Current", defaultValue = "0") Integer page,
@RequestHeader(name = "X-Page-Size", defaultValue = "10") Integer size,
@RequestHeader(name = "X-Sort-By", defaultValue = "id") String sortBy,
@RequestHeader(name = "X-Sort-Direction", defaultValue = "ASC") String sortDir) {
return orderService.getOrders(page, size, sortBy, sortDir);
}
@GetMapping("/orders/{id}")
public Order getOrder(@PathVariable Long id) {
return this.orderService.getOrder(id);
@GetMapping("/{id}")
public OrderDto getOrder(@PathVariable Long id) {
return orderService.getOrder(id);
}
@PostMapping("/orders")
public Order createOrder(@Valid @RequestBody Order order) {
return orderService.saveOrUpdateOrder(order);
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public OrderDto createOrder(@Valid @RequestBody Order order) {
return orderService.createOrUpdateOrder(order);
}
@PutMapping("/orders/{id}")
public Order updateOrder(@PathVariable Long id, @RequestBody Order order) {
Order updatedOrder = orderService.getOrder(id);
@PutMapping("/{id}")
@ResponseStatus(HttpStatus.OK)
public OrderDto updateOrder(@PathVariable Long id, @RequestBody OrderDto order) {
OrderDto updatedOrder = orderService.getOrder(id);
updatedOrder.setClient(order.getClient());
updatedOrder.setAddress(order.getAddress());
updatedOrder.setDate(order.getDate());
return orderService.saveOrUpdateOrder(updatedOrder);
return orderService.createOrUpdateOrder(mappingUtils.toOrder(updatedOrder));
}
@PatchMapping("/orders/{id}")
public Order patchOrder(@PathVariable Long id, @RequestBody JsonNode patch) throws JsonMappingException {
@PatchMapping("/{id}")
public OrderDto patchOrder(@PathVariable Long id, @RequestBody JsonNode patch) throws JsonMappingException {
return orderService.patchOrder(id, patch);
}
@DeleteMapping("/orders/{id}")
@DeleteMapping("/{id}")
public ResponseEntity<HttpStatus> deleteOrder(@PathVariable Long id) throws ResourceNotFoundException {
try {
orderService.deleteOrder(id);
......@@ -68,6 +83,5 @@ public class OrderController {
} catch (ResourceNotFoundException e) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}
}
package com.example.testj.web.rest;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.example.testj.exception.ResourceNotFoundException;
import com.example.testj.service.OrderLineService;
import com.example.testj.service.dto.OrderLineDto;
import com.example.testj.service.dto.OrderLineRequestDto;
import com.example.testj.utils.MappingUtils;
import jakarta.validation.Valid;
@RestController
@RequestMapping("/api/order-line")
@CrossOrigin(origins = "http://localhost:4200")
public class OrderLineController {
private final OrderLineService orderLineService;
private final MappingUtils mappingUtils;
public OrderLineController(OrderLineService orderLineService, MappingUtils mappingUtils) {
this.orderLineService = orderLineService;
this.mappingUtils = mappingUtils;
}
@GetMapping("/{id}")
public OrderLineDto get(@PathVariable Long id) {
return orderLineService.getOrderLine(id);
}
@PostMapping
public OrderLineDto create(@Valid @RequestBody OrderLineRequestDto orderLineRequest) {
return orderLineService.createOrderLine(orderLineRequest);
}
@PutMapping("/{id}")
public OrderLineDto updateOrder(@PathVariable Long id, @Valid @RequestBody OrderLineDto orderLine) {
return orderLineService.updateOrderLine(mappingUtils.toOrderLine(orderLine), id);
}
@DeleteMapping("/{id}")
public ResponseEntity<HttpStatus> delete(@PathVariable Long id) throws ResourceNotFoundException {
try {
orderLineService.deleteOrderLine(id);
return new ResponseEntity<>(HttpStatus.OK);
} catch (ResourceNotFoundException e) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}
}
......@@ -2,7 +2,7 @@ spring.application.name=testj
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.jpa.hibernate.ddl-auto=update
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.defer-datasource-initialization=true
spring.h2.console.enabled=true
......
......@@ -4,21 +4,20 @@
-- date DATE,
-- address VARCHAR(50)
-- );
insert into
orders (id, client, date, address)
values
(
1,
'Griz Soppit',
'11/18/2023',
'1/8/2023',
'4 Fieldstone Pass'
),
(2, 'Lorain Kroin', '2/23/2024', '3 Boyd Hill'),
(
3,
'Benedikta Earry',
'12/22/2023',
'12/2/2023',
'9 Ridge Oak Road'
),
(
......@@ -30,31 +29,31 @@ values
(
5,
'Tedmund Yukhin',
'1/29/2024',
'1/9/2024',
'1 Northport Trail'
),
(
6,
'Niki Thibodeaux',
'1/25/2024',
'1/5/2024',
'734 Vera Point'
),
(
7,
'Sheri Habershaw',
'5/25/2024',
'5/5/2024',
'7689 Melrose Center'
),
(
8,
'Coral Dellatorre',
'7/28/2023',
'7/8/2023',
'95 Onsgard Junction'
),
(
9,
'Leisha Barnicott',
'3/15/2024',
'3/5/2024',
'945 Hanover Park'
),
(
......@@ -66,13 +65,13 @@ values
(
11,
'Toni Lenchenko',
'2/16/2024',
'2/6/2024',
'83 Jenifer Lane'
),
(
12,
'Ynez Moncreiff',
'5/20/2024',
'5/2/2024',
'4182 Sutteridge Parkway'
),
(
......@@ -84,13 +83,13 @@ values
(
14,
'Franky Ply',
'3/16/2024',
'3/6/2024',
'037 Heffernan Drive'
),
(
15,
'Shel Aykroyd',
'6/28/2023',
'6/8/2023',
'53 Del Mar Terrace'
),
(
......@@ -102,13 +101,13 @@ values
(
17,
'Garald Blunsden',
'9/30/2023',
'9/3/2023',
'7 Acker Drive'
),
(
18,
'Carree Yakunkin',
'4/19/2024',
'4/9/2024',
'40 Transport Court'
),
(
......@@ -132,7 +131,7 @@ values
(
22,
'Hebert Canfer',
'9/24/2023',
'9/4/2023',
'829 Rusk Trail'
),
(
......@@ -150,32 +149,32 @@ values
(
25,
'Cissiee De Angelis',
'1/28/2024',
'1/8/2024',
'660 Talisman Place'
),
(
26,
'Rurik Killshaw',
'3/14/2024',
'3/4/2024',
'3 Dapin Park'
),
(
27,
'Carmelle Philler',
'4/24/2024',
'4/4/2024',
'355 Morning Road'
),
(
28,
'Sara-ann Pook',
'3/21/2024',
'3/1/2024',
'7967 Welch Plaza'
),
(29, 'Piper Libero', '1/3/2024', '3 Prentice Way'),
(
30,
'Shermy Simonnin',
'11/29/2023',
'11/9/2023',
'44910 Debra Court'
),
(
......@@ -193,19 +192,19 @@ values
(
33,
'Armstrong Jessard',
'5/13/2024',
'5/3/2024',
'1 Center Park'
),
(
34,
'Binnie Pearde',
'5/30/2024',
'5/3/2024',
'72808 Johnson Place'
),
(
35,
'Rena Gontier',
'6/15/2023',
'6/5/2023',
'277 Anzinger Terrace'
),
(
......@@ -224,7 +223,7 @@ values
(
39,
'Westbrook Thomsson',
'5/13/2024',
'5/3/2024',
'097 Mcbride Pass'
),
(
......@@ -236,7 +235,7 @@ values
(
41,
'Currie Bartosek',
'1/18/2024',
'1/8/2024',
'34503 Rockefeller Trail'
),
(
......@@ -248,13 +247,13 @@ values
(
43,
'Blaine Giff',
'12/16/2023',
'12/6/2023',
'0718 Amoth Way'
),
(
44,
'Lemmy Stidworthy',
'12/21/2023',
'12/2/2023',
'6258 Cottonwood Crossing'
),
(
......@@ -266,7 +265,7 @@ values
(
46,
'Massimiliano Bogays',
'10/24/2023',
'10/4/2023',
'578 Northridge Parkway'
),
(
......@@ -278,19 +277,19 @@ values
(
48,
'Ichabod Goulbourne',
'9/18/2023',
'9/8/2023',
'325 Comanche Park'
),
(
49,
'Rab Marishenko',
'9/26/2023',
'9/6/2023',
'310 Stuart Terrace'
),
(
50,
'Elinore Healings',
'10/22/2023',
'10/2/2023',
'4 Clyde Gallagher Terrace'
),
(
......@@ -302,7 +301,7 @@ values
(
52,
'Amandy Ridolfo',
'6/30/2023',
'6/3/2023',
'8 Muir Court'
),
(
......@@ -314,13 +313,13 @@ values
(
54,
'Adolf Ricoald',
'2/25/2024',
'2/5/2024',
'90865 Lawn Park'
),
(
55,
'Ilene Hardey',
'3/20/2024',
'3/2/2024',
'72 Redwing Hill'
),
(
......@@ -332,19 +331,19 @@ values
(
57,
'Rakel Gunter',
'12/18/2023',
'12/8/2023',
'33 Melvin Street'
),
(
58,
'Marielle Semour',
'3/21/2024',
'3/1/2024',
'804 Ohio Place'
),
(
59,
'Ciro Paulillo',
'9/16/2023',
'9/6/2023',
'637 Spohn Plaza'
),
(
......@@ -356,13 +355,13 @@ values
(
61,
'Brooks Licciardi',
'7/28/2023',
'7/8/2023',
'01404 Waxwing Lane'
),
(
62,
'Hunt Trusler',
'6/20/2023',
'6/2/2023',
'172 Hudson Crossing'
),
(
......@@ -386,25 +385,25 @@ values
(
66,
'Farr Bruhnicke',
'3/26/2024',
'3/6/2024',
'05 Arrowood Alley'
),
(
67,
'Ruth Blankau',
'2/14/2024',
'2/4/2024',
'8683 Oakridge Pass'
),
(
68,
'Pearline Tubbles',
'11/17/2023',
'11/7/2023',
'01122 Vidon Drive'
),
(
69,
'Des Penddreth',
'5/30/2024',
'5/3/2024',
'87034 Melody Center'
),
(
......@@ -416,43 +415,43 @@ values
(
71,
'Corly Mc Meekin',
'10/31/2023',
'10/3/2023',
'0 Forest Drive'
),
(
72,
'Arvie Daen',
'11/30/2023',
'11/3/2023',
'063 Thierer Pass'
),
(
73,
'Karla Waryk',
'3/24/2024',
'3/4/2024',
'072 Lindbergh Circle'
),
(
74,
'Axel Syson',
'9/16/2023',
'9/6/2023',
'9880 Butternut Trail'
),
(
75,
'Elliot Beswick',
'11/13/2023',
'11/3/2023',
'81686 Lakewood Way'
),
(
76,
'Stepha Jan',
'4/14/2024',
'4/4/2024',
'3 Weeping Birch Place'
),
(
77,
'Ignace Rockey',
'11/25/2023',
'11/5/2023',
'82131 Rieder Parkway'
),
(
......@@ -494,13 +493,13 @@ values
(
84,
'Zolly Screase',
'7/24/2023',
'7/4/2023',
'09057 Bayside Street'
),
(
85,
'Walden Sholl',
'10/30/2023',
'10/10/2023',
'3082 Tomscot Terrace'
),
(
......@@ -512,7 +511,7 @@ values
(
87,
'Flemming Mecco',
'6/22/2023',
'6/2/2023',
'99211 Killdeer Pass'
),
(
......@@ -524,26 +523,26 @@ values
(
89,
'Marjory Traske',
'7/25/2023',
'7/5/2023',
'37049 Manufacturers Center'
),
(
90,
'Ida Newlands',
'1/29/2024',
'1/9/2024',
'00 Thierer Alley'
),
(
91,
'Letti Merrisson',
'7/27/2023',
'7/7/2023',
'36 Green Plaza'
),
(92, 'Fran Bullene', '7/2/2023', '5 Bowman Plaza'),
(
93,
'Natala Harteley',
'1/22/2024',
'1/2/2024',
'20 High Crossing Junction'
),
(
......@@ -555,13 +554,13 @@ values
(
95,
'Stinky Le Grove',
'12/13/2023',
'12/3/2023',
'415 Haas Plaza'
),
(
96,
'Nalani Ravenshear',
'3/14/2024',
'3/4/2024',
'56132 Vera Street'
),
(
......@@ -585,9 +584,10 @@ values
(
100,
'Bing Everwin',
'6/22/2023',
'6/2/2023',
'07218 Mitchell Park'
);
alter sequence orders_seq restart with 150;
-- create table goods (
......@@ -595,7 +595,6 @@ alter sequence orders_seq restart with 150;
-- name VARCHAR(50),
-- price DECIMAL(5, 2)
-- );
insert into
goods (id, name, price)
values
......@@ -779,6 +778,7 @@ values
),
(99, 'Katalin Varga', 83.92),
(100, 'City of Angels', 76.91);
alter sequence goods_seq restart with 150;
-- create table order_line (
......@@ -787,108 +787,108 @@ alter sequence goods_seq restart with 150;
-- goods_id INT,
-- count INT
-- );
insert into
order_line (id, order_id, goods_id, count)
values
(1, 1, 18, 7),
(2, 1, 28, 8),
(3, 1, 41, 9),
(4, 1, 55, 6),
(5, 2, 59, 32),
(6, 2, 52, 66),
(7, 2, 3, 10),
(1, 1, 1, 7),
(2, 1, 2, 8),
(3, 1, 3, 9),
(4, 1, 4, 6),
(5, 1, 5, 3),
(6, 2, 52, 6),
(7, 2, 3, 1),
(8, 2, 35, 6),
(9, 2, 35, 80),
(10, 2, 5, 76),
(11, 3, 32, 20),
(12, 3, 9, 96),
(13, 3, 96, 74),
(9, 2, 35, 8),
(10, 2, 5, 7),
(11, 3, 32, 2),
(12, 3, 9, 9),
(13, 3, 96, 7),
(14, 3, 39, 14),
(15, 4, 16, 48),
(16, 4, 13, 54),
(17, 4, 42, 93),
(18, 5, 53, 10),
(19, 6, 19, 89),
(20, 5, 80, 95),
(21, 8, 20, 92),
(22, 4, 13, 100),
(23, 9, 16, 88),
(24, 3, 65, 27),
(25, 9, 38, 20),
(26, 2, 23, 85),
(27, 4, 45, 79),
(28, 4, 90, 43),
(29, 2, 30, 62),
(30, 3, 48, 26),
(31, 9, 8, 56),
(15, 4, 16, 8),
(16, 4, 13, 4),
(17, 4, 42, 9),
(18, 5, 53, 1),
(19, 6, 19, 8),
(20, 5, 80, 5),
(21, 8, 20, 2),
(22, 4, 13, 10),
(23, 9, 16, 8),
(24, 3, 65, 7),
(25, 9, 38, 2),
(26, 2, 23, 8),
(27, 4, 45, 9),
(28, 4, 90, 3),
(29, 2, 30, 2),
(30, 3, 48, 6),
(31, 9, 8, 6),
(32, 6, 62, 3),
(33, 3, 52, 46),
(34, 2, 52, 22),
(35, 7, 67, 23),
(36, 9, 6, 22),
(33, 3, 52, 6),
(34, 2, 52, 2),
(35, 7, 67, 3),
(36, 9, 6, 12),
(37, 5, 76, 9),
(38, 9, 28, 52),
(39, 3, 56, 57),
(40, 4, 70, 64),
(41, 5, 86, 25),
(42, 9, 46, 31),
(43, 9, 81, 43),
(44, 3, 6, 46),
(45, 1, 56, 18),
(46, 7, 89, 37),
(47, 8, 66, 87),
(48, 8, 39, 33),
(49, 6, 54, 43),
(50, 7, 32, 81),
(51, 1, 94, 33),
(52, 6, 50, 46),
(53, 5, 71, 61),
(54, 2, 73, 40),
(55, 7, 22, 35),
(56, 1, 82, 72),
(57, 4, 54, 61),
(58, 1, 4, 28),
(59, 2, 88, 94),
(60, 6, 97, 54),
(61, 4, 20, 27),
(62, 7, 61, 43),
(63, 5, 26, 96),
(64, 8, 80, 89),
(65, 9, 26, 39),
(66, 6, 60, 55),
(67, 5, 20, 93),
(68, 2, 16, 10),
(69, 8, 15, 13),
(70, 8, 81, 49),
(71, 3, 25, 73),
(72, 6, 39, 75),
(73, 1, 79, 11),
(74, 4, 31, 40),
(75, 3, 3, 38),
(76, 6, 95, 71),
(77, 8, 12, 69),
(78, 4, 28, 86),
(79, 5, 14, 54),
(80, 6, 69, 73),
(81, 9, 50, 43),
(82, 3, 15, 81),
(83, 4, 10, 21),
(84, 3, 29, 46),
(85, 5, 91, 96),
(86, 8, 74, 35),
(87, 7, 77, 42),
(88, 2, 73, 36),
(89, 4, 6, 85),
(90, 8, 67, 29),
(91, 7, 86, 58),
(92, 3, 68, 51),
(93, 8, 96, 31),
(38, 9, 28, 5),
(39, 3, 56, 7),
(40, 4, 70, 6),
(41, 5, 86, 2),
(42, 9, 46, 3),
(43, 9, 81, 4),
(44, 3, 6, 6),
(45, 1, 56, 8),
(46, 7, 89, 7),
(47, 8, 66, 7),
(48, 8, 39, 3),
(49, 6, 54, 3),
(50, 7, 32, 1),
(51, 1, 94, 3),
(52, 6, 50, 6),
(53, 5, 71, 1),
(54, 2, 73, 2),
(55, 7, 22, 5),
(56, 1, 82, 2),
(57, 4, 54, 1),
(58, 1, 4, 8),
(59, 2, 88, 4),
(60, 6, 97, 4),
(61, 4, 20, 7),
(62, 7, 61, 3),
(63, 5, 26, 6),
(64, 8, 80, 9),
(65, 9, 26, 3),
(66, 6, 60, 5),
(67, 5, 20, 9),
(68, 2, 16, 1),
(69, 8, 15, 3),
(70, 8, 81, 9),
(71, 3, 25, 3),
(72, 6, 39, 5),
(73, 1, 79, 1),
(74, 4, 31, 4),
(75, 3, 3, 3),
(76, 6, 95, 7),
(77, 8, 12, 9),
(78, 4, 28, 6),
(79, 5, 14, 4),
(80, 6, 69, 3),
(81, 9, 50, 3),
(82, 3, 15, 1),
(83, 4, 10, 1),
(84, 3, 29, 6),
(85, 5, 91, 6),
(86, 8, 74, 5),
(87, 7, 77, 2),
(88, 2, 73, 6),
(89, 4, 6, 8),
(90, 8, 67, 9),
(91, 7, 86, 8),
(92, 3, 68, 1),
(93, 8, 96, 3),
(94, 4, 42, 8),
(95, 2, 72, 84),
(96, 8, 1, 61),
(97, 2, 37, 65),
(98, 1, 40, 69),
(99, 2, 9, 13),
(100, 7, 57, 72);
(95, 2, 72, 4),
(96, 8, 1, 1),
(97, 2, 37, 5),
(98, 1, 40, 6),
(99, 2, 9, 3),
(100, 7, 57, 2);
alter sequence order_line_seq restart with 150;
\ No newline at end of file
{
"overrides": [
{
"files": "*.html",
"options": {
"parser": "angular"
}
}
]
}
# Testj
# Ng17
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 18.0.2.
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 17.3.8.
## Development server
......@@ -24,4 +24,4 @@ Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To u
## Further help
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page.
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.
......@@ -3,18 +3,12 @@
"version": 1,
"newProjectRoot": "projects",
"projects": {
"testj": {
"ng17": {
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
"style": "scss",
"standalone": false
},
"@schematics/angular:directive": {
"standalone": false
},
"@schematics/angular:pipe": {
"standalone": false
}
},
"root": "",
......@@ -24,21 +18,15 @@
"build": {
"builder": "@angular-devkit/build-angular:application",
"options": {
"outputPath": "dist/testj",
"outputPath": "dist/ng17",
"index": "src/index.html",
"browser": "src/main.ts",
"polyfills": [
"zone.js"
],
"polyfills": ["zone.js"],
"tsConfig": "tsconfig.app.json",
"inlineStyleLanguage": "scss",
"assets": [
{
"glob": "**/*",
"input": "public"
}
],
"assets": ["src/favicon.ico", "src/assets"],
"styles": [
"node_modules/bootstrap-icons/font/bootstrap-icons.scss",
"src/styles.scss"
],
"scripts": []
......@@ -48,13 +36,13 @@
"budgets": [
{
"type": "initial",
"maximumWarning": "500kB",
"maximumError": "1MB"
"maximumWarning": "500kb",
"maximumError": "1mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "2kB",
"maximumError": "4kB"
"maximumWarning": "2kb",
"maximumError": "4kb"
}
],
"outputHashing": "all"
......@@ -77,35 +65,28 @@
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"buildTarget": "testj:build:production"
"buildTarget": "ng17:build:production"
},
"development": {
"buildTarget": "testj:build:development"
"buildTarget": "ng17:build:development"
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n"
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"buildTarget": "ng17:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"polyfills": [
"zone.js",
"zone.js/testing"
],
"polyfills": ["zone.js", "zone.js/testing"],
"tsConfig": "tsconfig.spec.json",
"inlineStyleLanguage": "scss",
"assets": [
{
"glob": "**/*",
"input": "public"
}
],
"styles": [
"src/styles.scss"
],
"assets": ["src/favicon.ico", "src/assets"],
"styles": ["src/styles.scss"],
"scripts": []
}
}
......
This source diff could not be displayed because it is too large. You can view the blob instead.
{
"name": "testj",
"name": "ng17",
"version": "0.0.0",
"scripts": {
"ng": "ng",
......@@ -10,22 +10,27 @@
},
"private": true,
"dependencies": {
"@angular/animations": "^18.0.0",
"@angular/common": "^18.0.0",
"@angular/compiler": "^18.0.0",
"@angular/core": "^18.0.0",
"@angular/forms": "^18.0.0",
"@angular/platform-browser": "^18.0.0",
"@angular/platform-browser-dynamic": "^18.0.0",
"@angular/router": "^18.0.0",
"@angular/animations": "^17.3.0",
"@angular/common": "^17.3.0",
"@angular/compiler": "^17.3.0",
"@angular/core": "^17.3.0",
"@angular/forms": "^17.3.0",
"@angular/platform-browser": "^17.3.0",
"@angular/platform-browser-dynamic": "^17.3.0",
"@angular/router": "^17.3.0",
"@ng-bootstrap/ng-bootstrap": "^16.0.0",
"@popperjs/core": "^2.11.8",
"bootstrap": "^5.3.2",
"bootstrap-icons": "^1.11.3",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"zone.js": "~0.14.3"
},
"devDependencies": {
"@angular-devkit/build-angular": "^18.0.2",
"@angular/cli": "^18.0.2",
"@angular/compiler-cli": "^18.0.0",
"@angular-devkit/build-angular": "^17.3.8",
"@angular/cli": "^17.3.8",
"@angular/compiler-cli": "^17.3.0",
"@angular/localize": "^17.3.0",
"@types/jasmine": "~5.1.0",
"jasmine-core": "~5.1.0",
"karma": "~6.4.0",
......@@ -33,6 +38,7 @@
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.1.0",
"prettier": "^3.3.2",
"typescript": "~5.4.2"
}
}
.main {
}
.content {
width: 100%;
padding: 20px;
}
......@@ -3,17 +3,25 @@ import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import {provideHttpClient} from "@angular/common/http";
import { provideHttpClient } from '@angular/common/http';
import {
NgbDateAdapter,
NgbDateParserFormatter,
NgbModule,
} from '@ng-bootstrap/ng-bootstrap';
import {
CustomDateAdapter,
CustomDateParserFormatter,
} from './core/adapter/custom-date.adapter';
@NgModule({
declarations: [
AppComponent
declarations: [AppComponent],
imports: [BrowserModule, AppRoutingModule, NgbModule],
providers: [
provideHttpClient(),
{ provide: NgbDateAdapter, useClass: CustomDateAdapter },
{ provide: NgbDateParserFormatter, useClass: CustomDateParserFormatter },
],
imports: [
BrowserModule,
AppRoutingModule
],
providers: [provideHttpClient()],
bootstrap: [AppComponent]
bootstrap: [AppComponent],
})
export class AppModule { }
export class AppModule {}
import { Injectable } from '@angular/core';
import {
NgbDateParserFormatter,
NgbDateStruct,
} from '@ng-bootstrap/ng-bootstrap';
/**
* This Service handles how the date is represented in scripts i.e. ngModel.
*/
@Injectable()
export class CustomDateAdapter {
readonly DELIMITER = '/';
fromModel(value: string | null): NgbDateStruct | null {
if (value) {
const date = value.split(this.DELIMITER);
return {
day: parseInt(date[0], 10),
month: parseInt(date[1], 10),
year: parseInt(date[2], 10),
};
}
return null;
}
toModel(date: NgbDateStruct | null): string | null {
return date
? date.day + this.DELIMITER + date.month + this.DELIMITER + date.year
: null;
}
}
/**
* This Service handles how the date is rendered and parsed from keyboard i.e. in the bound input field.
*/
@Injectable()
export class CustomDateParserFormatter extends NgbDateParserFormatter {
readonly DELIMITER = '/';
parse(value: string): NgbDateStruct | null {
if (value) {
const date = value.split(this.DELIMITER);
return {
day: parseInt(date[0], 10),
month: parseInt(date[1], 10),
year: parseInt(date[2], 10),
};
}
return null;
}
format(date: NgbDateStruct | null): string {
return date
? date.day + this.DELIMITER + date.month + this.DELIMITER + date.year
: '';
}
}
export interface User {
export interface Goods {
id: number;
name: string;
price: number;
......
export interface OrderLine {
id: number;
order_id: number;
goods_id: number;
count: number;
}
import { Goods } from './goods.model';
export interface Order {
id: number;
address: string;
client: string;
date: string;
orderLines: OrderLine[];
}
export interface OrderLine {
id: number;
goods: Goods;
count: number;
}
export interface OrderLineReq {
id?: number;
orderId: number;
goodsId: number;
count: number;
}
export interface PagedResponseModel<T> {
content: T[];
page: PageModel;
}
export interface PageModel {
number: number;
size: number;
totalElements: number;
totalPages: number;
}
export interface ControlsValidations {
[key: string]: ValidationMessage[];
}
export interface ValidationMessage {
type: string;
message: string;
params?: object;
}
import { TestBed } from '@angular/core/testing';
import { GoodsService } from './goods.service';
describe('GoodsService', () => {
let service: GoodsService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(GoodsService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});
import { Injectable } from '@angular/core';
import { environment } from '../../../environments/environment';
import {
HttpClient,
HttpErrorResponse,
HttpHeaders,
} from '@angular/common/http';
import { catchError, EMPTY, Observable } from 'rxjs';
import { PagedResponseModel } from '../model/paged-response.model';
import { Goods } from '../model/goods.model';
@Injectable({
providedIn: 'root',
})
export class GoodsService {
private readonly apiUrl = `${environment.API_ROOT}${environment.API_PREFIX}${environment.GOODS_URL}`;
private page = 0;
private limit = 10;
constructor(private http: HttpClient) {}
get(
page = this.page,
limit = this.limit
): Observable<PagedResponseModel<Goods>> {
const url = `${this.apiUrl}`;
const headers = new HttpHeaders({
'Content-Type': 'application/json',
'X-Page-Current': String(page),
'X-Page-Size': String(limit),
});
return this.http.get<PagedResponseModel<Goods>>(url, { headers });
}
put(goods: Goods): Observable<Goods> {
const url = `${this.apiUrl}/${goods.id}`;
return this.http.put<Goods>(url, goods);
}
post(goods: Goods): Observable<Goods> {
const url = this.apiUrl;
return this.http.post<Goods>(url, goods);
}
delete(id: number) {
const url = `${this.apiUrl}/${id}`;
return this.http.delete(url).pipe(
catchError((err: HttpErrorResponse) => {
console.dir(err);
return EMPTY;
})
);
}
}
import { TestBed } from '@angular/core/testing';
import { OrderLineService } from './order-line.service';
describe('OrderLineService', () => {
let service: OrderLineService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(OrderLineService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { catchError, EMPTY } from 'rxjs';
import { environment } from '../../../environments/environment';
import { OrderLine, OrderLineReq } from '../model/order.model';
@Injectable({
providedIn: 'root',
})
export class OrderLineService {
private readonly apiUrl = `${environment.API_ROOT}${environment.API_PREFIX}${environment.ORDER_LINE_URL}`;
constructor(private http: HttpClient) {}
put(ol: OrderLineReq) {
const url = `${this.apiUrl}/${ol.id}`;
return this.http.put<OrderLine>(url, ol);
}
post(ol: OrderLineReq) {
const url = this.apiUrl;
return this.http.post<OrderLine>(url, ol);
}
delete(id: number) {
const url = `${this.apiUrl}/${id}`;
return this.http.delete(url).pipe(
catchError((err: HttpErrorResponse) => {
console.dir(err);
return EMPTY;
})
);
}
}
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable } from 'rxjs';
import {
HttpClient,
HttpErrorResponse,
HttpHeaders,
} from '@angular/common/http';
import { catchError, EMPTY, Observable } from 'rxjs';
import { environment } from '../../../environments/environment';
import { Order } from '../model/order.model';
import { PagedResponseModel } from '../model/paged-response.model';
@Injectable({
providedIn: 'root',
})
export class OrderService {
private readonly apiUrl = `${environment.API_ROOT}${environment.API_PREFIX}${environment.ORDER_URL}`;
private page = 1;
private limit = 10;
private page = 0;
private size = 10;
constructor(private http: HttpClient) {}
getOrders(): Observable<Order[]> {
getOrders(
page = this.page,
size = this.size
): Observable<PagedResponseModel<Order>> {
const url = `${this.apiUrl}`;
const headers = new HttpHeaders({
'Content-Type': 'application/json',
Page: String(this.page),
Limit: String(this.limit),
'X-Page-Current': String(page),
'X-Page-Size': String(size),
});
return this.http.get<Order[]>(url, { headers });
return this.http.get<PagedResponseModel<Order>>(url, { headers });
}
getOrder(id: number): Observable<Order> {
const url = `${this.apiUrl}/${id}`;
return this.http.get<Order>(url);
}
putOrder(order: Order) {
const url = `${this.apiUrl}/${order.id}`;
return this.http.put<Order>(url, order);
}
postOrder(order: Order) {
const url = this.apiUrl;
return this.http.post<Order>(url, order);
}
deleteOrder(id: number) {
const url = `${this.apiUrl}/${id}`;
return this.http.delete(url).pipe(
catchError((err: HttpErrorResponse) => {
console.dir(err);
return EMPTY;
})
);
}
}
<div class="modal-header">
<h4 class="modal-title" id="modal-basic-title">Product</h4>
<button
type="button"
class="btn-close"
aria-label="Close"
(click)="modal?.close()"
></button>
</div>
<div class="modal-body">
<app-goods-form
[product]="product"
(isUpdate)="goodsUpdate()"
></app-goods-form>
</div>
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { GoodsEditModalComponent } from './goods-edit-modal.component';
describe('GoodsEditModalComponent', () => {
let component: GoodsEditModalComponent;
let fixture: ComponentFixture<GoodsEditModalComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [GoodsEditModalComponent]
})
.compileComponents();
fixture = TestBed.createComponent(GoodsEditModalComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
import { takeUntil } from 'rxjs';
import { environment } from '../../../../../environments/environment';
import { AbstractBasicComponent } from '../../../../core/abstract/abstract-component';
import { Goods } from '../../../../core/model/goods.model';
const DEV = !environment.production;
@Component({
selector: 'app-goods-edit-modal',
templateUrl: './goods-edit-modal.component.html',
styleUrl: './goods-edit-modal.component.scss',
})
export class GoodsEditModalComponent extends AbstractBasicComponent {
@Input() product!: Goods;
@Output() isUpdate = new EventEmitter<boolean>();
modal: NgbModalRef | undefined;
constructor(modalService: NgbModal) {
super();
modalService.activeInstances
.pipe(takeUntil(this.destroy$))
.subscribe((mls) => {
this.modal = mls[mls.length - 1];
});
}
goodsUpdate() {
this.modal?.close();
this.isUpdate.emit(true);
}
}
<table class="table table-striped">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Name</th>
<th scope="col">Price</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
@for (product of goods; track product.id){
<tr>
<th scope="row">{{ product.id }}</th>
<td>{{ product.name }}</td>
<td>{{ product.price | currency }}</td>
<td>
<button
type="button"
class="btn btn-sm btn-outline-secondary me-2"
(click)="openEdit(product)"
>
Edit
</button>
<button
type="button"
class="btn btn-sm btn-outline-danger me-2"
(click)="deleteProduct(product.id)"
>
Delete
</button>
</td>
</tr>
}
</tbody>
</table>
@if (page) {
<ngb-pagination
[page]="page.number + 1"
(pageChange)="pageNumberChange($event - 1)"
[pageSize]="page.size"
[collectionSize]="page.totalElements"
></ngb-pagination>
}
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { GoodsListComponent } from './goods-list.component';
describe('GoodsListComponent', () => {
let component: GoodsListComponent;
let fixture: ComponentFixture<GoodsListComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [GoodsListComponent]
})
.compileComponents();
fixture = TestBed.createComponent(GoodsListComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { Goods } from '../../../../core/model/goods.model';
import { PageModel } from '../../../../core/model/paged-response.model';
@Component({
selector: 'app-goods-list',
templateUrl: './goods-list.component.html',
styleUrl: './goods-list.component.scss',
})
export class GoodsListComponent {
@Input() goods!: Goods[];
@Input() page!: PageModel;
@Output() pageNumber = new EventEmitter<number>();
@Output() openModal = new EventEmitter<Goods>();
@Output() delete = new EventEmitter<number>();
pageNumberChange(page: number) {
this.pageNumber.emit(page);
}
openEdit(product: Goods) {
this.openModal.emit(product);
}
deleteProduct(id: number) {
this.delete.emit(id);
}
}
<div class="modal-header">
<h4 class="modal-title" id="modal-basic-title">Order</h4>
<button
type="button"
class="btn-close"
aria-label="Close"
(click)="modal.close()"
></button>
</div>
<div class="modal-body">
<app-order-form
[order]="order"
(orderChange)="orderChanged($event)"
></app-order-form>
</div>
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { OrderEditModalComponent } from './order-edit-modal.component';
describe('OrderEditModalComponent', () => {
let component: OrderEditModalComponent;
let fixture: ComponentFixture<OrderEditModalComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [OrderEditModalComponent]
})
.compileComponents();
fixture = TestBed.createComponent(OrderEditModalComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
import { takeUntil } from 'rxjs';
import { AbstractBasicComponent } from '../../../../core/abstract/abstract-component';
import { Order } from '../../../../core/model/order.model';
@Component({
selector: 'app-order-edit-modal',
templateUrl: './order-edit-modal.component.html',
styleUrl: './order-edit-modal.component.scss',
})
export class OrderEditModalComponent extends AbstractBasicComponent {
@Input() order!: Order;
@Output() orderChange = new EventEmitter<Order>();
modal!: NgbModalRef;
constructor(modalService: NgbModal) {
super();
modalService.activeInstances
.pipe(takeUntil(this.destroy$))
.subscribe((mls) => {
this.modal = mls[mls.length - 1];
});
}
orderChanged(order: Order) {
this.orderChange.emit(order);
this.modal.close();
}
}
<ul>
<li *ngFor="let order of orders">
<dl>
<dt>Client</dt>
<dd>
{{ order.client }}
</dd>
<dt>Date</dt>
<dd>{{ order.date | date }}</dd>
</dl>
</li>
</ul>
<table class="table table-striped">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Client</th>
<th scope="col">Address</th>
<th scope="col">Date</th>
<th scope="col">Products</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
@for (order of orders; track order.id){
<tr>
<th scope="row">{{ order.id }}</th>
<td>{{ order.client }}</td>
<td>{{ order.address }}</td>
<td>{{ order.date | date }}</td>
<td>{{ order.orderLines.length }}</td>
<td>
<a
class="btn btn-sm btn-outline-secondary me-2"
role="button"
[routerLink]="['order', order.id]"
>
Details
</a>
<button
type="button"
class="btn btn-sm btn-outline-secondary me-2"
(click)="openOrder(order)"
>
Edit
</button>
<button
type="button"
class="btn btn-sm btn-outline-danger me-2"
(click)="deleteOrder(order.id)"
>
Delete
</button>
</td>
</tr>
}
</tbody>
</table>
@if (page) {
<ngb-pagination
[page]="page.number + 1"
(pageChange)="pageNumberChange($event - 1)"
[pageSize]="page.size"
[collectionSize]="page.totalElements"
></ngb-pagination>
}
import {ChangeDetectionStrategy, Component, Input} from '@angular/core';
import {AsyncPipe, DatePipe, NgForOf} from "@angular/common";
import {Observable} from "rxjs";
import {Order} from "../../../../core/model/order.model";
import {AbstractBasicComponent} from "../../../../core/abstract/abstract-component";
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { Order } from '../../../../core/model/order.model';
import { PageModel } from '../../../../core/model/paged-response.model';
@Component({
selector: 'app-orders-list',
......@@ -11,8 +9,21 @@ import {AbstractBasicComponent} from "../../../../core/abstract/abstract-compone
})
export class OrdersListComponent {
@Input() orders!: Order[];
@Input() page!: PageModel;
@Output() pageNumber = new EventEmitter<number>();
constructor(
){
@Output() openModal = new EventEmitter<Order>();
@Output() delete = new EventEmitter<number>();
pageNumberChange(page: number) {
this.pageNumber.emit(page);
}
openOrder(order: Order) {
this.openModal.emit(order);
}
deleteOrder(id: number) {
this.delete.emit(id);
}
}
<app-orders-list [orders]="(orders$ | async)!"></app-orders-list>
<ul ngbNav #nav="ngbNav" class="nav-tabs" [destroyOnHide]="false">
<li ngbNavItem>
<button ngbNavLink>Orders</button>
<ng-template ngbNavContent>
<div class="py-2 float-end">
<a
role="button"
[routerLink]="['order']"
class="btn btn-outline-secondary me-2"
>
New order
</a>
</div>
<ng-container *ngIf="ordersSubj$ | async as orders; else loading">
<app-orders-list
[orders]="orders.content"
[page]="orders.page"
(pageNumber)="ordersPageNumber($event)"
(openModal)="openOrderModal($event)"
(delete)="deleteOrder($event)"
></app-orders-list>
</ng-container>
<ng-template #loading> loading... </ng-template>
</ng-template>
</li>
<li ngbNavItem>
<button ngbNavLink>Products</button>
<ng-template ngbNavContent>
<div class="py-2 float-end">
<button
class="btn btn-outline-secondary me-2"
(click)="openNewProductModal()"
>
New product
</button>
</div>
<ng-container *ngIf="goodsSubj$ | async as goods; else loading">
<app-goods-list
[goods]="goods.content"
[page]="goods.page"
(pageNumber)="goodsPageNumber($event)"
(openModal)="openGoodsModal($event)"
(delete)="deleteGoods($event)"
></app-goods-list>
</ng-container>
<ng-template #loading> loading... </ng-template>
</ng-template>
</li>
</ul>
<div [ngbNavOutlet]="nav" class="mt-2"></div>
import {Component, OnInit} from '@angular/core';
import {Observable} from "rxjs";
import {Order} from "../../core/model/order.model";
import {OrderService} from "../../core/service/order.service";
import {AbstractBasicComponent} from "../../core/abstract/abstract-component";
import { Component, OnInit } from '@angular/core';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { Subject, concatMap, filter, take } from 'rxjs';
import { AbstractBasicComponent } from '../../core/abstract/abstract-component';
import { Goods } from '../../core/model/goods.model';
import { Order } from '../../core/model/order.model';
import {
PageModel,
PagedResponseModel,
} from '../../core/model/paged-response.model';
import { GoodsService } from '../../core/service/goods.service';
import { OrderService } from '../../core/service/order.service';
import { GoodsEditModalComponent } from './components/goods-edit-modal/goods-edit-modal.component';
import { OrderEditModalComponent } from './components/order-edit-modal/order-edit-modal.component';
@Component({
selector: 'app-index-page',
templateUrl: './index-page.component.html',
styleUrl: './index-page.component.scss'
styleUrl: './index-page.component.scss',
})
export class IndexPageComponent extends AbstractBasicComponent implements OnInit {
export class IndexPageComponent
extends AbstractBasicComponent
implements OnInit
{
protected ordersSubj$ = new Subject<PagedResponseModel<Order>>();
protected goodsSubj$ = new Subject<PagedResponseModel<Goods>>();
protected orders$!: Observable<Order[]>;
goodsPage!: PageModel;
orderPage!: PageModel;
constructor(private orderService: OrderService) {
constructor(
private orderService: OrderService,
private goodsService: GoodsService,
private modalService: NgbModal,
) {
super();
}
ngOnInit() {
this.orders$ = this.orderService.getOrders();
this.orderService.getOrders().subscribe((resp) => {
this.orderPage = resp.page;
this.ordersSubj$.next(resp);
});
this.goodsService.get().subscribe((resp) => {
this.goodsPage = resp.page;
this.goodsSubj$.next(resp);
});
}
ordersPageNumber(page: number) {
this.orderService.getOrders(page).subscribe((resp) => {
this.orderPage = resp.page;
this.ordersSubj$.next(resp);
});
}
goodsPageNumber(page: number) {
this.goodsService.get(page).subscribe((resp) => {
this.goodsPage = resp.page;
this.goodsSubj$.next(resp);
});
}
deleteOrder(id: number) {
this.orderService
.deleteOrder(id)
.pipe(concatMap(() => this.orderService.getOrders(this.orderPage.number)))
.subscribe((resp) => {
this.orderPage = resp.page;
this.ordersSubj$.next(resp);
});
}
deleteGoods(id: number) {
this.goodsService
.delete(id)
.pipe(concatMap(() => this.goodsService.get(this.goodsPage.number)))
.subscribe((resp) => {
this.goodsPage = resp.page;
this.goodsSubj$.next(resp);
});
}
openOrderModal(order: Order): void {
const orderModalRef = this.modalService.open(OrderEditModalComponent);
orderModalRef.componentInstance.order = order;
orderModalRef.componentInstance.orderChange
.pipe(
take(1),
concatMap(() => this.orderService.getOrders(this.orderPage.number)),
)
.subscribe((resp: PagedResponseModel<Order>) => {
this.orderPage = resp.page;
this.ordersSubj$.next(resp);
});
}
openGoodsModal(product: Goods): void {
const goodsModalRef = this.modalService.open(GoodsEditModalComponent);
goodsModalRef.componentInstance.product = product;
goodsModalRef.componentInstance.isUpdate
.pipe(
filter(Boolean),
concatMap(() => this.goodsService.get(this.goodsPage.number)),
)
.subscribe((resp: PagedResponseModel<Goods>) => {
this.goodsPage = resp.page;
this.goodsSubj$.next(resp);
});
}
openNewProductModal(): void {
const goodsModalRef = this.modalService.open(GoodsEditModalComponent);
goodsModalRef.componentInstance.isUpdate
.pipe(
filter(Boolean),
concatMap(() => this.goodsService.get(this.goodsPage.number)),
)
.subscribe((resp: PagedResponseModel<Goods>) => {
this.goodsPage = resp.page;
this.goodsSubj$.next(resp);
});
}
}
<div class="modal-header">
<h4 class="modal-title" id="modal-basic-title">Edit products</h4>
<button
type="button"
class="btn-close"
aria-label="Close"
(click)="modal?.close()"
></button>
</div>
<div class="modal-body">
@if (goods) {
<table class="table table-striped">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Name</th>
<th scope="col">Price</th>
<th scope="col">Count</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
@for (product of goods.content; track product.id) {
<tr>
<th scope="row">{{ product.id }}</th>
<td>{{ product.name }}</td>
<td>{{ product.price | currency }}</td>
<td>
<input
type="number"
min="1"
class="w-50 form-control"
[value]="productsCount(product.id)"
#count
/>
</td>
<td>
<button
type="button"
[ngClass]="
productsCount(product.id)
? 'btn-outline-primary'
: 'btn-outline-secondary'
"
class="btn btn-sm me-2"
(click)="addOrUpdate(product.id, +count.value)"
>
{{ productsCount(product.id) ? "Update" : "Add" }}
</button>
</td>
</tr>
}
</tbody>
</table>
@if (goods) {
<ngb-pagination
[page]="goods.page.number + 1"
(pageChange)="pageNumberChange($event - 1)"
[pageSize]="goods.page.size"
[collectionSize]="goods.page.totalElements"
></ngb-pagination>
}
} @else {
loading...
}
<ng-template #loading> loading... </ng-template>
</div>
<div class="modal-footer">
<button class="btn btn-outline-secondary" (click)="modal?.close()">
Close
</button>
</div>
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { AddProductModalComponent } from './add-product-modal.component';
describe('AddProductModalComponent', () => {
let component: AddProductModalComponent;
let fixture: ComponentFixture<AddProductModalComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [AddProductModalComponent]
})
.compileComponents();
fixture = TestBed.createComponent(AddProductModalComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
import { takeUntil } from 'rxjs';
import { AbstractBasicComponent } from '../../../../core/abstract/abstract-component';
import { Goods } from '../../../../core/model/goods.model';
import { Order } from '../../../../core/model/order.model';
import { PagedResponseModel } from '../../../../core/model/paged-response.model';
import { UpdateCount } from '../../order-page.component';
@Component({
selector: 'app-add-product-modal',
templateUrl: './add-product-modal.component.html',
styleUrl: './add-product-modal.component.scss',
})
export class AddProductModalComponent extends AbstractBasicComponent {
@Input() goods!: PagedResponseModel<Goods>;
@Input() order!: Order;
@Output() pageNumber = new EventEmitter<number>();
@Output() orderLineChange = new EventEmitter<UpdateCount>();
modal: NgbModalRef | undefined;
constructor(modalService: NgbModal) {
super();
modalService.activeInstances
.pipe(takeUntil(this.destroy$))
.subscribe((mls) => {
this.modal = mls[mls.length - 1];
});
}
addOrUpdate(goodsId: number, count: number) {
if (count) {
this.orderLineChange.emit({
goodsId,
count,
});
}
}
productsCount(productId: number): number {
return (
this.order?.orderLines.find((ol) => ol.goods.id === productId)?.count ?? 0
);
}
pageNumberChange(page: number) {
this.pageNumber.emit(page);
}
}
<h3>Order Products</h3>
<table class="table table-striped">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Name</th>
<th scope="col">Price</th>
<th scope="col">Count</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
@for (orderLine of orderLines; let i = $index; track orderLine.id) {
<tr>
<td>{{ i + 1 }}</td>
<td>{{ orderLine.goods.name }}</td>
<td>{{ orderLine.goods.price | currency }}</td>
<td>{{ orderLine.count }}</td>
<td>
<button
type="button"
class="btn btn-sm btn-outline-danger me-2"
(click)="deleteLine(orderLine.id)"
>
Delete
</button>
</td>
</tr>
}
@if (orderLines.length === 0) {
<tr>
<td colspan="5" class="text-center">Nothing</td>
</tr>
}
</tbody>
</table>
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { OrderLineListComponent } from './order-line-list.component';
describe('OrderLineListComponent', () => {
let component: OrderLineListComponent;
let fixture: ComponentFixture<OrderLineListComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [OrderLineListComponent]
})
.compileComponents();
fixture = TestBed.createComponent(OrderLineListComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { OrderLine } from '../../../../core/model/order.model';
@Component({
selector: 'app-order-line-list',
templateUrl: './order-line-list.component.html',
styleUrl: './order-line-list.component.scss',
})
export class OrderLineListComponent {
@Input() orderLines!: OrderLine[];
@Output() delete = new EventEmitter<number>();
deleteLine(id: number) {
this.delete.emit(id);
}
}
<ul class="nav">
<li class="nav-item">
<div class="py-2">
<a class="nav-link active" aria-current="page" [routerLink]="'/'"
><i class="bi bi-arrow-left me-2"></i>Back</a
>
</div>
</li>
</ul>
@if (isNew) {
<h3>New order</h3>
<app-order-form></app-order-form>
} @else {
<h3>Order</h3>
@if (orderSubj$ | async; as order) {
<table class="table">
<tr>
<th>Client</th>
<td>{{ order.client }}</td>
</tr>
<tr>
<th>Address</th>
<td>{{ order.address }}</td>
</tr>
<tr>
<th>Date</th>
<td>{{ order.date }}</td>
</tr>
</table>
<div class="py-2 float-end">
<button
class="btn btn-outline-secondary me-2"
(click)="openAddProductModal()"
>
Edit products
</button>
</div>
<app-order-line-list
(delete)="deleteLine($event)"
(add)="openAddProductModal()"
[orderLines]="order.orderLines"
></app-order-line-list>
} @else {
<div>Loading...</div>
}
}
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { OrderPageComponent } from './order-page.component';
describe('OrderPageComponent', () => {
let component: OrderPageComponent;
let fixture: ComponentFixture<OrderPageComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [OrderPageComponent]
})
.compileComponents();
fixture = TestBed.createComponent(OrderPageComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { ActivatedRoute } from '@angular/router';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { Subject, concatMap, takeUntil } from 'rxjs';
import { environment } from '../../../environments/environment';
import { AbstractBasicComponent } from '../../core/abstract/abstract-component';
import { Goods } from '../../core/model/goods.model';
import { Order } from '../../core/model/order.model';
import { PagedResponseModel } from '../../core/model/paged-response.model';
import { GoodsService } from '../../core/service/goods.service';
import { OrderLineService } from '../../core/service/order-line.service';
import { OrderService } from '../../core/service/order.service';
import { AddProductModalComponent } from './components/add-product-modal/add-product-modal.component';
const DEV = !environment.production;
@Component({
selector: 'app-order-page',
templateUrl: './order-page.component.html',
styleUrl: './order-page.component.scss',
})
export class OrderPageComponent
extends AbstractBasicComponent
implements OnInit
{
form: FormGroup;
orderSubj$ = new Subject<Order>();
order!: Order;
private id!: number;
constructor(
private orderService: OrderService,
private orderLineService: OrderLineService,
private goodsService: GoodsService,
private modalService: NgbModal,
fb: FormBuilder,
route: ActivatedRoute,
) {
super();
this.form = fb.group({
client: ['', Validators.required],
date: ['', Validators.required],
address: ['', Validators.required],
});
route.params
.pipe(takeUntil(this.destroy$))
.subscribe((params) => (this.id = +params['id']));
}
public get isNew(): boolean {
return this.id === null || isNaN(this.id);
}
ngOnInit(): void {
if (!this.isNew) {
this.orderService.getOrder(this.id).subscribe({
next: (order) => {
this.orderSubj$.next(order);
this.order = order;
},
error: (err) => {
if (DEV) {
console.dir(`Error: ${err}`);
}
},
});
}
}
deleteLine(id: number) {
this.orderLineService
.delete(id)
.pipe(concatMap(() => this.orderService.getOrder(this.id)))
.subscribe({
next: (order: Order) => {
this.orderSubj$.next(order);
this.order = order;
},
error: (err) => {
if (DEV) {
console.dir(`Error: ${err}`);
}
},
});
}
openAddProductModal(): void {
const orderModalRef = this.modalService.open(AddProductModalComponent, {
size: 'xl',
});
orderModalRef.componentInstance.order = this.order;
this.goodsService
.get()
.subscribe((resp) => (orderModalRef.componentInstance.goods = resp));
orderModalRef.componentInstance.pageNumber
.pipe(concatMap((page: number) => this.goodsService.get(page)))
.subscribe((resp: PagedResponseModel<Goods>) => {
orderModalRef.componentInstance.goods = resp;
});
orderModalRef.componentInstance.orderLineChange
.pipe(
takeUntil(this.destroy$),
concatMap((v: UpdateCount) => {
const orderLineId = this.order.orderLines.find(
(ol) => ol.goods.id === v.goodsId,
)?.id;
if (orderLineId) {
return this.orderLineService.put({
id: orderLineId,
orderId: this.order.id,
goodsId: v.goodsId,
count: v.count,
});
}
return this.orderLineService.post({
orderId: this.order.id,
goodsId: v.goodsId,
count: v.count,
});
}),
concatMap(() => this.orderService.getOrder(this.id)),
)
.subscribe({
next: (order: Order) => {
this.orderSubj$.next(order);
this.order = order;
orderModalRef.componentInstance.order = this.order;
},
error: (err: any) => {
if (DEV) {
console.dir(`Error: ${err}`);
}
},
});
}
previousState(): void {
window.history.back();
}
}
export interface UpdateCount {
goodsId: number;
count: number;
}
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { IndexPageComponent } from './index-page/index-page.component';
import { OrderPageComponent } from './order-page/order-page.component';
const routes: Routes = [
{path: '', component: IndexPageComponent}
{ path: '', component: IndexPageComponent },
{ path: 'order', component: OrderPageComponent },
{ path: 'order/:id', component: OrderPageComponent },
];
@NgModule({
......
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { PagesRoutingModule } from './pages-routing.module';
import { IndexPageComponent } from './index-page/index-page.component';
import { SharedModule } from '../shared/shared.module';
import { GoodsEditModalComponent } from './index-page/components/goods-edit-modal/goods-edit-modal.component';
import { GoodsListComponent } from './index-page/components/goods-list/goods-list.component';
import { OrderEditModalComponent } from './index-page/components/order-edit-modal/order-edit-modal.component';
import { OrdersListComponent } from './index-page/components/orders-list/orders-list.component';
import { IndexPageComponent } from './index-page/index-page.component';
import { AddProductModalComponent } from './order-page/components/add-product-modal/add-product-modal.component';
import { OrderLineListComponent } from './order-page/components/order-line-list/order-line-list.component';
import { OrderPageComponent } from './order-page/order-page.component';
import { PagesRoutingModule } from './pages-routing.module';
@NgModule({
declarations: [IndexPageComponent, OrdersListComponent],
imports: [CommonModule, PagesRoutingModule],
declarations: [
IndexPageComponent,
OrderPageComponent,
OrdersListComponent,
GoodsListComponent,
OrderLineListComponent,
GoodsEditModalComponent,
AddProductModalComponent,
OrderEditModalComponent,
],
imports: [CommonModule, SharedModule, PagesRoutingModule],
})
export class PagesModule {}
@for (validation of messages; track validation) {
@if (
control.hasError(validation.type) && (control.touched || control.dirty)
) {
{{ validation.message }}
}
}
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FormErrorsComponent } from './form-errors.component';
describe('FormErrorsComponent', () => {
let component: FormErrorsComponent;
let fixture: ComponentFixture<FormErrorsComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [FormErrorsComponent]
})
.compileComponents();
fixture = TestBed.createComponent(FormErrorsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, Input } from '@angular/core';
import { AbstractControl } from '@angular/forms';
import { ValidationMessage } from '../../core/model/validation.model';
@Component({
selector: 'app-form-errors',
templateUrl: './form-errors.component.html',
styleUrl: './form-errors.component.scss',
})
export class FormErrorsComponent {
@Input() messages!: ValidationMessage[];
@Input() control!: AbstractControl;
}
<form [formGroup]="form">
<div class="mb-3">
<div class="mb-3">
<label for="nameInput" class="form-label">Name</label>
<input
class="form-control"
id="nameInput"
placeholder="John Dow"
formControlName="name"
/>
</div>
<div class="mb-3">
<label for="priceInput" class="form-label">Price</label>
<input
type="number"
step="0.01"
class="form-control"
id="priceInput"
placeholder="123.00"
formControlName="price"
/>
</div>
</div>
</form>
<button type="button" class="btn btn-outline-secondary" (click)="save()">
{{ isNew ? "Create" : "Save" }}
</button>
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { GoodsFormComponent } from './goods-form.component';
describe('GoodsFormComponent', () => {
let component: GoodsFormComponent;
let fixture: ComponentFixture<GoodsFormComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [GoodsFormComponent]
})
.compileComponents();
fixture = TestBed.createComponent(GoodsFormComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { environment } from '../../../../environments/environment';
import { Goods } from '../../../core/model/goods.model';
import { GoodsService } from '../../../core/service/goods.service';
const DEV = !environment.production;
@Component({
selector: 'app-goods-form',
templateUrl: './goods-form.component.html',
styleUrl: './goods-form.component.scss',
})
export class GoodsFormComponent {
@Input() product!: Goods;
@Output() productChange = new EventEmitter<Goods>();
@Output() isUpdate = new EventEmitter<boolean>();
form: FormGroup;
constructor(
fb: FormBuilder,
private goodsService: GoodsService,
) {
this.form = fb.group({
name: ['', Validators.required],
price: ['', Validators.required],
});
}
public get isNew(): boolean {
return !this.product;
}
ngOnInit(): void {
if (!this.isNew) {
this.form.setValue({
name: this.product?.name,
price: this.product?.price,
});
}
}
save() {
if (this.form.valid) {
if (this.isNew) {
// Create
this.goodsService.post({ ...this.form.value }).subscribe({
next: (res) => {
this.isUpdate.emit(true);
},
error: (err) => {
if (DEV) {
console.dir(err);
}
},
});
} else {
// Update
this.goodsService
.put({ ...this.form.value, id: this.product.id })
.subscribe({
next: (res) => {
this.productChange.emit(res);
this.isUpdate.emit(true);
},
error: (err) => {
if (DEV) {
console.dir(err);
}
},
});
}
}
}
}
<form [formGroup]="form">
<div class="mb-3">
<div class="mb-3">
<label for="clientInput" class="form-label">Client</label>
<input
class="form-control"
id="clientInput"
placeholder="Client Name"
formControlName="client"
[class.is-invalid]="form.touched && form.get('client')?.invalid"
/>
<div class="invalid-feedback">
<app-form-errors
[control]="form.get('client')!"
[messages]="validationMsgs['client']"
></app-form-errors>
</div>
</div>
<div class="mb-3">
<label for="addressInput" class="form-label">Address</label>
<input
class="form-control"
id="addressInput"
placeholder="Address line"
formControlName="address"
[class.is-invalid]="form.touched && form.get('address')?.invalid"
/>
<div class="invalid-feedback">
<app-form-errors
[control]="form.get('address')!"
[messages]="validationMsgs['address']"
></app-form-errors>
</div>
</div>
<div class="mb-3">
<label for="dateInput" class="form-label">Date</label>
<div class="input-group">
<input
class="form-control"
placeholder="dd/mm/yyyy"
type="text"
ngbDatepicker
#d="ngbDatepicker"
id="addressInput"
formControlName="date"
[class.is-invalid]="form.touched && form.get('date')?.invalid"
/>
<button
class="btn btn-outline-secondary bi bi-calendar3"
(click)="d.toggle()"
type="button"
></button>
</div>
<div class="invalid-feedback">
<app-form-errors
[control]="form.get('date')!"
[messages]="validationMsgs['date']"
></app-form-errors>
</div>
</div>
</div>
<button class="btn btn-outline-primary me-2" (click)="save()">
{{ isNew ? "Create" : "Update" }}
</button>
</form>
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { OrderFormComponent } from './order-form.component';
describe('OrderFormComponent', () => {
let component: OrderFormComponent;
let fixture: ComponentFixture<OrderFormComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [OrderFormComponent]
})
.compileComponents();
fixture = TestBed.createComponent(OrderFormComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import { environment } from '../../../../environments/environment';
import { Order } from '../../../core/model/order.model';
import { ControlsValidations } from '../../../core/model/validation.model';
import { OrderService } from '../../../core/service/order.service';
const DEV = !environment.production;
@Component({
selector: 'app-order-form',
templateUrl: './order-form.component.html',
styleUrl: './order-form.component.scss',
})
export class OrderFormComponent {
@Input() order!: Order;
@Output() orderChange = new EventEmitter<Order>();
form: FormGroup;
validationMsgs: ControlsValidations;
constructor(
fb: FormBuilder,
private router: Router,
private orderService: OrderService,
) {
this.form = fb.group({
client: ['', Validators.required],
address: ['', Validators.required],
date: ['', Validators.required],
});
this.validationMsgs = {
client: [{ type: 'required', message: 'Required fileld' }],
address: [{ type: 'required', message: 'Required fileld' }],
date: [
{ type: 'required', message: 'Required fileld' },
{ type: 'ngbDate', message: 'Invalid date format' },
],
};
}
public get isNew(): boolean {
return !this.order;
}
save() {
if (this.form.valid) {
if (this.isNew) {
this.orderService.postOrder({ ...this.form.value }).subscribe({
next: (res) => {
this.orderChange.emit(res);
this.router.navigate(['order', res.id]);
},
error: (err) => {
if (DEV) {
console.dir(err);
}
},
});
} else {
this.orderService
.putOrder({ ...this.form.value, id: this.order.id })
.subscribe({
next: (res) => {
this.orderChange.emit(res);
},
error: (err) => {
if (DEV) {
console.dir(err);
}
},
});
}
}
}
ngOnInit(): void {
if (!this.isNew) {
this.form.setValue({
client: this.order.client,
date: this.order.date,
address: this.order.address,
});
}
}
}
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import {
NgbDatepickerModule,
NgbNav,
NgbNavContent,
NgbNavItem,
NgbNavItemRole,
NgbNavLinkBase,
NgbNavLinkButton,
NgbNavOutlet,
NgbPagination,
} from '@ng-bootstrap/ng-bootstrap';
import { FormErrorsComponent } from './form-errors/form-errors.component';
import { GoodsFormComponent } from './forms/goods-form/goods-form.component';
import { OrderFormComponent } from './forms/order-form/order-form.component';
@NgModule({
declarations: [],
declarations: [FormErrorsComponent, OrderFormComponent, GoodsFormComponent],
imports: [
CommonModule
]
CommonModule,
FormsModule,
ReactiveFormsModule,
NgbNav,
NgbNavContent,
NgbNavItem,
NgbNavItemRole,
NgbNavLinkBase,
NgbNavLinkButton,
NgbNavOutlet,
NgbDatepickerModule,
NgbPagination,
],
exports: [
FormsModule,
ReactiveFormsModule,
NgbNav,
NgbNavContent,
NgbNavItem,
NgbNavItemRole,
NgbNavLinkBase,
NgbNavLinkButton,
NgbNavOutlet,
NgbDatepickerModule,
NgbPagination,
FormErrorsComponent,
OrderFormComponent,
GoodsFormComponent,
],
})
export class SharedModule { }
export class SharedModule {}
......@@ -5,5 +5,6 @@ export const environment = {
API_PREFIX: '/api',
ORDER_URL: '/orders',
ORDER_LINE_URL: '/order-line',
GOODS_URL: '/goods',
};
......@@ -5,5 +5,6 @@ export const environment = {
API_PREFIX: '/api',
ORDER_URL: '/orders',
ORDER_LINE_URL: '/order-line',
GOODS_URL: '/goods',
};
/// <reference types="@angular/localize" />
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
......
/* You can add global styles to this file, and also import other style files */
/* Importing Bootstrap SCSS file. */
@import "bootstrap/scss/bootstrap";
......@@ -3,7 +3,9 @@
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/app",
"types": []
"types": [
"@angular/localize"
]
},
"files": [
"src/main.ts"
......
......@@ -13,7 +13,7 @@
"sourceMap": true,
"declaration": false,
"experimentalDecorators": true,
"moduleResolution": "bundler",
"moduleResolution": "node",
"importHelpers": true,
"target": "ES2022",
"module": "ES2022",
......
......@@ -4,7 +4,8 @@
"compilerOptions": {
"outDir": "./out-tsc/spec",
"types": [
"jasmine"
"jasmine",
"@angular/localize"
]
},
"include": [
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment