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; package com.example.testj.domain;
import jakarta.persistence.*; import jakarta.persistence.Column;
import jakarta.validation.constraints.Digits; import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotEmpty;
import java.util.List;
@Entity @Entity
@Table(name = "goods") @Table(name = "goods")
public class Goods { public class Goods {
...@@ -18,13 +19,22 @@ public class Goods { ...@@ -18,13 +19,22 @@ public class Goods {
@Column(name = "name") @Column(name = "name")
private String name; private String name;
@Digits
@NotEmpty
@Column(name = "price") @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") public Goods(Goods goods) {
List<OrderLine> orderLines; this.name = goods.getName();
this.price = goods.getPrice();
}
public Long getId() { public Long getId() {
return id; return id;
...@@ -42,11 +52,11 @@ public class Goods { ...@@ -42,11 +52,11 @@ public class Goods {
this.name = name; this.name = name;
} }
public Number getPrice() { public Double getPrice() {
return price; return price;
} }
public void setPrice(Number price) { public void setPrice(Double price) {
this.price = price; this.price = price;
} }
} }
package com.example.testj.domain; package com.example.testj.domain;
import jakarta.persistence.*; import java.util.List;
import jakarta.validation.constraints.NotBlank;
import org.springframework.format.annotation.DateTimeFormat; 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 @Entity
@Table(name = "orders") @Table(name = "orders")
...@@ -26,7 +34,7 @@ public class Order { ...@@ -26,7 +34,7 @@ public class Order {
@Column(name = "address") @Column(name = "address")
private String 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; List<OrderLine> orderLines;
public Order() { public Order() {
...@@ -38,10 +46,23 @@ public class Order { ...@@ -38,10 +46,23 @@ public class Order {
this.date = date; 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() { public List<OrderLine> getOrderLines() {
return orderLines; return orderLines;
} }
public void setOrderLines(List<OrderLine> orderLines) {
this.orderLines = orderLines;
}
public String getDate() { public String getDate() {
return date; return date;
} }
......
package com.example.testj.domain; package com.example.testj.domain;
import jakarta.persistence.*;
import org.springframework.format.annotation.NumberFormat; 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 @Entity
@Table(name = "order_line") @Table(name = "order_line")
...@@ -13,20 +21,20 @@ public class OrderLine { ...@@ -13,20 +21,20 @@ public class OrderLine {
@Column(name = "id") @Column(name = "id")
private Long id; private Long id;
@JsonIgnore
@ManyToOne @ManyToOne
@JoinColumn(name = "order_id", nullable = false) @JoinColumn(name = "order_id")
private Order order; private Order order;
@ManyToOne @ManyToOne
@JoinColumn(name = "goods_id", nullable = false) @JoinColumn(name = "goods_id")
private Goods goods; private Goods goods;
@Column(name = "count") @Column(name = "count")
@NumberFormat @NumberFormat
@Min(1)
private int count; private int count;
public Long getId() { public Long getId() {
return id; return id;
} }
...@@ -46,4 +54,17 @@ public class OrderLine { ...@@ -46,4 +54,17 @@ public class OrderLine {
public Goods getGoods() { public Goods getGoods() {
return goods; 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; ...@@ -2,8 +2,9 @@ package com.example.testj.repository;
import com.example.testj.domain.Goods; import com.example.testj.domain.Goods;
import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
@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; 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.domain.Order;
import com.example.testj.dto.OrderDTO;
import com.example.testj.exception.ResourceNotFoundException; import com.example.testj.exception.ResourceNotFoundException;
import com.example.testj.repository.OrderRepository; 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.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper; 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 @Service
public class OrderService { public class OrderService {
private final OrderRepository orderRepository; private final OrderRepository orderRepository;
private final MappingUtils mappingUtils;
public OrderService(OrderRepository orderRepository) { public OrderService(OrderRepository orderRepository, MappingUtils mappingUtils) {
this.orderRepository = orderRepository; this.orderRepository = orderRepository;
this.mappingUtils = mappingUtils;
} }
public PagedModel<Order> getOrders(Integer page, Integer size, String sortBy, String sortDir) { public PagedModel<OrderDto> 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)))); 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 { public OrderDto getOrder(Long id) throws ResourceNotFoundException {
return orderRepository.findById(id).orElseThrow(ResourceNotFoundException::new); return mappingUtils.toOrderDto(orderRepository.findById(id).orElseThrow(ResourceNotFoundException::new));
} }
public Order saveOrUpdateOrder(Order order) { public void deleteOrder(Long id) throws ResourceNotFoundException {
return orderRepository.save(order); orderRepository.findById(id).orElseThrow(ResourceNotFoundException::new);
orderRepository.deleteById(id);
} }
public Order patchOrder(Long id, JsonNode patch) throws JsonMappingException { public OrderDto createOrUpdateOrder(Order order) {
Order orderData = orderRepository.findById(id).orElseThrow(ResourceNotFoundException::new); return mappingUtils.toOrderDto(orderRepository.save(order));
Order patchedOrder = new ObjectMapper().updateValue(orderData, patch);
return orderRepository.save(patchedOrder);
} }
public void deleteOrder(Long id) throws ResourceNotFoundException { public OrderDto patchOrder(Long id, JsonNode patch) throws JsonMappingException {
orderRepository.findById(id).orElseThrow(ResourceNotFoundException::new); Order orderData = orderRepository.findById(id).orElseThrow(ResourceNotFoundException::new);
orderRepository.deleteById(id); 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 java.io.Serializable;
import com.example.testj.domain.Goods;
/** /**
* DTO for {@link com.example.testj.domain.Goods} * DTO for {@link com.example.testj.domain.Goods}
*/ */
public class GoodsDto implements Serializable { public class GoodsDto implements Serializable {
private final Long id; private Long id;
private final String name; private String name;
private final Number price; private double price;
public GoodsDto(Long id, String name, Number price) { public GoodsDto() {
this.id = id; }
this.name = name;
this.price = price; public GoodsDto(Goods goods) {
this.id = goods.getId();
this.name = goods.getName();
this.price = goods.getPrice();
} }
public Long getId() { public Long getId() {
return id; return id;
} }
public void setId(Long id) {
this.id = id;
}
public String getName() { public String getName() {
return name; return name;
} }
public Number getPrice() { public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price; return price;
} }
@Override public void setPrice(double price) {
public String toString() { this.price = price;
return getClass().getSimpleName() + "(" +
"id = " + id + ", " +
"name = " + name + ", " +
"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.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 { private List<OrderLine> orderLines;
public OrderDTO(Order order) {
public OrderDto() {
}
public OrderDto(Order order) {
this.id = order.getId(); this.id = order.getId();
this.client = order.getClient(); this.client = order.getClient();
this.address = order.getAddress(); this.address = order.getAddress();
this.date = order.getDate(); this.date = order.getDate();
this.orderLines = order.getOrderLines();
} }
private Long id;
private String client;
private String date;
private String address;
public String getDate() { public String getDate() {
return date; return date;
} }
...@@ -45,7 +53,11 @@ public class OrderDTO implements Serializable { ...@@ -45,7 +53,11 @@ public class OrderDTO implements Serializable {
return id; return id;
} }
public void setId(Long id) { public List<OrderLine> getOrderLines() {
this.id = id; 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; package com.example.testj.web.rest;
import com.example.testj.domain.Goods; import org.springframework.data.web.PagedModel;
import com.example.testj.repository.GoodsRepository; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CrossOrigin; 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.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 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 @RestController
@RequestMapping("/api/goods")
@CrossOrigin(origins = "http://localhost:4200") @CrossOrigin(origins = "http://localhost:4200")
public class GoodsController { public class GoodsController {
private final GoodsRepository goodsRepository; private final GoodsService goodsService;
private final MappingUtils mappingUtils;
public GoodsController(GoodsRepository goodsRepository) { public GoodsController(GoodsService goodsService, MappingUtils mappingUtils) {
this.goodsRepository = goodsRepository; this.goodsService = goodsService;
this.mappingUtils = mappingUtils;
} }
@GetMapping("/goods") @GetMapping
public List<Goods> goods() { public PagedModel<GoodsDto> goods(
return (List<Goods>) goodsRepository.findAll(); @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; 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.Order;
import com.example.testj.domain.RestErrorInfo;
import com.example.testj.dto.OrderDTO;
import com.example.testj.exception.ResourceNotFoundException; import com.example.testj.exception.ResourceNotFoundException;
import com.example.testj.repository.OrderRepository;
import com.example.testj.service.OrderService; 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.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode; 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 @RestController
@RequestMapping("/api") @RequestMapping("/api/orders")
@CrossOrigin(origins = "http://localhost:4200") @CrossOrigin(origins = "http://localhost:4200")
public class OrderController { public class OrderController {
private final OrderRepository orderRepository;
private final OrderService orderService; private final OrderService orderService;
private final MappingUtils mappingUtils;
public OrderController(OrderRepository orderRepository, OrderService orderService) { public OrderController(OrderService orderService, MappingUtils mappingUtils) {
this.orderRepository = orderRepository;
this.orderService = orderService; this.orderService = orderService;
this.mappingUtils = mappingUtils;
} }
@GetMapping("/orders") @GetMapping
public PagedModel<Order> getOrders(@RequestHeader(name = "X-Current-Page", defaultValue = "0") Integer page, public PagedModel<OrderDto> getOrders(
@RequestHeader(name = "X-Page-Size", defaultValue = "10") Integer size, @RequestHeader(name = "X-Page-Current", defaultValue = "0") Integer page,
@RequestHeader(name = "X-Sort-By", defaultValue = "id") String sortBy, @RequestHeader(name = "X-Page-Size", defaultValue = "10") Integer size,
@RequestHeader(name = "X-Sort-Direction", defaultValue = "ASC") String sortDir) { @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); return orderService.getOrders(page, size, sortBy, sortDir);
} }
@GetMapping("/orders/{id}") @GetMapping("/{id}")
public Order getOrder(@PathVariable Long id) { public OrderDto getOrder(@PathVariable Long id) {
return this.orderService.getOrder(id); return orderService.getOrder(id);
} }
@PostMapping("/orders") @PostMapping
public Order createOrder(@Valid @RequestBody Order order) { @ResponseStatus(HttpStatus.CREATED)
return orderService.saveOrUpdateOrder(order); public OrderDto createOrder(@Valid @RequestBody Order order) {
return orderService.createOrUpdateOrder(order);
} }
@PutMapping("/orders/{id}") @PutMapping("/{id}")
public Order updateOrder(@PathVariable Long id, @RequestBody Order order) { @ResponseStatus(HttpStatus.OK)
Order updatedOrder = orderService.getOrder(id); public OrderDto updateOrder(@PathVariable Long id, @RequestBody OrderDto order) {
updatedOrder.setClient(order.getClient());
updatedOrder.setAddress(order.getAddress()); OrderDto updatedOrder = orderService.getOrder(id);
updatedOrder.setDate(order.getDate()); updatedOrder.setClient(order.getClient());
return orderService.saveOrUpdateOrder(updatedOrder); updatedOrder.setAddress(order.getAddress());
updatedOrder.setDate(order.getDate());
return orderService.createOrUpdateOrder(mappingUtils.toOrder(updatedOrder));
} }
@PatchMapping("/orders/{id}") @PatchMapping("/{id}")
public Order patchOrder(@PathVariable Long id, @RequestBody JsonNode patch) throws JsonMappingException { public OrderDto patchOrder(@PathVariable Long id, @RequestBody JsonNode patch) throws JsonMappingException {
return orderService.patchOrder(id, patch); return orderService.patchOrder(id, patch);
} }
@DeleteMapping("/orders/{id}") @DeleteMapping("/{id}")
public ResponseEntity<HttpStatus> deleteOrder(@PathVariable Long id) throws ResourceNotFoundException { public ResponseEntity<HttpStatus> deleteOrder(@PathVariable Long id) throws ResourceNotFoundException {
try { try {
orderService.deleteOrder(id); orderService.deleteOrder(id);
...@@ -68,6 +83,5 @@ public class OrderController { ...@@ -68,6 +83,5 @@ public class OrderController {
} catch (ResourceNotFoundException e) { } catch (ResourceNotFoundException e) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND); 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 ...@@ -2,7 +2,7 @@ spring.application.name=testj
spring.datasource.url=jdbc:h2:mem:testdb spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver 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.jpa.defer-datasource-initialization=true
spring.h2.console.enabled=true spring.h2.console.enabled=true
......
...@@ -4,21 +4,20 @@ ...@@ -4,21 +4,20 @@
-- date DATE, -- date DATE,
-- address VARCHAR(50) -- address VARCHAR(50)
-- ); -- );
insert into insert into
orders (id, client, date, address) orders (id, client, date, address)
values values
( (
1, 1,
'Griz Soppit', 'Griz Soppit',
'11/18/2023', '1/8/2023',
'4 Fieldstone Pass' '4 Fieldstone Pass'
), ),
(2, 'Lorain Kroin', '2/23/2024', '3 Boyd Hill'), (2, 'Lorain Kroin', '2/23/2024', '3 Boyd Hill'),
( (
3, 3,
'Benedikta Earry', 'Benedikta Earry',
'12/22/2023', '12/2/2023',
'9 Ridge Oak Road' '9 Ridge Oak Road'
), ),
( (
...@@ -30,31 +29,31 @@ values ...@@ -30,31 +29,31 @@ values
( (
5, 5,
'Tedmund Yukhin', 'Tedmund Yukhin',
'1/29/2024', '1/9/2024',
'1 Northport Trail' '1 Northport Trail'
), ),
( (
6, 6,
'Niki Thibodeaux', 'Niki Thibodeaux',
'1/25/2024', '1/5/2024',
'734 Vera Point' '734 Vera Point'
), ),
( (
7, 7,
'Sheri Habershaw', 'Sheri Habershaw',
'5/25/2024', '5/5/2024',
'7689 Melrose Center' '7689 Melrose Center'
), ),
( (
8, 8,
'Coral Dellatorre', 'Coral Dellatorre',
'7/28/2023', '7/8/2023',
'95 Onsgard Junction' '95 Onsgard Junction'
), ),
( (
9, 9,
'Leisha Barnicott', 'Leisha Barnicott',
'3/15/2024', '3/5/2024',
'945 Hanover Park' '945 Hanover Park'
), ),
( (
...@@ -66,13 +65,13 @@ values ...@@ -66,13 +65,13 @@ values
( (
11, 11,
'Toni Lenchenko', 'Toni Lenchenko',
'2/16/2024', '2/6/2024',
'83 Jenifer Lane' '83 Jenifer Lane'
), ),
( (
12, 12,
'Ynez Moncreiff', 'Ynez Moncreiff',
'5/20/2024', '5/2/2024',
'4182 Sutteridge Parkway' '4182 Sutteridge Parkway'
), ),
( (
...@@ -84,13 +83,13 @@ values ...@@ -84,13 +83,13 @@ values
( (
14, 14,
'Franky Ply', 'Franky Ply',
'3/16/2024', '3/6/2024',
'037 Heffernan Drive' '037 Heffernan Drive'
), ),
( (
15, 15,
'Shel Aykroyd', 'Shel Aykroyd',
'6/28/2023', '6/8/2023',
'53 Del Mar Terrace' '53 Del Mar Terrace'
), ),
( (
...@@ -102,13 +101,13 @@ values ...@@ -102,13 +101,13 @@ values
( (
17, 17,
'Garald Blunsden', 'Garald Blunsden',
'9/30/2023', '9/3/2023',
'7 Acker Drive' '7 Acker Drive'
), ),
( (
18, 18,
'Carree Yakunkin', 'Carree Yakunkin',
'4/19/2024', '4/9/2024',
'40 Transport Court' '40 Transport Court'
), ),
( (
...@@ -132,7 +131,7 @@ values ...@@ -132,7 +131,7 @@ values
( (
22, 22,
'Hebert Canfer', 'Hebert Canfer',
'9/24/2023', '9/4/2023',
'829 Rusk Trail' '829 Rusk Trail'
), ),
( (
...@@ -150,32 +149,32 @@ values ...@@ -150,32 +149,32 @@ values
( (
25, 25,
'Cissiee De Angelis', 'Cissiee De Angelis',
'1/28/2024', '1/8/2024',
'660 Talisman Place' '660 Talisman Place'
), ),
( (
26, 26,
'Rurik Killshaw', 'Rurik Killshaw',
'3/14/2024', '3/4/2024',
'3 Dapin Park' '3 Dapin Park'
), ),
( (
27, 27,
'Carmelle Philler', 'Carmelle Philler',
'4/24/2024', '4/4/2024',
'355 Morning Road' '355 Morning Road'
), ),
( (
28, 28,
'Sara-ann Pook', 'Sara-ann Pook',
'3/21/2024', '3/1/2024',
'7967 Welch Plaza' '7967 Welch Plaza'
), ),
(29, 'Piper Libero', '1/3/2024', '3 Prentice Way'), (29, 'Piper Libero', '1/3/2024', '3 Prentice Way'),
( (
30, 30,
'Shermy Simonnin', 'Shermy Simonnin',
'11/29/2023', '11/9/2023',
'44910 Debra Court' '44910 Debra Court'
), ),
( (
...@@ -193,19 +192,19 @@ values ...@@ -193,19 +192,19 @@ values
( (
33, 33,
'Armstrong Jessard', 'Armstrong Jessard',
'5/13/2024', '5/3/2024',
'1 Center Park' '1 Center Park'
), ),
( (
34, 34,
'Binnie Pearde', 'Binnie Pearde',
'5/30/2024', '5/3/2024',
'72808 Johnson Place' '72808 Johnson Place'
), ),
( (
35, 35,
'Rena Gontier', 'Rena Gontier',
'6/15/2023', '6/5/2023',
'277 Anzinger Terrace' '277 Anzinger Terrace'
), ),
( (
...@@ -224,7 +223,7 @@ values ...@@ -224,7 +223,7 @@ values
( (
39, 39,
'Westbrook Thomsson', 'Westbrook Thomsson',
'5/13/2024', '5/3/2024',
'097 Mcbride Pass' '097 Mcbride Pass'
), ),
( (
...@@ -236,7 +235,7 @@ values ...@@ -236,7 +235,7 @@ values
( (
41, 41,
'Currie Bartosek', 'Currie Bartosek',
'1/18/2024', '1/8/2024',
'34503 Rockefeller Trail' '34503 Rockefeller Trail'
), ),
( (
...@@ -248,13 +247,13 @@ values ...@@ -248,13 +247,13 @@ values
( (
43, 43,
'Blaine Giff', 'Blaine Giff',
'12/16/2023', '12/6/2023',
'0718 Amoth Way' '0718 Amoth Way'
), ),
( (
44, 44,
'Lemmy Stidworthy', 'Lemmy Stidworthy',
'12/21/2023', '12/2/2023',
'6258 Cottonwood Crossing' '6258 Cottonwood Crossing'
), ),
( (
...@@ -266,7 +265,7 @@ values ...@@ -266,7 +265,7 @@ values
( (
46, 46,
'Massimiliano Bogays', 'Massimiliano Bogays',
'10/24/2023', '10/4/2023',
'578 Northridge Parkway' '578 Northridge Parkway'
), ),
( (
...@@ -278,19 +277,19 @@ values ...@@ -278,19 +277,19 @@ values
( (
48, 48,
'Ichabod Goulbourne', 'Ichabod Goulbourne',
'9/18/2023', '9/8/2023',
'325 Comanche Park' '325 Comanche Park'
), ),
( (
49, 49,
'Rab Marishenko', 'Rab Marishenko',
'9/26/2023', '9/6/2023',
'310 Stuart Terrace' '310 Stuart Terrace'
), ),
( (
50, 50,
'Elinore Healings', 'Elinore Healings',
'10/22/2023', '10/2/2023',
'4 Clyde Gallagher Terrace' '4 Clyde Gallagher Terrace'
), ),
( (
...@@ -302,7 +301,7 @@ values ...@@ -302,7 +301,7 @@ values
( (
52, 52,
'Amandy Ridolfo', 'Amandy Ridolfo',
'6/30/2023', '6/3/2023',
'8 Muir Court' '8 Muir Court'
), ),
( (
...@@ -314,13 +313,13 @@ values ...@@ -314,13 +313,13 @@ values
( (
54, 54,
'Adolf Ricoald', 'Adolf Ricoald',
'2/25/2024', '2/5/2024',
'90865 Lawn Park' '90865 Lawn Park'
), ),
( (
55, 55,
'Ilene Hardey', 'Ilene Hardey',
'3/20/2024', '3/2/2024',
'72 Redwing Hill' '72 Redwing Hill'
), ),
( (
...@@ -332,19 +331,19 @@ values ...@@ -332,19 +331,19 @@ values
( (
57, 57,
'Rakel Gunter', 'Rakel Gunter',
'12/18/2023', '12/8/2023',
'33 Melvin Street' '33 Melvin Street'
), ),
( (
58, 58,
'Marielle Semour', 'Marielle Semour',
'3/21/2024', '3/1/2024',
'804 Ohio Place' '804 Ohio Place'
), ),
( (
59, 59,
'Ciro Paulillo', 'Ciro Paulillo',
'9/16/2023', '9/6/2023',
'637 Spohn Plaza' '637 Spohn Plaza'
), ),
( (
...@@ -356,13 +355,13 @@ values ...@@ -356,13 +355,13 @@ values
( (
61, 61,
'Brooks Licciardi', 'Brooks Licciardi',
'7/28/2023', '7/8/2023',
'01404 Waxwing Lane' '01404 Waxwing Lane'
), ),
( (
62, 62,
'Hunt Trusler', 'Hunt Trusler',
'6/20/2023', '6/2/2023',
'172 Hudson Crossing' '172 Hudson Crossing'
), ),
( (
...@@ -386,25 +385,25 @@ values ...@@ -386,25 +385,25 @@ values
( (
66, 66,
'Farr Bruhnicke', 'Farr Bruhnicke',
'3/26/2024', '3/6/2024',
'05 Arrowood Alley' '05 Arrowood Alley'
), ),
( (
67, 67,
'Ruth Blankau', 'Ruth Blankau',
'2/14/2024', '2/4/2024',
'8683 Oakridge Pass' '8683 Oakridge Pass'
), ),
( (
68, 68,
'Pearline Tubbles', 'Pearline Tubbles',
'11/17/2023', '11/7/2023',
'01122 Vidon Drive' '01122 Vidon Drive'
), ),
( (
69, 69,
'Des Penddreth', 'Des Penddreth',
'5/30/2024', '5/3/2024',
'87034 Melody Center' '87034 Melody Center'
), ),
( (
...@@ -416,43 +415,43 @@ values ...@@ -416,43 +415,43 @@ values
( (
71, 71,
'Corly Mc Meekin', 'Corly Mc Meekin',
'10/31/2023', '10/3/2023',
'0 Forest Drive' '0 Forest Drive'
), ),
( (
72, 72,
'Arvie Daen', 'Arvie Daen',
'11/30/2023', '11/3/2023',
'063 Thierer Pass' '063 Thierer Pass'
), ),
( (
73, 73,
'Karla Waryk', 'Karla Waryk',
'3/24/2024', '3/4/2024',
'072 Lindbergh Circle' '072 Lindbergh Circle'
), ),
( (
74, 74,
'Axel Syson', 'Axel Syson',
'9/16/2023', '9/6/2023',
'9880 Butternut Trail' '9880 Butternut Trail'
), ),
( (
75, 75,
'Elliot Beswick', 'Elliot Beswick',
'11/13/2023', '11/3/2023',
'81686 Lakewood Way' '81686 Lakewood Way'
), ),
( (
76, 76,
'Stepha Jan', 'Stepha Jan',
'4/14/2024', '4/4/2024',
'3 Weeping Birch Place' '3 Weeping Birch Place'
), ),
( (
77, 77,
'Ignace Rockey', 'Ignace Rockey',
'11/25/2023', '11/5/2023',
'82131 Rieder Parkway' '82131 Rieder Parkway'
), ),
( (
...@@ -494,13 +493,13 @@ values ...@@ -494,13 +493,13 @@ values
( (
84, 84,
'Zolly Screase', 'Zolly Screase',
'7/24/2023', '7/4/2023',
'09057 Bayside Street' '09057 Bayside Street'
), ),
( (
85, 85,
'Walden Sholl', 'Walden Sholl',
'10/30/2023', '10/10/2023',
'3082 Tomscot Terrace' '3082 Tomscot Terrace'
), ),
( (
...@@ -512,7 +511,7 @@ values ...@@ -512,7 +511,7 @@ values
( (
87, 87,
'Flemming Mecco', 'Flemming Mecco',
'6/22/2023', '6/2/2023',
'99211 Killdeer Pass' '99211 Killdeer Pass'
), ),
( (
...@@ -524,26 +523,26 @@ values ...@@ -524,26 +523,26 @@ values
( (
89, 89,
'Marjory Traske', 'Marjory Traske',
'7/25/2023', '7/5/2023',
'37049 Manufacturers Center' '37049 Manufacturers Center'
), ),
( (
90, 90,
'Ida Newlands', 'Ida Newlands',
'1/29/2024', '1/9/2024',
'00 Thierer Alley' '00 Thierer Alley'
), ),
( (
91, 91,
'Letti Merrisson', 'Letti Merrisson',
'7/27/2023', '7/7/2023',
'36 Green Plaza' '36 Green Plaza'
), ),
(92, 'Fran Bullene', '7/2/2023', '5 Bowman Plaza'), (92, 'Fran Bullene', '7/2/2023', '5 Bowman Plaza'),
( (
93, 93,
'Natala Harteley', 'Natala Harteley',
'1/22/2024', '1/2/2024',
'20 High Crossing Junction' '20 High Crossing Junction'
), ),
( (
...@@ -555,13 +554,13 @@ values ...@@ -555,13 +554,13 @@ values
( (
95, 95,
'Stinky Le Grove', 'Stinky Le Grove',
'12/13/2023', '12/3/2023',
'415 Haas Plaza' '415 Haas Plaza'
), ),
( (
96, 96,
'Nalani Ravenshear', 'Nalani Ravenshear',
'3/14/2024', '3/4/2024',
'56132 Vera Street' '56132 Vera Street'
), ),
( (
...@@ -585,9 +584,10 @@ values ...@@ -585,9 +584,10 @@ values
( (
100, 100,
'Bing Everwin', 'Bing Everwin',
'6/22/2023', '6/2/2023',
'07218 Mitchell Park' '07218 Mitchell Park'
); );
alter sequence orders_seq restart with 150; alter sequence orders_seq restart with 150;
-- create table goods ( -- create table goods (
...@@ -595,7 +595,6 @@ alter sequence orders_seq restart with 150; ...@@ -595,7 +595,6 @@ alter sequence orders_seq restart with 150;
-- name VARCHAR(50), -- name VARCHAR(50),
-- price DECIMAL(5, 2) -- price DECIMAL(5, 2)
-- ); -- );
insert into insert into
goods (id, name, price) goods (id, name, price)
values values
...@@ -779,6 +778,7 @@ values ...@@ -779,6 +778,7 @@ values
), ),
(99, 'Katalin Varga', 83.92), (99, 'Katalin Varga', 83.92),
(100, 'City of Angels', 76.91); (100, 'City of Angels', 76.91);
alter sequence goods_seq restart with 150; alter sequence goods_seq restart with 150;
-- create table order_line ( -- create table order_line (
...@@ -787,108 +787,108 @@ alter sequence goods_seq restart with 150; ...@@ -787,108 +787,108 @@ alter sequence goods_seq restart with 150;
-- goods_id INT, -- goods_id INT,
-- count INT -- count INT
-- ); -- );
insert into insert into
order_line (id, order_id, goods_id, count) order_line (id, order_id, goods_id, count)
values values
(1, 1, 18, 7), (1, 1, 1, 7),
(2, 1, 28, 8), (2, 1, 2, 8),
(3, 1, 41, 9), (3, 1, 3, 9),
(4, 1, 55, 6), (4, 1, 4, 6),
(5, 2, 59, 32), (5, 1, 5, 3),
(6, 2, 52, 66), (6, 2, 52, 6),
(7, 2, 3, 10), (7, 2, 3, 1),
(8, 2, 35, 6), (8, 2, 35, 6),
(9, 2, 35, 80), (9, 2, 35, 8),
(10, 2, 5, 76), (10, 2, 5, 7),
(11, 3, 32, 20), (11, 3, 32, 2),
(12, 3, 9, 96), (12, 3, 9, 9),
(13, 3, 96, 74), (13, 3, 96, 7),
(14, 3, 39, 14), (14, 3, 39, 14),
(15, 4, 16, 48), (15, 4, 16, 8),
(16, 4, 13, 54), (16, 4, 13, 4),
(17, 4, 42, 93), (17, 4, 42, 9),
(18, 5, 53, 10), (18, 5, 53, 1),
(19, 6, 19, 89), (19, 6, 19, 8),
(20, 5, 80, 95), (20, 5, 80, 5),
(21, 8, 20, 92), (21, 8, 20, 2),
(22, 4, 13, 100), (22, 4, 13, 10),
(23, 9, 16, 88), (23, 9, 16, 8),
(24, 3, 65, 27), (24, 3, 65, 7),
(25, 9, 38, 20), (25, 9, 38, 2),
(26, 2, 23, 85), (26, 2, 23, 8),
(27, 4, 45, 79), (27, 4, 45, 9),
(28, 4, 90, 43), (28, 4, 90, 3),
(29, 2, 30, 62), (29, 2, 30, 2),
(30, 3, 48, 26), (30, 3, 48, 6),
(31, 9, 8, 56), (31, 9, 8, 6),
(32, 6, 62, 3), (32, 6, 62, 3),
(33, 3, 52, 46), (33, 3, 52, 6),
(34, 2, 52, 22), (34, 2, 52, 2),
(35, 7, 67, 23), (35, 7, 67, 3),
(36, 9, 6, 22), (36, 9, 6, 12),
(37, 5, 76, 9), (37, 5, 76, 9),
(38, 9, 28, 52), (38, 9, 28, 5),
(39, 3, 56, 57), (39, 3, 56, 7),
(40, 4, 70, 64), (40, 4, 70, 6),
(41, 5, 86, 25), (41, 5, 86, 2),
(42, 9, 46, 31), (42, 9, 46, 3),
(43, 9, 81, 43), (43, 9, 81, 4),
(44, 3, 6, 46), (44, 3, 6, 6),
(45, 1, 56, 18), (45, 1, 56, 8),
(46, 7, 89, 37), (46, 7, 89, 7),
(47, 8, 66, 87), (47, 8, 66, 7),
(48, 8, 39, 33), (48, 8, 39, 3),
(49, 6, 54, 43), (49, 6, 54, 3),
(50, 7, 32, 81), (50, 7, 32, 1),
(51, 1, 94, 33), (51, 1, 94, 3),
(52, 6, 50, 46), (52, 6, 50, 6),
(53, 5, 71, 61), (53, 5, 71, 1),
(54, 2, 73, 40), (54, 2, 73, 2),
(55, 7, 22, 35), (55, 7, 22, 5),
(56, 1, 82, 72), (56, 1, 82, 2),
(57, 4, 54, 61), (57, 4, 54, 1),
(58, 1, 4, 28), (58, 1, 4, 8),
(59, 2, 88, 94), (59, 2, 88, 4),
(60, 6, 97, 54), (60, 6, 97, 4),
(61, 4, 20, 27), (61, 4, 20, 7),
(62, 7, 61, 43), (62, 7, 61, 3),
(63, 5, 26, 96), (63, 5, 26, 6),
(64, 8, 80, 89), (64, 8, 80, 9),
(65, 9, 26, 39), (65, 9, 26, 3),
(66, 6, 60, 55), (66, 6, 60, 5),
(67, 5, 20, 93), (67, 5, 20, 9),
(68, 2, 16, 10), (68, 2, 16, 1),
(69, 8, 15, 13), (69, 8, 15, 3),
(70, 8, 81, 49), (70, 8, 81, 9),
(71, 3, 25, 73), (71, 3, 25, 3),
(72, 6, 39, 75), (72, 6, 39, 5),
(73, 1, 79, 11), (73, 1, 79, 1),
(74, 4, 31, 40), (74, 4, 31, 4),
(75, 3, 3, 38), (75, 3, 3, 3),
(76, 6, 95, 71), (76, 6, 95, 7),
(77, 8, 12, 69), (77, 8, 12, 9),
(78, 4, 28, 86), (78, 4, 28, 6),
(79, 5, 14, 54), (79, 5, 14, 4),
(80, 6, 69, 73), (80, 6, 69, 3),
(81, 9, 50, 43), (81, 9, 50, 3),
(82, 3, 15, 81), (82, 3, 15, 1),
(83, 4, 10, 21), (83, 4, 10, 1),
(84, 3, 29, 46), (84, 3, 29, 6),
(85, 5, 91, 96), (85, 5, 91, 6),
(86, 8, 74, 35), (86, 8, 74, 5),
(87, 7, 77, 42), (87, 7, 77, 2),
(88, 2, 73, 36), (88, 2, 73, 6),
(89, 4, 6, 85), (89, 4, 6, 8),
(90, 8, 67, 29), (90, 8, 67, 9),
(91, 7, 86, 58), (91, 7, 86, 8),
(92, 3, 68, 51), (92, 3, 68, 1),
(93, 8, 96, 31), (93, 8, 96, 3),
(94, 4, 42, 8), (94, 4, 42, 8),
(95, 2, 72, 84), (95, 2, 72, 4),
(96, 8, 1, 61), (96, 8, 1, 1),
(97, 2, 37, 65), (97, 2, 37, 5),
(98, 1, 40, 69), (98, 1, 40, 6),
(99, 2, 9, 13), (99, 2, 9, 3),
(100, 7, 57, 72); (100, 7, 57, 2);
alter sequence order_line_seq restart with 150;
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 ## Development server
...@@ -24,4 +24,4 @@ Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To u ...@@ -24,4 +24,4 @@ Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To u
## Further help ## 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 @@ ...@@ -3,18 +3,12 @@
"version": 1, "version": 1,
"newProjectRoot": "projects", "newProjectRoot": "projects",
"projects": { "projects": {
"testj": { "ng17": {
"projectType": "application", "projectType": "application",
"schematics": { "schematics": {
"@schematics/angular:component": { "@schematics/angular:component": {
"style": "scss", "style": "scss",
"standalone": false "standalone": false
},
"@schematics/angular:directive": {
"standalone": false
},
"@schematics/angular:pipe": {
"standalone": false
} }
}, },
"root": "", "root": "",
...@@ -24,21 +18,15 @@ ...@@ -24,21 +18,15 @@
"build": { "build": {
"builder": "@angular-devkit/build-angular:application", "builder": "@angular-devkit/build-angular:application",
"options": { "options": {
"outputPath": "dist/testj", "outputPath": "dist/ng17",
"index": "src/index.html", "index": "src/index.html",
"browser": "src/main.ts", "browser": "src/main.ts",
"polyfills": [ "polyfills": ["zone.js"],
"zone.js"
],
"tsConfig": "tsconfig.app.json", "tsConfig": "tsconfig.app.json",
"inlineStyleLanguage": "scss", "inlineStyleLanguage": "scss",
"assets": [ "assets": ["src/favicon.ico", "src/assets"],
{
"glob": "**/*",
"input": "public"
}
],
"styles": [ "styles": [
"node_modules/bootstrap-icons/font/bootstrap-icons.scss",
"src/styles.scss" "src/styles.scss"
], ],
"scripts": [] "scripts": []
...@@ -48,13 +36,13 @@ ...@@ -48,13 +36,13 @@
"budgets": [ "budgets": [
{ {
"type": "initial", "type": "initial",
"maximumWarning": "500kB", "maximumWarning": "500kb",
"maximumError": "1MB" "maximumError": "1mb"
}, },
{ {
"type": "anyComponentStyle", "type": "anyComponentStyle",
"maximumWarning": "2kB", "maximumWarning": "2kb",
"maximumError": "4kB" "maximumError": "4kb"
} }
], ],
"outputHashing": "all" "outputHashing": "all"
...@@ -77,35 +65,28 @@ ...@@ -77,35 +65,28 @@
"builder": "@angular-devkit/build-angular:dev-server", "builder": "@angular-devkit/build-angular:dev-server",
"configurations": { "configurations": {
"production": { "production": {
"buildTarget": "testj:build:production" "buildTarget": "ng17:build:production"
}, },
"development": { "development": {
"buildTarget": "testj:build:development" "buildTarget": "ng17:build:development"
} }
}, },
"defaultConfiguration": "development" "defaultConfiguration": "development"
}, },
"extract-i18n": { "extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n" "builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"buildTarget": "ng17:build"
}
}, },
"test": { "test": {
"builder": "@angular-devkit/build-angular:karma", "builder": "@angular-devkit/build-angular:karma",
"options": { "options": {
"polyfills": [ "polyfills": ["zone.js", "zone.js/testing"],
"zone.js",
"zone.js/testing"
],
"tsConfig": "tsconfig.spec.json", "tsConfig": "tsconfig.spec.json",
"inlineStyleLanguage": "scss", "inlineStyleLanguage": "scss",
"assets": [ "assets": ["src/favicon.ico", "src/assets"],
{ "styles": ["src/styles.scss"],
"glob": "**/*",
"input": "public"
}
],
"styles": [
"src/styles.scss"
],
"scripts": [] "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", "version": "0.0.0",
"scripts": { "scripts": {
"ng": "ng", "ng": "ng",
...@@ -10,22 +10,27 @@ ...@@ -10,22 +10,27 @@
}, },
"private": true, "private": true,
"dependencies": { "dependencies": {
"@angular/animations": "^18.0.0", "@angular/animations": "^17.3.0",
"@angular/common": "^18.0.0", "@angular/common": "^17.3.0",
"@angular/compiler": "^18.0.0", "@angular/compiler": "^17.3.0",
"@angular/core": "^18.0.0", "@angular/core": "^17.3.0",
"@angular/forms": "^18.0.0", "@angular/forms": "^17.3.0",
"@angular/platform-browser": "^18.0.0", "@angular/platform-browser": "^17.3.0",
"@angular/platform-browser-dynamic": "^18.0.0", "@angular/platform-browser-dynamic": "^17.3.0",
"@angular/router": "^18.0.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", "rxjs": "~7.8.0",
"tslib": "^2.3.0", "tslib": "^2.3.0",
"zone.js": "~0.14.3" "zone.js": "~0.14.3"
}, },
"devDependencies": { "devDependencies": {
"@angular-devkit/build-angular": "^18.0.2", "@angular-devkit/build-angular": "^17.3.8",
"@angular/cli": "^18.0.2", "@angular/cli": "^17.3.8",
"@angular/compiler-cli": "^18.0.0", "@angular/compiler-cli": "^17.3.0",
"@angular/localize": "^17.3.0",
"@types/jasmine": "~5.1.0", "@types/jasmine": "~5.1.0",
"jasmine-core": "~5.1.0", "jasmine-core": "~5.1.0",
"karma": "~6.4.0", "karma": "~6.4.0",
...@@ -33,6 +38,7 @@ ...@@ -33,6 +38,7 @@
"karma-coverage": "~2.2.0", "karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0", "karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.1.0", "karma-jasmine-html-reporter": "~2.1.0",
"prettier": "^3.3.2",
"typescript": "~5.4.2" "typescript": "~5.4.2"
} }
} }
.main {
}
.content {
width: 100%;
padding: 20px;
}
...@@ -3,17 +3,25 @@ import { BrowserModule } from '@angular/platform-browser'; ...@@ -3,17 +3,25 @@ import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module'; import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component'; 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({ @NgModule({
declarations: [ declarations: [AppComponent],
AppComponent imports: [BrowserModule, AppRoutingModule, NgbModule],
providers: [
provideHttpClient(),
{ provide: NgbDateAdapter, useClass: CustomDateAdapter },
{ provide: NgbDateParserFormatter, useClass: CustomDateParserFormatter },
], ],
imports: [ bootstrap: [AppComponent],
BrowserModule,
AppRoutingModule
],
providers: [provideHttpClient()],
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; id: number;
name: string; name: string;
price: number; price: number;
......
export interface OrderLine {
id: number;
order_id: number;
goods_id: number;
count: number;
}
import { Goods } from './goods.model';
export interface Order { export interface Order {
id: number; id: number;
address: string; address: string;
client: string; client: string;
date: 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 { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http'; import {
import { Observable } from 'rxjs'; HttpClient,
HttpErrorResponse,
HttpHeaders,
} from '@angular/common/http';
import { catchError, EMPTY, Observable } from 'rxjs';
import { environment } from '../../../environments/environment'; import { environment } from '../../../environments/environment';
import { Order } from '../model/order.model'; import { Order } from '../model/order.model';
import { PagedResponseModel } from '../model/paged-response.model';
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
}) })
export class OrderService { export class OrderService {
private readonly apiUrl = `${environment.API_ROOT}${environment.API_PREFIX}${environment.ORDER_URL}`; private readonly apiUrl = `${environment.API_ROOT}${environment.API_PREFIX}${environment.ORDER_URL}`;
private page = 1; private page = 0;
private limit = 10; private size = 10;
constructor(private http: HttpClient) {} constructor(private http: HttpClient) {}
getOrders(): Observable<Order[]> { getOrders(
page = this.page,
size = this.size
): Observable<PagedResponseModel<Order>> {
const url = `${this.apiUrl}`; const url = `${this.apiUrl}`;
const headers = new HttpHeaders({ const headers = new HttpHeaders({
'Content-Type': 'application/json', 'Content-Type': 'application/json',
Page: String(this.page), 'X-Page-Current': String(page),
Limit: String(this.limit), '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> <table class="table table-striped">
<li *ngFor="let order of orders"> <thead>
<dl> <tr>
<dt>Client</dt> <th scope="col">#</th>
<dd> <th scope="col">Client</th>
{{ order.client }} <th scope="col">Address</th>
</dd> <th scope="col">Date</th>
<dt>Date</dt> <th scope="col">Products</th>
<dd>{{ order.date | date }}</dd> <th scope="col"></th>
</dl> </tr>
</li> </thead>
</ul> <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 { Component, EventEmitter, Input, Output } from '@angular/core';
import {AsyncPipe, DatePipe, NgForOf} from "@angular/common"; import { Order } from '../../../../core/model/order.model';
import {Observable} from "rxjs"; import { PageModel } from '../../../../core/model/paged-response.model';
import {Order} from "../../../../core/model/order.model";
import {AbstractBasicComponent} from "../../../../core/abstract/abstract-component";
@Component({ @Component({
selector: 'app-orders-list', selector: 'app-orders-list',
...@@ -11,8 +9,21 @@ import {AbstractBasicComponent} from "../../../../core/abstract/abstract-compone ...@@ -11,8 +9,21 @@ import {AbstractBasicComponent} from "../../../../core/abstract/abstract-compone
}) })
export class OrdersListComponent { export class OrdersListComponent {
@Input() orders!: Order[]; @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 { Component, OnInit } from '@angular/core';
import {Observable} from "rxjs"; import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import {Order} from "../../core/model/order.model"; import { Subject, concatMap, filter, take } from 'rxjs';
import {OrderService} from "../../core/service/order.service"; import { AbstractBasicComponent } from '../../core/abstract/abstract-component';
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({ @Component({
selector: 'app-index-page', selector: 'app-index-page',
templateUrl: './index-page.component.html', 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(); super();
} }
ngOnInit() { 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 { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router'; import { RouterModule, Routes } from '@angular/router';
import { IndexPageComponent } from './index-page/index-page.component'; import { IndexPageComponent } from './index-page/index-page.component';
import { OrderPageComponent } from './order-page/order-page.component';
const routes: Routes = [ const routes: Routes = [
{path: '', component: IndexPageComponent} { path: '', component: IndexPageComponent },
{ path: 'order', component: OrderPageComponent },
{ path: 'order/:id', component: OrderPageComponent },
]; ];
@NgModule({ @NgModule({
......
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { PagesRoutingModule } from './pages-routing.module'; import { SharedModule } from '../shared/shared.module';
import { IndexPageComponent } from './index-page/index-page.component'; 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 { 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({ @NgModule({
declarations: [IndexPageComponent, OrdersListComponent], declarations: [
imports: [CommonModule, PagesRoutingModule], IndexPageComponent,
OrderPageComponent,
OrdersListComponent,
GoodsListComponent,
OrderLineListComponent,
GoodsEditModalComponent,
AddProductModalComponent,
OrderEditModalComponent,
],
imports: [CommonModule, SharedModule, PagesRoutingModule],
}) })
export class PagesModule {} 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 { 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({ @NgModule({
declarations: [], declarations: [FormErrorsComponent, OrderFormComponent, GoodsFormComponent],
imports: [ 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 = { ...@@ -5,5 +5,6 @@ export const environment = {
API_PREFIX: '/api', API_PREFIX: '/api',
ORDER_URL: '/orders', ORDER_URL: '/orders',
ORDER_LINE_URL: '/order-line',
GOODS_URL: '/goods', GOODS_URL: '/goods',
}; };
...@@ -5,5 +5,6 @@ export const environment = { ...@@ -5,5 +5,6 @@ export const environment = {
API_PREFIX: '/api', API_PREFIX: '/api',
ORDER_URL: '/orders', ORDER_URL: '/orders',
ORDER_LINE_URL: '/order-line',
GOODS_URL: '/goods', GOODS_URL: '/goods',
}; };
/// <reference types="@angular/localize" />
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module'; import { AppModule } from './app/app.module';
......
/* You can add global styles to this file, and also import other style files */ /* 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 @@ ...@@ -3,7 +3,9 @@
"extends": "./tsconfig.json", "extends": "./tsconfig.json",
"compilerOptions": { "compilerOptions": {
"outDir": "./out-tsc/app", "outDir": "./out-tsc/app",
"types": [] "types": [
"@angular/localize"
]
}, },
"files": [ "files": [
"src/main.ts" "src/main.ts"
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
"sourceMap": true, "sourceMap": true,
"declaration": false, "declaration": false,
"experimentalDecorators": true, "experimentalDecorators": true,
"moduleResolution": "bundler", "moduleResolution": "node",
"importHelpers": true, "importHelpers": true,
"target": "ES2022", "target": "ES2022",
"module": "ES2022", "module": "ES2022",
......
...@@ -4,7 +4,8 @@ ...@@ -4,7 +4,8 @@
"compilerOptions": { "compilerOptions": {
"outDir": "./out-tsc/spec", "outDir": "./out-tsc/spec",
"types": [ "types": [
"jasmine" "jasmine",
"@angular/localize"
] ]
}, },
"include": [ "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