From 39a810e4b3b81eb2c25e6867bae1e99ae4a14da9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Michaluk?= Date: Mon, 4 Dec 2017 16:14:10 +0100 Subject: [PATCH] ProductDemand persistence + draft of events projection --- .../demand/forecasting/DemandEntity.java | 74 +++++++++++++++++++ .../forecasting/DemandEventsMapping.java | 6 ++ .../demand/forecasting/DemandRepository.java | 63 +++++++++++++++- .../demand/forecasting/DemandService.java | 6 +- .../forecasting/ProductDemandEntity.java | 27 +++++++ .../projection/CurrentDemandDao.java | 11 +++ .../projection/CurrentDemandEntity.java | 21 ++++++ .../projection/CurrentDemandsProjection.java | 12 +++ .../projection/DeliveryForecastDao.java | 14 ++++ .../projection/DeliveryForecastEntity.java | 18 +++++ .../forecasting/DemandRepositoryTest.groovy | 9 +-- 11 files changed, 250 insertions(+), 11 deletions(-) create mode 100644 app-monolith/src/main/java/pl/com/bottega/factory/demand/forecasting/projection/CurrentDemandDao.java create mode 100644 app-monolith/src/main/java/pl/com/bottega/factory/demand/forecasting/projection/CurrentDemandEntity.java create mode 100644 app-monolith/src/main/java/pl/com/bottega/factory/demand/forecasting/projection/CurrentDemandsProjection.java create mode 100644 app-monolith/src/main/java/pl/com/bottega/factory/demand/forecasting/projection/DeliveryForecastDao.java create mode 100644 app-monolith/src/main/java/pl/com/bottega/factory/demand/forecasting/projection/DeliveryForecastEntity.java diff --git a/app-monolith/src/main/java/pl/com/bottega/factory/demand/forecasting/DemandEntity.java b/app-monolith/src/main/java/pl/com/bottega/factory/demand/forecasting/DemandEntity.java index 4fdac11..daa36e3 100644 --- a/app-monolith/src/main/java/pl/com/bottega/factory/demand/forecasting/DemandEntity.java +++ b/app-monolith/src/main/java/pl/com/bottega/factory/demand/forecasting/DemandEntity.java @@ -2,7 +2,9 @@ package pl.com.bottega.factory.demand.forecasting; import lombok.Data; import lombok.EqualsAndHashCode; +import lombok.Getter; import lombok.NoArgsConstructor; +import pl.com.bottega.tools.TechnicalId; import javax.persistence.*; import java.time.LocalDate; @@ -22,4 +24,76 @@ public class DemandEntity { @Column LocalDate date; + @Column + Long level; + @Column + Demand.Schema schema; + + @Column + Long adjustmentLevel; + @Column + Demand.Schema adjustmentSchema; + @Column + boolean adjustmentStrong; + + DemandEntity(ProductDemandEntity product, LocalDate date) { + this.product = product; + this.date = date; + } + + static Demand getDemand(DemandEntity entity) { + return entity == null || entity.level == null + ? null + : Demand.of(entity.level, entity.schema); + } + + static Adjustment getAdjustment(DemandEntity entity) { + return entity == null || entity.adjustmentLevel == null + ? null + : new Adjustment( + Demand.of(entity.adjustmentLevel, entity.adjustmentSchema), + entity.adjustmentStrong + ); + } + + void setDemand(Demand demand) { + if (demand == null) { + setLevel(null); + setSchema(null); + } else { + setLevel(demand.getLevel()); + setSchema(demand.getSchema()); + } + } + + void setAdjustment(Adjustment adjustment) { + if (adjustment == null) { + setAdjustmentLevel(null); + setAdjustmentSchema(null); + setAdjustmentStrong(false); + } else { + setAdjustmentLevel(adjustment.getDemand().getLevel()); + setAdjustmentSchema(adjustment.getDemand().getSchema()); + setAdjustmentStrong(adjustment.isStrong()); + } + } + + DemandEntityId createId() { + return new DemandEntityId(product.getRefNo(), date, id); + } + + @Getter + static class DemandEntityId extends DailyId implements TechnicalId { + + Long id; + + DemandEntityId(String refNo, LocalDate date) { + super(refNo, date); + } + + DemandEntityId(String refNo, LocalDate date, Long id) { + super(refNo, date); + this.id = id; + } + } } diff --git a/app-monolith/src/main/java/pl/com/bottega/factory/demand/forecasting/DemandEventsMapping.java b/app-monolith/src/main/java/pl/com/bottega/factory/demand/forecasting/DemandEventsMapping.java index e5a7e46..7223d4e 100644 --- a/app-monolith/src/main/java/pl/com/bottega/factory/demand/forecasting/DemandEventsMapping.java +++ b/app-monolith/src/main/java/pl/com/bottega/factory/demand/forecasting/DemandEventsMapping.java @@ -2,12 +2,18 @@ package pl.com.bottega.factory.demand.forecasting; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; +import pl.com.bottega.factory.demand.forecasting.projection.CurrentDemandsProjection; @Lazy @Component class DemandEventsMapping implements DemandEvents { + CurrentDemandsProjection demands; + //ShortagePredictionMapping predictions; + @Override public void emit(DemandedLevelsChanged event) { + demands.emit(event); + //predictions.emit(event); } } diff --git a/app-monolith/src/main/java/pl/com/bottega/factory/demand/forecasting/DemandRepository.java b/app-monolith/src/main/java/pl/com/bottega/factory/demand/forecasting/DemandRepository.java index 304fcf7..657eebd 100644 --- a/app-monolith/src/main/java/pl/com/bottega/factory/demand/forecasting/DemandRepository.java +++ b/app-monolith/src/main/java/pl/com/bottega/factory/demand/forecasting/DemandRepository.java @@ -2,10 +2,22 @@ package pl.com.bottega.factory.demand.forecasting; import lombok.AllArgsConstructor; import org.springframework.stereotype.Component; +import pl.com.bottega.factory.demand.forecasting.DailyDemand.DemandUpdated; +import pl.com.bottega.factory.demand.forecasting.DemandEntity.DemandEntityId; import pl.com.bottega.factory.demand.forecasting.persistence.DemandDao; import pl.com.bottega.factory.demand.forecasting.persistence.ProductDemandDao; +import pl.com.bottega.factory.product.management.RefNoId; +import pl.com.bottega.tools.TechnicalId; +import javax.persistence.EntityManager; +import javax.persistence.LockModeType; import java.time.Clock; +import java.time.LocalDate; +import java.util.Map; +import java.util.function.Function; + +import static java.util.Optional.ofNullable; +import static java.util.stream.Collectors.toMap; @Component @AllArgsConstructor @@ -13,13 +25,62 @@ class DemandRepository { private Clock clock; private DemandEventsMapping events; + private EntityManager em; private ProductDemandDao rootDao; private DemandDao demandDao; ProductDemand get(String refNo) { - return null; + ProductDemandEntity root = rootDao.findByRefNo(refNo); + RefNoId id = root.createId(); + + Map data = + demandDao.findByProductRefNoAndDateGreaterThanEqual(refNo, LocalDate.now(clock)).stream() + .collect(toMap( + DemandEntity::getDate, + Function.identity() + )); + + Demands demands = new Demands(); + demands.fetch = date -> map(refNo, date, data, demands); + + return new ProductDemand(id, demands, clock, events); + } + + private DailyDemand map(String refNo, LocalDate date, + Map data, + Demands demands) { + return ofNullable(data.get(date)) + .map(entity -> new DailyDemand( + entity.createId(), + demands, + DemandEntity.getDemand(entity), + DemandEntity.getAdjustment(entity))) + .orElseGet(() -> new DailyDemand( + new DemandEntityId(refNo, date), + demands, + null, + null + )); } void save(ProductDemand model) { + ProductDemandEntity root = rootDao.findOne(TechnicalId.get(model.id)); + if (model.demands.getUpdates().size() > 0) { + em.lock(root, LockModeType.OPTIMISTIC_FORCE_INCREMENT); + } + for (DemandUpdated updated : model.demands.getUpdates()) { + DemandEntity entity; + if (TechnicalId.isPersisted(updated.getId())) { + entity = demandDao.getOne(TechnicalId.get(updated.getId())); + } else { + entity = new DemandEntity(root, updated.getId().getDate()); + } + entity.setDemand(updated.getDocumented()); + entity.setAdjustment(updated.getAdjustment()); + + if (!TechnicalId.isPersisted(updated.getId())) { + demandDao.save(entity); + } + } } } diff --git a/app-monolith/src/main/java/pl/com/bottega/factory/demand/forecasting/DemandService.java b/app-monolith/src/main/java/pl/com/bottega/factory/demand/forecasting/DemandService.java index db35df4..e5dc0f3 100644 --- a/app-monolith/src/main/java/pl/com/bottega/factory/demand/forecasting/DemandService.java +++ b/app-monolith/src/main/java/pl/com/bottega/factory/demand/forecasting/DemandService.java @@ -1,11 +1,7 @@ package pl.com.bottega.factory.demand.forecasting; -import org.springframework.stereotype.Service; - import javax.transaction.Transactional; -@Service -@Transactional public class DemandService { private DemandRepository repository; @@ -13,9 +9,9 @@ public class DemandService { public void process(Document document) { ProductDemand model = repository.get(document.getRefNo()); model.process(document); - repository.save(model); } + @Transactional public void adjust(AdjustDemand adjustDemand) { ProductDemand model = repository.get(adjustDemand.getRefNo()); model.adjust(adjustDemand); diff --git a/app-monolith/src/main/java/pl/com/bottega/factory/demand/forecasting/ProductDemandEntity.java b/app-monolith/src/main/java/pl/com/bottega/factory/demand/forecasting/ProductDemandEntity.java index 60fccbc..516911a 100644 --- a/app-monolith/src/main/java/pl/com/bottega/factory/demand/forecasting/ProductDemandEntity.java +++ b/app-monolith/src/main/java/pl/com/bottega/factory/demand/forecasting/ProductDemandEntity.java @@ -2,7 +2,10 @@ package pl.com.bottega.factory.demand.forecasting; import lombok.Data; import lombok.EqualsAndHashCode; +import lombok.Getter; import lombok.NoArgsConstructor; +import pl.com.bottega.factory.product.management.RefNoId; +import pl.com.bottega.tools.TechnicalId; import javax.persistence.*; @@ -20,4 +23,28 @@ public class ProductDemandEntity { @Column String refNo; + public ProductDemandEntity(String refNo) { + this.refNo = refNo; + } + + ProductDemandEntityId createId() { + return new ProductDemandEntityId(refNo, id, version); + } + + @Getter + static class ProductDemandEntityId extends RefNoId implements TechnicalId { + + Long id; + Long version; + + ProductDemandEntityId(String refNo) { + super(refNo); + } + + ProductDemandEntityId(String refNo, long id, Long version) { + super(refNo); + this.id = id; + this.version = version; + } + } } diff --git a/app-monolith/src/main/java/pl/com/bottega/factory/demand/forecasting/projection/CurrentDemandDao.java b/app-monolith/src/main/java/pl/com/bottega/factory/demand/forecasting/projection/CurrentDemandDao.java new file mode 100644 index 0000000..74b349f --- /dev/null +++ b/app-monolith/src/main/java/pl/com/bottega/factory/demand/forecasting/projection/CurrentDemandDao.java @@ -0,0 +1,11 @@ +package pl.com.bottega.factory.demand.forecasting.projection; + +import java.time.Instant; +import java.util.List; + +public interface CurrentDemandDao { + + void save(CurrentDemandEntity entity); + + List findRefNoFromDate(String refNo, Instant now); +} diff --git a/app-monolith/src/main/java/pl/com/bottega/factory/demand/forecasting/projection/CurrentDemandEntity.java b/app-monolith/src/main/java/pl/com/bottega/factory/demand/forecasting/projection/CurrentDemandEntity.java new file mode 100644 index 0000000..4adf8a6 --- /dev/null +++ b/app-monolith/src/main/java/pl/com/bottega/factory/demand/forecasting/projection/CurrentDemandEntity.java @@ -0,0 +1,21 @@ +package pl.com.bottega.factory.demand.forecasting.projection; + +import pl.com.bottega.factory.demand.forecasting.Demand; +import lombok.Data; + +import java.time.LocalDate; + +@Data +public class CurrentDemandEntity { + private final String refNo; + private final LocalDate date; + private final long level; + private final Demand.Schema schema; + + public CurrentDemandEntity(String refNo, LocalDate date, long level, Demand.Schema schema) { + this.refNo = refNo; + this.date = date; + this.level = level; + this.schema = schema; + } +} diff --git a/app-monolith/src/main/java/pl/com/bottega/factory/demand/forecasting/projection/CurrentDemandsProjection.java b/app-monolith/src/main/java/pl/com/bottega/factory/demand/forecasting/projection/CurrentDemandsProjection.java new file mode 100644 index 0000000..9f73446 --- /dev/null +++ b/app-monolith/src/main/java/pl/com/bottega/factory/demand/forecasting/projection/CurrentDemandsProjection.java @@ -0,0 +1,12 @@ +package pl.com.bottega.factory.demand.forecasting.projection; + +import lombok.AllArgsConstructor; +import pl.com.bottega.factory.demand.forecasting.DemandEvents; + +@AllArgsConstructor +public class CurrentDemandsProjection implements DemandEvents { + + @Override + public void emit(DemandedLevelsChanged event) { + } +} diff --git a/app-monolith/src/main/java/pl/com/bottega/factory/demand/forecasting/projection/DeliveryForecastDao.java b/app-monolith/src/main/java/pl/com/bottega/factory/demand/forecasting/projection/DeliveryForecastDao.java new file mode 100644 index 0000000..1de21e3 --- /dev/null +++ b/app-monolith/src/main/java/pl/com/bottega/factory/demand/forecasting/projection/DeliveryForecastDao.java @@ -0,0 +1,14 @@ +package pl.com.bottega.factory.demand.forecasting.projection; + +import java.time.Instant; +import java.time.LocalDate; +import java.util.List; + +public interface DeliveryForecastDao { + + void save(DeliveryForecastEntity entity); + + List findRefNoFrom(String refNo, Instant instant, int daysAhead); + + void delete(String refNo, LocalDate date); +} diff --git a/app-monolith/src/main/java/pl/com/bottega/factory/demand/forecasting/projection/DeliveryForecastEntity.java b/app-monolith/src/main/java/pl/com/bottega/factory/demand/forecasting/projection/DeliveryForecastEntity.java new file mode 100644 index 0000000..441ce8e --- /dev/null +++ b/app-monolith/src/main/java/pl/com/bottega/factory/demand/forecasting/projection/DeliveryForecastEntity.java @@ -0,0 +1,18 @@ +package pl.com.bottega.factory.demand.forecasting.projection; + +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +public class DeliveryForecastEntity { + private final String refNo; + private final LocalDateTime time; + private final long level; + + public DeliveryForecastEntity(String refNo, LocalDateTime time, long level) { + this.refNo = refNo; + this.time = time; + this.level = level; + } +} diff --git a/app-monolith/src/test/groovy/pl/com/bottega/factory/demand/forecasting/DemandRepositoryTest.groovy b/app-monolith/src/test/groovy/pl/com/bottega/factory/demand/forecasting/DemandRepositoryTest.groovy index 79cb027..fc336b3 100644 --- a/app-monolith/src/test/groovy/pl/com/bottega/factory/demand/forecasting/DemandRepositoryTest.groovy +++ b/app-monolith/src/test/groovy/pl/com/bottega/factory/demand/forecasting/DemandRepositoryTest.groovy @@ -5,9 +5,9 @@ import org.springframework.boot.test.context.SpringBootTest import org.springframework.test.annotation.Commit import pl.com.bottega.factory.demand.forecasting.persistence.DemandDao import pl.com.bottega.factory.demand.forecasting.persistence.ProductDemandDao -import spock.lang.PendingFeature import spock.lang.Specification +import javax.persistence.EntityManager import javax.transaction.Transactional import java.time.Clock import java.time.Instant @@ -22,6 +22,8 @@ class DemandRepositoryTest extends Specification { def clock = Clock.fixed(Instant.now(), ZoneId.systemDefault()) def events = Mock(DemandEventsMapping) @Autowired + EntityManager em + @Autowired ProductDemandDao rootDao @Autowired DemandDao demandDao @@ -33,10 +35,9 @@ class DemandRepositoryTest extends Specification { def setup() { demandDao.deleteAllInBatch() rootDao.deleteAllInBatch() - repository = new DemandRepository(clock, events, rootDao, demandDao) + repository = new DemandRepository(clock, events, em, rootDao, demandDao) } - @PendingFeature def "persists new demand"() { given: rootDao.save(new ProductDemandEntity("3009000")) @@ -52,7 +53,6 @@ class DemandRepositoryTest extends Specification { demandDao.findAll().size() == 1 } - @PendingFeature def "updates existing demand"() { given: def root = rootDao.save(new ProductDemandEntity("3009000")) @@ -73,7 +73,6 @@ class DemandRepositoryTest extends Specification { demand.every { it.getAdjustmentLevel() == 2000 } } - @PendingFeature def "doesn't fetch historical data"() { given: def root = rootDao.save(new ProductDemandEntity("3009000"))