stock forecast query exposed
This commit is contained in:
@@ -6,7 +6,6 @@ import java.util.List;
|
||||
|
||||
@Value
|
||||
public class ProductDescription {
|
||||
String refNo;
|
||||
String matNum;
|
||||
List<String> names;
|
||||
}
|
||||
|
||||
@@ -2,12 +2,21 @@ package pl.com.bottega.factory.product.management;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
|
||||
import org.springframework.data.rest.core.annotation.RestResource;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@Repository
|
||||
|
||||
@RepositoryRestResource(
|
||||
path = "product/management/descriptions",
|
||||
collectionResourceRel = "descriptions of products")
|
||||
public interface ProductDescriptionDao extends JpaRepository<ProductDescriptionEntity, Long> {
|
||||
public interface ProductDescriptionDao extends JpaRepository<ProductDescriptionEntity, String> {
|
||||
|
||||
@RestResource(exported = false)
|
||||
default Optional<ProductDescription> description(String refNo) {
|
||||
return Optional.ofNullable(findOne(refNo))
|
||||
.map(ProductDescriptionEntity::getDescription);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,19 +11,19 @@ import javax.persistence.*;
|
||||
@Entity(name = "ProductDescription")
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@EqualsAndHashCode(of = "id")
|
||||
@EqualsAndHashCode(of = "refNo")
|
||||
public class ProductDescriptionEntity {
|
||||
|
||||
@Id
|
||||
@Column
|
||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||
private Long id;
|
||||
private String refNo;
|
||||
|
||||
@Column
|
||||
@Convert(converter = DescriptionAsJson.class)
|
||||
ProductDescription description;
|
||||
|
||||
public ProductDescriptionEntity(ProductDescription description) {
|
||||
public ProductDescriptionEntity(String refNo, ProductDescription description) {
|
||||
this.refNo = refNo;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package pl.com.bottega.factory.production.planning.projection;
|
||||
|
||||
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import pl.com.bottega.tools.ProjectionDao;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
@Repository
|
||||
@RepositoryRestResource(
|
||||
path = "production/planning/outputs/daily",
|
||||
collectionResourceRel = "forecast of production daily outputs")
|
||||
public interface ProductionDailyOutputDao extends ProjectionDao<ProductionDailyOutputEntity, Long> {
|
||||
|
||||
List<ProductionDailyOutputEntity> findByRefNoAndDateGreaterThanEqual(String refNo, LocalDate date);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package pl.com.bottega.factory.production.planning.projection;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import javax.persistence.*;
|
||||
import java.time.LocalDate;
|
||||
|
||||
@Entity(name = "ProductionDailyOutput")
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
@EqualsAndHashCode(of = "id")
|
||||
public class ProductionDailyOutputEntity {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||
private Long id;
|
||||
@Column
|
||||
private String refNo;
|
||||
@Column
|
||||
private LocalDate date;
|
||||
@Column
|
||||
private long output;
|
||||
|
||||
public ProductionDailyOutputEntity(String refNo, LocalDate date, long output) {
|
||||
this.refNo = refNo;
|
||||
this.date = date;
|
||||
this.output = output;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package pl.com.bottega.factory.stock.forecast;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Singular;
|
||||
import lombok.Value;
|
||||
import pl.com.bottega.factory.product.management.ProductDescription;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
@Value
|
||||
@Builder
|
||||
class StockForecast {
|
||||
|
||||
String refNo;
|
||||
ProductDescription description;
|
||||
@Singular
|
||||
List<DailyForecast> forecasts;
|
||||
|
||||
@Value
|
||||
static class DailyForecast {
|
||||
LocalDate date;
|
||||
long stock;
|
||||
long withLocked;
|
||||
long demand;
|
||||
long output;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package pl.com.bottega.factory.stock.forecast;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
|
||||
@Controller
|
||||
@RequestMapping("/stock/forecasts")
|
||||
@AllArgsConstructor
|
||||
class StockForecastController {
|
||||
|
||||
private final StockForecastQuery query;
|
||||
|
||||
@RequestMapping(value = "/{refNo}", method = RequestMethod.GET)
|
||||
@Transactional(readOnly = true)
|
||||
ResponseEntity<StockForecast> get(@PathVariable("refNo") String refNo) {
|
||||
return ResponseEntity.ok(query.get(refNo));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package pl.com.bottega.factory.stock.forecast;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import pl.com.bottega.factory.demand.forecasting.projection.CurrentDemandDao;
|
||||
import pl.com.bottega.factory.demand.forecasting.projection.CurrentDemandEntity;
|
||||
import pl.com.bottega.factory.product.management.ProductDescription;
|
||||
import pl.com.bottega.factory.product.management.ProductDescriptionDao;
|
||||
import pl.com.bottega.factory.product.management.ProductDescriptionEntity;
|
||||
import pl.com.bottega.factory.production.planning.projection.ProductionDailyOutputDao;
|
||||
import pl.com.bottega.factory.production.planning.projection.ProductionDailyOutputEntity;
|
||||
import pl.com.bottega.factory.shortages.prediction.calculation.CurrentStock;
|
||||
import pl.com.bottega.factory.stock.forecast.StockForecast.StockForecastBuilder;
|
||||
import pl.com.bottega.factory.warehouse.WarehouseService;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import static java.util.stream.Collectors.toMap;
|
||||
|
||||
@Component
|
||||
@Transactional(readOnly = true)
|
||||
@AllArgsConstructor
|
||||
class StockForecastQuery {
|
||||
|
||||
private final WarehouseService stocks;
|
||||
private final CurrentDemandDao demands;
|
||||
private final ProductionDailyOutputDao outputs;
|
||||
private final ProductDescriptionDao descriptions;
|
||||
private final Clock clock;
|
||||
|
||||
StockForecast get(String refNo) {
|
||||
CurrentStock stock = stocks.forRefNo(refNo);
|
||||
LocalDate today = LocalDate.now(clock);
|
||||
return build(refNo, today, Optional.ofNullable(descriptions.findOne(refNo))
|
||||
.map(ProductDescriptionEntity::getDescription).orElse(null), stock,
|
||||
this.demands
|
||||
.findByRefNoAndDateGreaterThanEqual(refNo, today).stream()
|
||||
.collect(toMap(
|
||||
CurrentDemandEntity::getDate,
|
||||
CurrentDemandEntity::getLevel
|
||||
)),
|
||||
this.outputs
|
||||
.findByRefNoAndDateGreaterThanEqual(refNo, today).stream()
|
||||
.collect(toMap(
|
||||
ProductionDailyOutputEntity::getDate,
|
||||
ProductionDailyOutputEntity::getOutput
|
||||
)));
|
||||
}
|
||||
|
||||
private StockForecast build(String refNo, LocalDate today,
|
||||
ProductDescription description, CurrentStock stock,
|
||||
Map<LocalDate, Long> demands,
|
||||
Map<LocalDate, Long> outputs) {
|
||||
LocalDate stopAtDay = today.plusDays(15);
|
||||
long level = stock.getLevel();
|
||||
StockForecastBuilder builder = StockForecast.builder();
|
||||
for (LocalDate date = today; date.isBefore(stopAtDay); date = date.plusDays(1)) {
|
||||
builder.forecast(
|
||||
new StockForecast.DailyForecast(
|
||||
date,
|
||||
level,
|
||||
level + stock.getLocked(),
|
||||
demands.getOrDefault(date, 0L),
|
||||
outputs.getOrDefault(date, 0L)
|
||||
));
|
||||
}
|
||||
return builder
|
||||
.refNo(refNo)
|
||||
.description(description)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -18,8 +18,8 @@ class ProductDescriptionPersistenceTest extends Specification {
|
||||
|
||||
def "verify access to ProductDescription data"() {
|
||||
given:
|
||||
dao.save(new ProductDescriptionEntity(
|
||||
new ProductDescription("3009000", "461952398951", singletonList("PROWAD.POJ.NA JARZ.ESSENT"))))
|
||||
dao.save(new ProductDescriptionEntity("3009000",
|
||||
new ProductDescription("461952398951", singletonList("PROWAD.POJ.NA JARZ.ESSENT"))))
|
||||
|
||||
when:
|
||||
def entities = dao.findAll()
|
||||
@@ -27,6 +27,6 @@ class ProductDescriptionPersistenceTest extends Specification {
|
||||
then:
|
||||
entities.size() == 1
|
||||
entities.get(0).description ==
|
||||
new ProductDescription("3009000", "461952398951", singletonList("PROWAD.POJ.NA JARZ.ESSENT"))
|
||||
new ProductDescription("461952398951", singletonList("PROWAD.POJ.NA JARZ.ESSENT"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ class ShortagePredictionProcessORMRepositoryTest extends Specification {
|
||||
ShortagePredictionProcessORMRepository repository
|
||||
|
||||
def setup() {
|
||||
dao.deleteAllInBatch()
|
||||
dao.deleteAll()
|
||||
repository = new ShortagePredictionProcessORMRepository(
|
||||
dao, forecasts, notifications
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user