manual testing of api
This commit is contained in:
@@ -5,12 +5,14 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.autoconfigure.domain.EntityScan;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.data.jpa.convert.threeten.Jsr310JpaConverters;
|
||||
import pl.com.bottega.factory.shortages.prediction.calculation.CurrentStock;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import pl.com.bottega.factory.shortages.prediction.calculation.Stock;
|
||||
import pl.com.bottega.factory.warehouse.WarehouseService;
|
||||
|
||||
import java.time.Clock;
|
||||
|
||||
@SpringBootApplication
|
||||
@EnableScheduling
|
||||
@EntityScan(
|
||||
basePackageClasses = {AppConfiguration.class, Jsr310JpaConverters.class}
|
||||
)
|
||||
@@ -23,7 +25,7 @@ public class AppConfiguration {
|
||||
@Bean
|
||||
public WarehouseService warehouseService() {
|
||||
// mocked facade for external service
|
||||
return refNo -> new CurrentStock(0, 0);
|
||||
return refNo -> new Stock(1200, 700);
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -4,7 +4,6 @@ import lombok.AllArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
import pl.com.bottega.factory.delivery.planning.definition.DeliveryPlannerDefinitionDao;
|
||||
import pl.com.bottega.factory.delivery.planning.definition.DeliveryPlannerDefinitionEntity;
|
||||
import pl.com.bottega.factory.product.management.RefNoId;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
@@ -12,7 +11,7 @@ import static java.util.Optional.ofNullable;
|
||||
|
||||
@Component
|
||||
@AllArgsConstructor
|
||||
public class DeliveryAutoPlannerRepository {
|
||||
public class DeliveryAutoPlannerORMRepository {
|
||||
|
||||
DeliveryPlannerDefinitionDao dao;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package pl.com.bottega.factory.delivery.planning.definition;
|
||||
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
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;
|
||||
@@ -9,7 +8,7 @@ import org.springframework.stereotype.Repository;
|
||||
@Repository
|
||||
@RepositoryRestResource(
|
||||
path = "delivery-definitions", collectionResourceRel = "delivery-definitions")
|
||||
public interface DeliveryPlannerDefinitionDao extends CrudRepository<DeliveryPlannerDefinitionEntity, Long> {
|
||||
public interface DeliveryPlannerDefinitionDao extends JpaRepository<DeliveryPlannerDefinitionEntity, Long> {
|
||||
@RestResource(path = "refNos", rel = "refNos")
|
||||
DeliveryPlannerDefinitionEntity findByRefNo(@Param("refNo") String refNo);
|
||||
DeliveryPlannerDefinitionEntity findByRefNo(String refNo);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package pl.com.bottega.factory.delivery.planning.definition;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springframework.data.rest.core.annotation.HandleAfterCreate;
|
||||
import org.springframework.data.rest.core.annotation.HandleAfterSave;
|
||||
import org.springframework.data.rest.core.annotation.RepositoryEventHandler;
|
||||
import org.springframework.stereotype.Component;
|
||||
@@ -14,6 +15,7 @@ public class DeliveryPlannerDefinitionEventsMapping {
|
||||
private DeliveryForecastProjection projection;
|
||||
|
||||
@HandleAfterSave
|
||||
@HandleAfterCreate
|
||||
public void handle(DeliveryPlannerDefinitionEntity entity) {
|
||||
projection.handleDeliveryPlannerDefinitionChange(entity.getRefNo());
|
||||
}
|
||||
|
||||
@@ -3,22 +3,23 @@ package pl.com.bottega.factory.delivery.planning.projection;
|
||||
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
|
||||
import org.springframework.data.rest.core.annotation.RestResource;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import pl.com.bottega.tools.ProjectionDao;
|
||||
import pl.com.bottega.tools.ProjectionRepository;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Repository
|
||||
@RepositoryRestResource(path = "delivery-forecasts", collectionResourceRel = "delivery-forecasts")
|
||||
public interface DeliveryForecastDao extends ProjectionDao<DeliveryForecastEntity, Long> {
|
||||
public interface DeliveryForecastDao extends ProjectionRepository<DeliveryForecastEntity, Long> {
|
||||
|
||||
@RestResource(exported = false)
|
||||
List<DeliveryForecastEntity> findByRefNoAndDateGreaterThanEqual(String refNo, Instant instant);
|
||||
@RestResource(path = "refNos", rel = "refNos")
|
||||
List<DeliveryForecastEntity> findByRefNoAndTimeGreaterThanEqual(String refNo, LocalDateTime from);
|
||||
|
||||
@RestResource(exported = false)
|
||||
void deleteByRefNoAndDate(String refNo, LocalDate date);
|
||||
|
||||
@RestResource(exported = false)
|
||||
void deleteByRefNo(String refNo);
|
||||
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package pl.com.bottega.factory.delivery.planning.projection;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
import pl.com.bottega.factory.delivery.planning.DeliveryAutoPlanner;
|
||||
import pl.com.bottega.factory.delivery.planning.DeliveryAutoPlannerRepository;
|
||||
import pl.com.bottega.factory.delivery.planning.DeliveryAutoPlannerORMRepository;
|
||||
import pl.com.bottega.factory.demand.forecasting.Demand;
|
||||
import pl.com.bottega.factory.demand.forecasting.DemandEvents;
|
||||
import pl.com.bottega.factory.demand.forecasting.projection.CurrentDemandDao;
|
||||
@@ -20,7 +20,7 @@ public class DeliveryForecastProjection implements DemandEvents {
|
||||
private final Clock clock;
|
||||
private final DeliveryForecastDao forecastDao;
|
||||
private final CurrentDemandDao demandDao;
|
||||
private final DeliveryAutoPlannerRepository planners;
|
||||
private final DeliveryAutoPlannerORMRepository planners;
|
||||
|
||||
@Override
|
||||
public void emit(DemandedLevelsChanged event) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package pl.com.bottega.factory.demand.forecasting;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Component;
|
||||
import pl.com.bottega.factory.delivery.planning.projection.DeliveryForecastProjection;
|
||||
@@ -8,11 +9,12 @@ import pl.com.bottega.factory.shortages.prediction.ShortagePredictionEventsMappi
|
||||
|
||||
@Lazy
|
||||
@Component
|
||||
@AllArgsConstructor
|
||||
class DemandEventsMapping implements DemandEvents {
|
||||
|
||||
CurrentDemandProjection demands;
|
||||
DeliveryForecastProjection deliveries;
|
||||
ShortagePredictionEventsMapping predictions;
|
||||
private final CurrentDemandProjection demands;
|
||||
private final DeliveryForecastProjection deliveries;
|
||||
private final ShortagePredictionEventsMapping predictions;
|
||||
|
||||
@Override
|
||||
public void emit(DemandedLevelsChanged event) {
|
||||
|
||||
@@ -15,6 +15,7 @@ public class DemandService {
|
||||
public void process(Document document) {
|
||||
ProductDemand model = repository.get(document.getRefNo());
|
||||
model.process(document);
|
||||
repository.save(model);
|
||||
}
|
||||
|
||||
public void adjust(AdjustDemand adjustDemand) {
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package pl.com.bottega.factory.demand.forecasting.command;
|
||||
|
||||
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
|
||||
import org.springframework.data.rest.core.annotation.RestResource;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import pl.com.bottega.tools.CommandRepository;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
@Repository
|
||||
@RepositoryRestResource(path = "demand-adjustments", collectionResourceRel = "demand-adjustments")
|
||||
public interface DemandAdjustmentDao extends CommandRepository<DemandAdjustmentEntity, Long> {
|
||||
@RestResource(exported = false)
|
||||
void deleteByCleanAfterGreaterThanEqual(LocalDate date);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package pl.com.bottega.factory.demand.forecasting.command;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import pl.com.bottega.factory.demand.forecasting.AdjustDemand;
|
||||
import pl.com.bottega.tools.JsonConverter;
|
||||
|
||||
import javax.persistence.*;
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDate;
|
||||
|
||||
@Entity(name = "DemandAdjustment")
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
@EqualsAndHashCode(of = "id")
|
||||
public class DemandAdjustmentEntity implements Serializable {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||
private Long id;
|
||||
|
||||
@Column
|
||||
private String note;
|
||||
@Column
|
||||
private String customerRepresentative;
|
||||
@Column
|
||||
@Setter
|
||||
private LocalDate cleanAfter;
|
||||
|
||||
@Column(length = 4096)
|
||||
@Convert(converter = AdjustDemandAsJson.class)
|
||||
private AdjustDemand adjustment;
|
||||
|
||||
public static class AdjustDemandAsJson extends JsonConverter<AdjustDemand> {
|
||||
public AdjustDemandAsJson() {
|
||||
super(AdjustDemand.class);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package pl.com.bottega.factory.demand.forecasting.command;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.data.rest.core.annotation.HandleBeforeCreate;
|
||||
import org.springframework.data.rest.core.annotation.HandleBeforeSave;
|
||||
import org.springframework.data.rest.core.annotation.RepositoryEventHandler;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import pl.com.bottega.factory.demand.forecasting.DemandService;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.time.LocalDate;
|
||||
|
||||
@Component
|
||||
@AllArgsConstructor
|
||||
@RepositoryEventHandler
|
||||
public class Handler {
|
||||
|
||||
private final DemandService service;
|
||||
private final DemandAdjustmentDao adjustments;
|
||||
private final Clock clock;
|
||||
|
||||
@HandleBeforeCreate
|
||||
@HandleBeforeSave
|
||||
public void adjustDemand(DemandAdjustmentEntity resource) {
|
||||
LocalDate latest = resource.getAdjustment()
|
||||
.latestAdjustment()
|
||||
.orElse(LocalDate.now(clock));
|
||||
resource.setCleanAfter(latest.plusDays(7));
|
||||
service.adjust(resource.getAdjustment());
|
||||
}
|
||||
|
||||
@Scheduled(cron = "0 0 12 * * ?")
|
||||
@EventListener(ApplicationReadyEvent.class)
|
||||
@Transactional
|
||||
public void clean() {
|
||||
adjustments.deleteByCleanAfterGreaterThanEqual(LocalDate.now(clock));
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,5 @@ import java.util.List;
|
||||
@Repository
|
||||
@RestResource(exported = false)
|
||||
public interface DemandDao extends JpaRepository<DemandEntity, Long> {
|
||||
|
||||
List<DemandEntity> findByProductRefNoAndDateGreaterThanEqual(String refNo, LocalDate now);
|
||||
}
|
||||
|
||||
@@ -8,8 +8,5 @@ import pl.com.bottega.factory.demand.forecasting.ProductDemandEntity;
|
||||
@Repository
|
||||
@RestResource(exported = false)
|
||||
public interface ProductDemandDao extends JpaRepository<ProductDemandEntity, Long> {
|
||||
|
||||
ProductDemandEntity findById(Long id);
|
||||
|
||||
ProductDemandEntity findByRefNo(String refNo);
|
||||
}
|
||||
|
||||
@@ -3,16 +3,15 @@ package pl.com.bottega.factory.demand.forecasting.projection;
|
||||
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
|
||||
import org.springframework.data.rest.core.annotation.RestResource;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import pl.com.bottega.tools.ProjectionDao;
|
||||
import pl.com.bottega.tools.ProjectionRepository;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
@Repository
|
||||
@RepositoryRestResource(path = "demand-forecasts", collectionResourceRel = "demand-forecasts")
|
||||
public interface CurrentDemandDao extends ProjectionDao<CurrentDemandEntity, Long> {
|
||||
|
||||
@RestResource(exported = false)
|
||||
public interface CurrentDemandDao extends ProjectionRepository<CurrentDemandEntity, Long> {
|
||||
@RestResource(path = "refNos", rel = "refNos")
|
||||
List<CurrentDemandEntity> findByRefNoAndDateGreaterThanEqual(String refNo, LocalDate date);
|
||||
|
||||
@RestResource(exported = false)
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
package pl.com.bottega.factory.product.management;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.springframework.data.rest.core.annotation.HandleAfterCreate;
|
||||
import org.springframework.data.rest.core.annotation.HandleAfterSave;
|
||||
import org.springframework.data.rest.core.annotation.RepositoryEventHandler;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@AllArgsConstructor
|
||||
@RepositoryEventHandler
|
||||
public class ProductDescriptionEventsMapping {
|
||||
|
||||
@HandleAfterSave
|
||||
@HandleAfterCreate
|
||||
public void handle(ProductDescriptionEntity entity) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
package pl.com.bottega.factory.production.planning.projection;
|
||||
|
||||
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
|
||||
import org.springframework.data.rest.core.annotation.RestResource;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import pl.com.bottega.tools.ProjectionDao;
|
||||
import pl.com.bottega.tools.ProjectionRepository;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
@Repository
|
||||
@RepositoryRestResource(path = "production-outputs-daily", collectionResourceRel = "production-outputs-daily")
|
||||
public interface ProductionDailyOutputDao extends ProjectionDao<ProductionDailyOutputEntity, Long> {
|
||||
|
||||
public interface ProductionDailyOutputDao extends ProjectionRepository<ProductionDailyOutputEntity, Long> {
|
||||
@RestResource(path = "refNos", rel = "refNos")
|
||||
List<ProductionDailyOutputEntity> findByRefNoAndDateGreaterThanEqual(String refNo, LocalDate date);
|
||||
}
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
package pl.com.bottega.factory.production.planning.projection;
|
||||
|
||||
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
|
||||
import org.springframework.data.rest.core.annotation.RestResource;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import pl.com.bottega.tools.ProjectionDao;
|
||||
import pl.com.bottega.tools.ProjectionRepository;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Repository
|
||||
@RepositoryRestResource(path = "production-outputs", collectionResourceRel = "production-outputs")
|
||||
public interface ProductionOutputDao extends ProjectionDao<ProductionOutputEntity, Long> {
|
||||
|
||||
List<ProductionOutputEntity> findByRefNoAndStartGreaterThanEqual(String refNo, Instant instant);
|
||||
public interface ProductionOutputDao extends ProjectionRepository<ProductionOutputEntity, Long> {
|
||||
@RestResource(path = "refNos", rel = "refNos")
|
||||
List<ProductionOutputEntity> findByRefNoAndStartGreaterThanEqual(String refNo, LocalDateTime from);
|
||||
}
|
||||
|
||||
@@ -7,14 +7,12 @@ import pl.com.bottega.factory.demand.forecasting.DemandEvents;
|
||||
import pl.com.bottega.factory.shortages.prediction.monitoring.ShortagePredictionProcess;
|
||||
import pl.com.bottega.factory.shortages.prediction.monitoring.ShortagePredictionProcessRepository;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
@Lazy
|
||||
@Component
|
||||
@AllArgsConstructor
|
||||
public class ShortagePredictionEventsMapping implements DemandEvents {
|
||||
|
||||
private ShortagePredictionProcessRepository repository;
|
||||
private final ShortagePredictionProcessRepository repository;
|
||||
|
||||
@Override
|
||||
public void emit(DemandedLevelsChanged event) {
|
||||
|
||||
@@ -12,9 +12,9 @@ import pl.com.bottega.factory.warehouse.WarehouseService;
|
||||
import java.time.Clock;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static java.util.stream.Collectors.toMap;
|
||||
@@ -23,36 +23,36 @@ import static java.util.stream.Collectors.toMap;
|
||||
@AllArgsConstructor
|
||||
class ForecastORMProvider implements Forecasts {
|
||||
|
||||
private WarehouseService stocks;
|
||||
private DeliveryForecastDao demands;
|
||||
private ProductionOutputDao outputs;
|
||||
private Clock clock;
|
||||
private final WarehouseService stocks;
|
||||
private final DeliveryForecastDao demands;
|
||||
private final ProductionOutputDao outputs;
|
||||
private final Clock clock;
|
||||
|
||||
@Override
|
||||
public Forecast get(RefNoId refNo, int daysAhead) {
|
||||
CurrentStock stock = stocks.forRefNo(refNo);
|
||||
Stock stock = stocks.forRefNo(refNo);
|
||||
Instant now = Instant.now(clock);
|
||||
LocalDateTime time = now.atZone(clock.getZone()).toLocalDateTime();
|
||||
|
||||
Map<LocalDateTime, Long> demands = this.demands
|
||||
.findByRefNoAndDateGreaterThanEqual(refNo.getRefNo(), now).stream()
|
||||
.findByRefNoAndTimeGreaterThanEqual(refNo.getRefNo(), time).stream()
|
||||
.collect(toMap(
|
||||
DeliveryForecastEntity::getTime,
|
||||
DeliveryForecastEntity::getLevel
|
||||
));
|
||||
List<LocalDateTime> times = new ArrayList<>(demands.keySet());
|
||||
SortedSet<LocalDateTime> deliveryTimes = new TreeSet<>(demands.keySet());
|
||||
|
||||
Demands demand = new Demands(demands);
|
||||
|
||||
ProductionOutputs outputs = new ProductionForecast(
|
||||
this.outputs.findByRefNoAndStartGreaterThanEqual(refNo.getRefNo(), now).stream()
|
||||
this.outputs.findByRefNoAndStartGreaterThanEqual(refNo.getRefNo(), time).stream()
|
||||
.map(e -> new Item(
|
||||
e.getStart(),
|
||||
e.getDuration(),
|
||||
e.getPartsPerMinute()))
|
||||
.collect(Collectors.toList())
|
||||
).outputsInTimes(time, demands.keySet());
|
||||
).outputsInTimes(time, deliveryTimes);
|
||||
|
||||
return new Forecast(refNo.getRefNo(), time, times, stock, outputs, demand);
|
||||
return new Forecast(refNo.getRefNo(), time, deliveryTimes, stock, outputs, demand);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
package pl.com.bottega.factory.shortages.prediction.monitoring;
|
||||
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
|
||||
import org.springframework.data.rest.core.annotation.RestResource;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import pl.com.bottega.tools.ProjectionDao;
|
||||
import pl.com.bottega.tools.ProjectionRepository;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@Repository
|
||||
@RepositoryRestResource(path = "shortages", collectionResourceRel = "shortages")
|
||||
public interface ShortagesDao extends ProjectionDao<ShortagesEntity, Long> {
|
||||
public interface ShortagesDao extends ProjectionRepository<ShortagesEntity, Long> {
|
||||
@RestResource(path = "refNos", rel = "refNos")
|
||||
Optional<ShortagesEntity> findByRefNo(@Param("refNo") String refNo);
|
||||
Optional<ShortagesEntity> findByRefNo(String refNo);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package pl.com.bottega.factory.stock.forecast;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Builder;
|
||||
import lombok.Singular;
|
||||
import lombok.Value;
|
||||
@@ -8,6 +9,8 @@ import pl.com.bottega.factory.product.management.ProductDescription;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
import static com.fasterxml.jackson.annotation.JsonFormat.Shape.STRING;
|
||||
|
||||
@Value
|
||||
@Builder
|
||||
class StockForecast {
|
||||
@@ -19,6 +22,7 @@ class StockForecast {
|
||||
|
||||
@Value
|
||||
static class DailyForecast {
|
||||
@JsonFormat(shape = STRING, pattern = "yyyy-MM-dd")
|
||||
LocalDate date;
|
||||
long stock;
|
||||
long withLocked;
|
||||
|
||||
@@ -4,21 +4,21 @@ 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;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import pl.com.bottega.factory.product.management.RefNoId;
|
||||
|
||||
//@Controller
|
||||
//@RequestMapping("/stock/forecasts")
|
||||
@Controller
|
||||
@RequestMapping("stock-forecasts")
|
||||
@AllArgsConstructor
|
||||
class StockForecastController {
|
||||
|
||||
private final StockForecastQuery query;
|
||||
|
||||
@RequestMapping(value = "/{refNo}", method = RequestMethod.GET)
|
||||
@RequestMapping(value = "search/refNos", method = RequestMethod.GET)
|
||||
@Transactional(readOnly = true)
|
||||
ResponseEntity<StockForecast> get(@PathVariable("refNo") String refNo) {
|
||||
ResponseEntity<StockForecast> get(@RequestParam String refNo) {
|
||||
return ResponseEntity.ok(query.get(new RefNoId(refNo)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import pl.com.bottega.factory.product.management.ProductDescriptionEntity;
|
||||
import pl.com.bottega.factory.product.management.RefNoId;
|
||||
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.shortages.prediction.calculation.Stock;
|
||||
import pl.com.bottega.factory.stock.forecast.StockForecast.StockForecastBuilder;
|
||||
import pl.com.bottega.factory.warehouse.WarehouseService;
|
||||
|
||||
@@ -34,10 +34,12 @@ class StockForecastQuery {
|
||||
private final Clock clock;
|
||||
|
||||
StockForecast get(RefNoId refNo) {
|
||||
CurrentStock stock = stocks.forRefNo(refNo);
|
||||
Stock stock = stocks.forRefNo(refNo);
|
||||
LocalDate today = LocalDate.now(clock);
|
||||
return build(refNo, today, Optional.ofNullable(descriptions.findByRefNo(refNo.getRefNo()))
|
||||
.map(ProductDescriptionEntity::getDescription).orElse(null), stock,
|
||||
return build(refNo, today,
|
||||
Optional.ofNullable(descriptions.findByRefNo(refNo.getRefNo()))
|
||||
.map(ProductDescriptionEntity::getDescription).orElse(null),
|
||||
stock,
|
||||
this.demands
|
||||
.findByRefNoAndDateGreaterThanEqual(refNo.getRefNo(), today).stream()
|
||||
.collect(toMap(
|
||||
@@ -49,25 +51,31 @@ class StockForecastQuery {
|
||||
.collect(toMap(
|
||||
ProductionDailyOutputEntity::getDate,
|
||||
ProductionDailyOutputEntity::getOutput
|
||||
)));
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
private StockForecast build(RefNoId refNo, LocalDate today,
|
||||
ProductDescription description, CurrentStock stock,
|
||||
ProductDescription description, Stock 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)) {
|
||||
long demand = demands.getOrDefault(date, 0L);
|
||||
long output = outputs.getOrDefault(date, 0L);
|
||||
long withLocked = level + stock.getLocked();
|
||||
builder.forecast(
|
||||
new StockForecast.DailyForecast(
|
||||
date,
|
||||
level,
|
||||
level + stock.getLocked(),
|
||||
demands.getOrDefault(date, 0L),
|
||||
outputs.getOrDefault(date, 0L)
|
||||
));
|
||||
withLocked,
|
||||
demand,
|
||||
output
|
||||
)
|
||||
);
|
||||
level = level - demand + output;
|
||||
}
|
||||
return builder
|
||||
.refNo(refNo.getRefNo())
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package pl.com.bottega.factory.warehouse;
|
||||
|
||||
import pl.com.bottega.factory.product.management.RefNoId;
|
||||
import pl.com.bottega.factory.shortages.prediction.calculation.CurrentStock;
|
||||
import pl.com.bottega.factory.shortages.prediction.calculation.Stock;
|
||||
|
||||
public interface WarehouseService {
|
||||
CurrentStock forRefNo(RefNoId refNo);
|
||||
Stock forRefNo(RefNoId refNo);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package pl.com.bottega.tools;
|
||||
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.data.rest.core.annotation.RestResource;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@Repository
|
||||
public interface CommandRepository<T, ID extends Serializable> extends CrudRepository<T, ID> {
|
||||
|
||||
@Override
|
||||
@RestResource(exported = false)
|
||||
void delete(ID id);
|
||||
|
||||
@Override
|
||||
@RestResource(exported = false)
|
||||
void delete(T entity);
|
||||
|
||||
@Override
|
||||
@RestResource(exported = false)
|
||||
void delete(Iterable<? extends T> entities);
|
||||
|
||||
@Override
|
||||
@RestResource(exported = false)
|
||||
void deleteAll();
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import org.springframework.stereotype.Repository;
|
||||
import java.io.Serializable;
|
||||
|
||||
@Repository
|
||||
public interface ProjectionDao<T, ID extends Serializable> extends CrudRepository<T, ID> {
|
||||
public interface ProjectionRepository<T, ID extends Serializable> extends CrudRepository<T, ID> {
|
||||
|
||||
@Override
|
||||
@RestResource(exported = false)
|
||||
@@ -1,4 +1,11 @@
|
||||
|
||||
Aggregate Persistence:
|
||||
1) normalisation of aggregate entities (Daily Demand)
|
||||
2) optimistic force increment
|
||||
3) mapper (Daily Demand)
|
||||
4) wrapper (Product Demand)
|
||||
5) event sourcing (Product Demand)
|
||||
|
||||
Rest:
|
||||
1) crud for json document
|
||||
2) query calculated
|
||||
@@ -9,29 +16,3 @@ Rest:
|
||||
|
||||
6) Eventual consistency
|
||||
Read your own writes (E-tag, Expect, Retry-After)
|
||||
|
||||
|
||||
Aggregate Persistence:
|
||||
1) optimistic force increment
|
||||
2) normalisation of aggregate entities (Daily Demand)
|
||||
3) mapper (Daily Demand)
|
||||
4) wrapper (Product Demand)
|
||||
5) event sourcing (Product Demand)
|
||||
|
||||
http://pillopl.github.io/eventual-consistency-and-rest/
|
||||
https://github.com/pilloPl/orders-manager
|
||||
http://groovy-lang.org/json.html
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
vattenfall:
|
||||
|
||||
why status is active when card is EXPIRED?
|
||||
rfid: 04938C9AF74D80, point: EVB-P1552169 2 (EVB-P1552169_2) EXPIRED Revoked(authId=04938C9AF74D80, status=ACTIVE)
|
||||
|
||||
exceptions:
|
||||
null pointer
|
||||
more results for query single card
|
||||
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"refNo": "3009000",
|
||||
"definitions": {
|
||||
"AtDayStart": {
|
||||
"06:00": 1
|
||||
},
|
||||
"TillDayEnd": {
|
||||
"22:00": 1
|
||||
},
|
||||
"Twice": {
|
||||
"16:00": 0.5,
|
||||
"20:00": 0.5
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"refNo": "3009000",
|
||||
"definition": {
|
||||
"definitions": {
|
||||
"AtDayStart": {
|
||||
"06:00": 1.0
|
||||
},
|
||||
"TillDayEnd": {
|
||||
"22:00": 1.0
|
||||
},
|
||||
"Twice": {
|
||||
"16:00": 0.5,
|
||||
"20:00": 0.5
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"refNo": "3009000",
|
||||
"date": "2017-12-10",
|
||||
"time": "2017-12-10T18:00:00.000",
|
||||
"level": "3000"
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"note": "Customer moved demand for day earlier",
|
||||
"customerRepresentative": "Krysia z logistyki",
|
||||
"adjustment": {
|
||||
"refNo": "3009000",
|
||||
"adjustments": {
|
||||
"2017-12-11": {
|
||||
"demand": {
|
||||
"level": 2800,
|
||||
"schema": "AtDayStart"
|
||||
},
|
||||
"strong": true
|
||||
},
|
||||
"2017-12-12": {
|
||||
"demand": {
|
||||
"level": 0,
|
||||
"schema": "AtDayStart"
|
||||
},
|
||||
"strong": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"refNo": "3009000",
|
||||
"date": "2017-12-10",
|
||||
"level": "2800",
|
||||
"schema": "TillDayEnd"
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"refNo": "3009000",
|
||||
"description": {
|
||||
"matNum": "461952398951",
|
||||
"names": [
|
||||
"PROWAD.POJ.NA JARZ.ESSENT"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"refNo": "3009000",
|
||||
"date": "2017-12-10",
|
||||
"output": "1200"
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"refNo": "3009000",
|
||||
"start": "2017-12-10T06:15:00.000",
|
||||
"duration": "DT120M",
|
||||
"partsPerMinute": "10",
|
||||
"total": "1200"
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"refNo": "3009000",
|
||||
"shortages": {
|
||||
"refNo": "3009000",
|
||||
"lockedParts": 0,
|
||||
"found": "2017-12-10T18:21:07.293",
|
||||
"shortages": {
|
||||
"2018-12-11T00:00": 2800
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
{
|
||||
"refNo": "3009000",
|
||||
"description": {
|
||||
"matNum": "461952398951",
|
||||
"names": [
|
||||
"PROWAD.POJ.NA JARZ.ESSENT"
|
||||
]
|
||||
},
|
||||
"forecasts": [
|
||||
{
|
||||
"date": "2017-12-10",
|
||||
"stock": 1200,
|
||||
"withLocked": 1900,
|
||||
"demand": 0,
|
||||
"output": 0
|
||||
},
|
||||
{
|
||||
"date": "2017-12-11",
|
||||
"stock": 1200,
|
||||
"withLocked": 1900,
|
||||
"demand": 2800,
|
||||
"output": 0
|
||||
},
|
||||
{
|
||||
"date": "2017-12-12",
|
||||
"stock": -1600,
|
||||
"withLocked": -900,
|
||||
"demand": 0,
|
||||
"output": 0
|
||||
},
|
||||
{
|
||||
"date": "2017-12-13",
|
||||
"stock": -1600,
|
||||
"withLocked": -900,
|
||||
"demand": 0,
|
||||
"output": 0
|
||||
},
|
||||
{
|
||||
"date": "2017-12-14",
|
||||
"stock": -1600,
|
||||
"withLocked": -900,
|
||||
"demand": 0,
|
||||
"output": 0
|
||||
},
|
||||
{
|
||||
"date": "2017-12-15",
|
||||
"stock": -1600,
|
||||
"withLocked": -900,
|
||||
"demand": 0,
|
||||
"output": 0
|
||||
},
|
||||
{
|
||||
"date": "2017-12-16",
|
||||
"stock": -1600,
|
||||
"withLocked": -900,
|
||||
"demand": 0,
|
||||
"output": 0
|
||||
},
|
||||
{
|
||||
"date": "2017-12-17",
|
||||
"stock": -1600,
|
||||
"withLocked": -900,
|
||||
"demand": 0,
|
||||
"output": 0
|
||||
},
|
||||
{
|
||||
"date": "2017-12-18",
|
||||
"stock": -1600,
|
||||
"withLocked": -900,
|
||||
"demand": 0,
|
||||
"output": 0
|
||||
},
|
||||
{
|
||||
"date": "2017-12-19",
|
||||
"stock": -1600,
|
||||
"withLocked": -900,
|
||||
"demand": 0,
|
||||
"output": 0
|
||||
},
|
||||
{
|
||||
"date": "2017-12-20",
|
||||
"stock": -1600,
|
||||
"withLocked": -900,
|
||||
"demand": 0,
|
||||
"output": 0
|
||||
},
|
||||
{
|
||||
"date": "2017-12-21",
|
||||
"stock": -1600,
|
||||
"withLocked": -900,
|
||||
"demand": 0,
|
||||
"output": 0
|
||||
},
|
||||
{
|
||||
"date": "2017-12-22",
|
||||
"stock": -1600,
|
||||
"withLocked": -900,
|
||||
"demand": 0,
|
||||
"output": 0
|
||||
},
|
||||
{
|
||||
"date": "2017-12-23",
|
||||
"stock": -1600,
|
||||
"withLocked": -900,
|
||||
"demand": 0,
|
||||
"output": 0
|
||||
},
|
||||
{
|
||||
"date": "2017-12-24",
|
||||
"stock": -1600,
|
||||
"withLocked": -900,
|
||||
"demand": 0,
|
||||
"output": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -15,10 +15,10 @@ interface DeliveriesSuggestion {
|
||||
static DeliveriesSuggestion timesAndFractions(Map<LocalTime, Double> timesAndFractions) {
|
||||
return (refNo, date, demand) ->
|
||||
timesAndFractions.entrySet().stream()
|
||||
.map(e -> new Delivery(
|
||||
refNo,
|
||||
date.atTime(e.getKey()), ((long)((double)demand.getLevel() / e.getValue())))
|
||||
);
|
||||
.map(e -> new Delivery(
|
||||
refNo,
|
||||
date.atTime(e.getKey()), ((long) ((double) demand.getLevel() / e.getValue())))
|
||||
);
|
||||
}
|
||||
|
||||
Stream<Delivery> deliveriesFor(String refNo, LocalDate date, Demand demand);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package pl.com.bottega.factory.delivery.planning;
|
||||
|
||||
import pl.com.bottega.factory.demand.forecasting.Demand;
|
||||
import lombok.AllArgsConstructor;
|
||||
import pl.com.bottega.factory.demand.forecasting.Demand;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -3,8 +3,9 @@ package pl.com.bottega.factory.demand.forecasting;
|
||||
import lombok.Value;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
@Value
|
||||
@@ -12,15 +13,13 @@ public class AdjustDemand {
|
||||
String refNo;
|
||||
Map<LocalDate, Adjustment> adjustments;
|
||||
|
||||
public AdjustDemand(String refNo,
|
||||
Map<LocalDate, Adjustment> adjustments) {
|
||||
this.refNo = refNo;
|
||||
this.adjustments = Collections.unmodifiableMap(adjustments);
|
||||
}
|
||||
|
||||
public void forEachStartingFrom(LocalDate date, BiConsumer<LocalDate, Adjustment> f) {
|
||||
adjustments.entrySet().stream()
|
||||
.filter(e -> !e.getKey().isBefore(date))
|
||||
.forEach(e -> f.accept(e.getKey(), e.getValue()));
|
||||
}
|
||||
|
||||
public Optional<LocalDate> latestAdjustment() {
|
||||
return adjustments.keySet().stream().max(Comparator.naturalOrder());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,25 +4,25 @@ import lombok.AllArgsConstructor;
|
||||
import pl.com.bottega.factory.shortages.prediction.Shortages;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.SortedSet;
|
||||
|
||||
@AllArgsConstructor
|
||||
public class Forecast {
|
||||
|
||||
String refNo;
|
||||
LocalDateTime created;
|
||||
List<LocalDateTime> times;
|
||||
CurrentStock stock;
|
||||
ProductionOutputs outputs;
|
||||
Demands demands;
|
||||
private final String refNo;
|
||||
private final LocalDateTime created;
|
||||
private final SortedSet<LocalDateTime> deliveryTimes;
|
||||
private final Stock stock;
|
||||
private final ProductionOutputs outputs;
|
||||
private final Demands demands;
|
||||
|
||||
public Optional<Shortages> findShortages() {
|
||||
long level = stock.getLevel();
|
||||
|
||||
Shortages.Builder found = Shortages.builder(refNo, stock.getLocked(), created);
|
||||
LocalDateTime lastTime = created;
|
||||
for (LocalDateTime time : times) {
|
||||
for (LocalDateTime time : deliveryTimes) {
|
||||
long demand = demands.get(time);
|
||||
long produced = outputs.getOutput(lastTime, time);
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ package pl.com.bottega.factory.shortages.prediction.calculation;
|
||||
import lombok.Value;
|
||||
|
||||
@Value
|
||||
public class CurrentStock {
|
||||
public class Stock {
|
||||
long level;
|
||||
long locked;
|
||||
}
|
||||
@@ -12,12 +12,12 @@ trait ShortagesCalculationAssemblerTrait {
|
||||
String refNo = "3009000"
|
||||
Set<LocalDateTime> times
|
||||
|
||||
Forecasts forecastProvider(CurrentStock stock, Demands demands, ProductionOutputs outputs) {
|
||||
Forecasts forecastProvider(Stock stock, Demands demands, ProductionOutputs outputs) {
|
||||
def forecast = forecast(stock, demands, outputs)
|
||||
return { RefNoId refNo, int daysAhead -> forecast } as Forecasts
|
||||
}
|
||||
|
||||
Forecast forecast(CurrentStock stock, Demands demands, ProductionOutputs outputs) {
|
||||
Forecast forecast(Stock stock, Demands demands, ProductionOutputs outputs) {
|
||||
new Forecast(refNo, now, times as List, stock, outputs, demands)
|
||||
}
|
||||
|
||||
@@ -45,12 +45,12 @@ trait ShortagesCalculationAssemblerTrait {
|
||||
new Demands(demands)
|
||||
}
|
||||
|
||||
CurrentStock stock(long levels) {
|
||||
new CurrentStock(levels, 0)
|
||||
Stock stock(long levels) {
|
||||
new Stock(levels, 0)
|
||||
}
|
||||
|
||||
CurrentStock stock(long level, long locked) {
|
||||
new CurrentStock(level, locked)
|
||||
Stock stock(long level, long locked) {
|
||||
new Stock(level, locked)
|
||||
}
|
||||
|
||||
Optional<Shortages> noShortages() {
|
||||
|
||||
Reference in New Issue
Block a user