diff --git a/app-monolith/src/main/java/pl/com/bottega/factory/delivery/planning/definition/DeliveryPlannerDefinitionEntity.java b/app-monolith/src/main/java/pl/com/bottega/factory/delivery/planning/definition/DeliveryPlannerDefinitionEntity.java index 407d869..95ac55d 100644 --- a/app-monolith/src/main/java/pl/com/bottega/factory/delivery/planning/definition/DeliveryPlannerDefinitionEntity.java +++ b/app-monolith/src/main/java/pl/com/bottega/factory/delivery/planning/definition/DeliveryPlannerDefinitionEntity.java @@ -19,7 +19,7 @@ public class DeliveryPlannerDefinitionEntity { @Column @Convert(converter = DescriptionAsJson.class) - DeliveryPlannerDefinition definition; + private DeliveryPlannerDefinition definition; public DeliveryPlannerDefinitionEntity(String refNo, DeliveryPlannerDefinition definition) { this.refNo = refNo; diff --git a/app-monolith/src/main/java/pl/com/bottega/factory/delivery/planning/projection/DeliveryForecastProjection.java b/app-monolith/src/main/java/pl/com/bottega/factory/delivery/planning/projection/DeliveryForecastProjection.java index 8a63e0a..d27f2b9 100644 --- a/app-monolith/src/main/java/pl/com/bottega/factory/delivery/planning/projection/DeliveryForecastProjection.java +++ b/app-monolith/src/main/java/pl/com/bottega/factory/delivery/planning/projection/DeliveryForecastProjection.java @@ -24,7 +24,7 @@ public class DeliveryForecastProjection implements DemandEvents { @Override public void emit(DemandedLevelsChanged event) { - DeliveryAutoPlanner planner = planners.get(event.getId().getRefNo()); + DeliveryAutoPlanner planner = planners.get(event.getRefNo()); event.getResults().keySet() .forEach(daily -> forecastDao.deleteByRefNoAndDate( daily.getRefNo(), 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 cf88ec8..adc4511 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 @@ -10,32 +10,32 @@ import javax.persistence.*; import java.time.LocalDate; import java.util.Optional; -@Data @Entity(name = "Demand") +@Data @NoArgsConstructor @EqualsAndHashCode(of = "id") public class DemandEntity { @Id @GeneratedValue(strategy = GenerationType.AUTO) - Long id; + private Long id; @ManyToOne - ProductDemandEntity product; + private ProductDemandEntity product; @Column - LocalDate date; + private LocalDate date; @Column - Long level; + private Long level; @Column - Demand.Schema schema; + private Demand.Schema schema; @Column - Long adjustmentLevel; + private Long adjustmentLevel; @Column - Demand.Schema adjustmentSchema; + private Demand.Schema adjustmentSchema; @Column - boolean adjustmentStrong; + private boolean adjustmentStrong; DemandEntity(ProductDemandEntity product, LocalDate date) { this.product = product; 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/DemandORMRepository.java similarity index 99% rename from app-monolith/src/main/java/pl/com/bottega/factory/demand/forecasting/DemandRepository.java rename to app-monolith/src/main/java/pl/com/bottega/factory/demand/forecasting/DemandORMRepository.java index 8098454..d5b6962 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/DemandORMRepository.java @@ -21,7 +21,7 @@ import static java.util.stream.Collectors.toMap; @Component @AllArgsConstructor -class DemandRepository { +class DemandORMRepository { private Clock clock; private DemandEventsMapping events; 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 5c98291..b95c0b7 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,20 +1,22 @@ package pl.com.bottega.factory.demand.forecasting; +import lombok.AllArgsConstructor; import org.springframework.stereotype.Service; import javax.transaction.Transactional; @Service +@Transactional +@AllArgsConstructor public class DemandService { - private DemandRepository repository; + private final DemandORMRepository repository; public void process(Document document) { ProductDemand model = repository.get(document.getRefNo()); model.process(document); } - @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 516911a..a9fe163 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 @@ -9,19 +9,19 @@ import pl.com.bottega.tools.TechnicalId; import javax.persistence.*; -@Data @Entity(name = "ProductDemand") +@Data @NoArgsConstructor @EqualsAndHashCode(of = "id") public class ProductDemandEntity { @Id @GeneratedValue(strategy = GenerationType.AUTO) - Long id; + private Long id; @Version - Long version; + private Long version; @Column - String refNo; + private String refNo; public ProductDemandEntity(String refNo) { this.refNo = refNo; diff --git a/app-monolith/src/main/java/pl/com/bottega/factory/shortages/prediction/ShortagePredictionMapping.java b/app-monolith/src/main/java/pl/com/bottega/factory/shortages/prediction/ShortagePredictionMapping.java index bead295..f678b9f 100644 --- a/app-monolith/src/main/java/pl/com/bottega/factory/shortages/prediction/ShortagePredictionMapping.java +++ b/app-monolith/src/main/java/pl/com/bottega/factory/shortages/prediction/ShortagePredictionMapping.java @@ -7,16 +7,16 @@ 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; -@AllArgsConstructor -@Component @Lazy +@Component +@AllArgsConstructor public class ShortagePredictionMapping implements DemandEvents { private ShortagePredictionProcessRepository repository; @Override public void emit(DemandedLevelsChanged event) { - ShortagePredictionProcess model = repository.get(event.getId()); + ShortagePredictionProcess model = repository.get(event.getRefNo()); model.onDemandChanged(); repository.save(model); } diff --git a/app-monolith/src/main/java/pl/com/bottega/factory/shortages/prediction/monitoring/ShortagePredictionProcessORMRepository.java b/app-monolith/src/main/java/pl/com/bottega/factory/shortages/prediction/monitoring/ShortagePredictionProcessORMRepository.java new file mode 100644 index 0000000..1b1b310 --- /dev/null +++ b/app-monolith/src/main/java/pl/com/bottega/factory/shortages/prediction/monitoring/ShortagePredictionProcessORMRepository.java @@ -0,0 +1,57 @@ +package pl.com.bottega.factory.shortages.prediction.monitoring; + +import lombok.AllArgsConstructor; +import org.springframework.stereotype.Component; +import pl.com.bottega.factory.shortages.prediction.Configuration; +import pl.com.bottega.factory.shortages.prediction.Shortages; +import pl.com.bottega.factory.shortages.prediction.calculation.Forecasts; +import pl.com.bottega.factory.shortages.prediction.notification.NotificationOfShortage; + +import static java.util.Optional.ofNullable; + +@Component +@AllArgsConstructor +class ShortagePredictionProcessORMRepository implements ShortagePredictionProcessRepository { + + private final ShortagesDao dao; + private final ShortageDiffPolicy policy = ShortageDiffPolicy.ValuesAreEquals; + private final Forecasts forecasts; + private final Configuration configuration = () -> 14; + private final NotificationOfShortage notifications; + + @Override + public ShortagePredictionProcess get(String refNo) { + return new ShortagePredictionProcess( + refNo, fetchData(refNo), + policy, forecasts, configuration, new EventsHandler() + ); + } + + @Override + public void save(ShortagePredictionProcess model) { + // persisted after event + } + + private Shortages fetchData(String refNo) { + return ofNullable(dao.findOne(refNo)) + .map(ShortagesEntity::getShortages).orElse(null); + } + + private class EventsHandler implements ShortageEvents { + @Override + public void emit(NewShortage event) { + String refNo = event.getShortages().getRefNo(); + ShortagesEntity entity = ofNullable(dao.findOne(refNo)) + .orElseGet(() -> new ShortagesEntity(refNo)); + entity.setShortages(event.getShortages()); + dao.save(entity); + notifications.emit(event); + } + + @Override + public void emit(ShortageSolved event) { + dao.delete(event.getRefNo()); + notifications.emit(event); + } + } +} diff --git a/app-monolith/src/main/java/pl/com/bottega/factory/shortages/prediction/monitoring/ShortagesDao.java b/app-monolith/src/main/java/pl/com/bottega/factory/shortages/prediction/monitoring/ShortagesDao.java new file mode 100644 index 0000000..f8457f4 --- /dev/null +++ b/app-monolith/src/main/java/pl/com/bottega/factory/shortages/prediction/monitoring/ShortagesDao.java @@ -0,0 +1,8 @@ +package pl.com.bottega.factory.shortages.prediction.monitoring; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface ShortagesDao extends JpaRepository { +} diff --git a/app-monolith/src/main/java/pl/com/bottega/factory/shortages/prediction/monitoring/ShortagesEntity.java b/app-monolith/src/main/java/pl/com/bottega/factory/shortages/prediction/monitoring/ShortagesEntity.java new file mode 100644 index 0000000..a91e316 --- /dev/null +++ b/app-monolith/src/main/java/pl/com/bottega/factory/shortages/prediction/monitoring/ShortagesEntity.java @@ -0,0 +1,37 @@ +package pl.com.bottega.factory.shortages.prediction.monitoring; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import pl.com.bottega.factory.shortages.prediction.Shortages; +import pl.com.bottega.tools.JsonConverter; + +import javax.persistence.Column; +import javax.persistence.Convert; +import javax.persistence.Entity; +import javax.persistence.Id; + +@Entity(name = "Shortage") +@Data +@NoArgsConstructor +@EqualsAndHashCode(of = "refNo") +class ShortagesEntity { + + @Id + @Column + private String refNo; + + @Column + @Convert(converter = ShortagesAsJson.class) + Shortages shortages; + + ShortagesEntity(String refNo) { + this.refNo = refNo; + } + + public static class ShortagesAsJson extends JsonConverter { + public ShortagesAsJson() { + super(Shortages.class); + } + } +} diff --git a/app-monolith/src/main/java/pl/com/bottega/factory/shortages/prediction/notification/NotificationConfiguration.java b/app-monolith/src/main/java/pl/com/bottega/factory/shortages/prediction/notification/NotificationConfiguration.java new file mode 100644 index 0000000..baa735d --- /dev/null +++ b/app-monolith/src/main/java/pl/com/bottega/factory/shortages/prediction/notification/NotificationConfiguration.java @@ -0,0 +1,51 @@ +package pl.com.bottega.factory.shortages.prediction.notification; + +import lombok.AllArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import pl.com.bottega.factory.shortages.prediction.Shortages; + +import java.time.Clock; + +@Configuration +@AllArgsConstructor +public class NotificationConfiguration { + + private final Clock clock; + private final QualityTasks qualityTasks = new MockedJiraIntegration(); + private final Notifications notifications = new MockedPlannerPushNotifications(); + private final RecoveryTaskPriorityChangePolicy policy = + RecoveryTaskPriorityChangePolicy.shortageInDays(2); + + @Bean + public NotificationOfShortage notificationOfShortage() { + return new NotificationOfShortage( + qualityTasks, clock, policy, + NotificationOfShortage.rulesOfPlannerNotification(notifications) + ); + } + + private static class MockedPlannerPushNotifications implements Notifications { + @Override + public void alertPlanner(Shortages shortage) { + + } + + @Override + public void softNotifyPlanner(Shortages shortage) { + + } + + @Override + public void markOnPlan(Shortages shortage) { + + } + } + + private class MockedJiraIntegration implements QualityTasks { + @Override + public void increasePriorityFor(String productRefNo) { + + } + } +} 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/DemandORMRepositoryTest.groovy similarity index 93% rename from app-monolith/src/test/groovy/pl/com/bottega/factory/demand/forecasting/DemandRepositoryTest.groovy rename to app-monolith/src/test/groovy/pl/com/bottega/factory/demand/forecasting/DemandORMRepositoryTest.groovy index fc336b3..d789631 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/DemandORMRepositoryTest.groovy @@ -17,7 +17,7 @@ import java.time.ZoneId @SpringBootTest @Transactional @Commit -class DemandRepositoryTest extends Specification { +class DemandORMRepositoryTest extends Specification { def clock = Clock.fixed(Instant.now(), ZoneId.systemDefault()) def events = Mock(DemandEventsMapping) @@ -28,14 +28,14 @@ class DemandRepositoryTest extends Specification { @Autowired DemandDao demandDao - DemandRepository repository + DemandORMRepository repository final def today = LocalDate.now(clock) def setup() { demandDao.deleteAllInBatch() rootDao.deleteAllInBatch() - repository = new DemandRepository(clock, events, em, rootDao, demandDao) + repository = new DemandORMRepository(clock, events, em, rootDao, demandDao) } def "persists new demand"() { diff --git a/app-monolith/src/test/groovy/pl/com/bottega/factory/shortages/prediction/monitoring/ShortagePredictionProcessORMRepositoryTest.groovy b/app-monolith/src/test/groovy/pl/com/bottega/factory/shortages/prediction/monitoring/ShortagePredictionProcessORMRepositoryTest.groovy new file mode 100644 index 0000000..1f01baa --- /dev/null +++ b/app-monolith/src/test/groovy/pl/com/bottega/factory/shortages/prediction/monitoring/ShortagePredictionProcessORMRepositoryTest.groovy @@ -0,0 +1,136 @@ +package pl.com.bottega.factory.shortages.prediction.monitoring + +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.test.annotation.Commit +import pl.com.bottega.factory.shortages.prediction.Shortages +import pl.com.bottega.factory.shortages.prediction.calculation.Forecasts +import pl.com.bottega.factory.shortages.prediction.notification.NotificationOfShortage +import spock.lang.Specification + +import javax.transaction.Transactional +import java.time.LocalDateTime + +import static pl.com.bottega.factory.shortages.prediction.monitoring.NewShortage.After.DemandChanged + +@SpringBootTest +@Transactional +@Commit +class ShortagePredictionProcessORMRepositoryTest extends Specification { + + LocalDateTime now = LocalDateTime.now() + String refNo = "3009000" + + @Autowired + ShortagesDao dao + def forecasts = Mock(Forecasts) + def notifications = Mock(NotificationOfShortage) + ShortagePredictionProcessORMRepository repository + + def setup() { + dao.deleteAllInBatch() + repository = new ShortagePredictionProcessORMRepository( + dao, forecasts, notifications + ) + } + + def "provides process instance when no shortage persisted"() { + when: + def process = fetchProcess() + + then: + shortagesCurrentlyKnownBy(process) == noShortages() + } + + def "provides process instance with last known shortage"() { + given: + persistedShortage(someShortages()) + + when: + def process = fetchProcess() + + then: + shortagesCurrentlyKnownBy(process) == someShortages() + } + + def "persists first shortage"() { + when: + def process = fetchProcess() + processEmitsNewShortage(process, someShortages()) + + then: + shortagesCurrentlyPersisted() == someShortages() + } + + def "updates previous shortage"() { + given: + persistedShortage(someOldShortages()) + + when: + def process = fetchProcess() + processEmitsNewShortage(process, someShortages()) + + then: + shortagesCurrentlyPersisted() == someShortages() + } + + def "deletes solved shortage"() { + given: + persistedShortage(someShortages()) + + when: + def process = fetchProcess() + processEmitsShortageSolved(process) + + then: + noShortagesPersisted() + } + + def persistedShortage(Shortages shortages) { + def entity = new ShortagesEntity(refNo) + entity.setShortages(shortages) + dao.save(entity) + } + + Shortages shortagesCurrentlyPersisted() { + dao.findOne(refNo).shortages + } + + void noShortagesPersisted() { + assert dao.findOne(refNo) == null + } + + ShortagePredictionProcess fetchProcess() { + repository.get(refNo) + } + + Shortages noShortages() { + null + } + + Shortages someShortages() { + Shortages.builder(refNo, 0, now) + .missing(now.plusDays(1), 500) + .build() + .orElse(null) + } + + Shortages someOldShortages() { + Shortages.builder(refNo, 0, now.minusDays(1)) + .missing(now.plusDays(2), 2500) + .build() + .orElse(null) + } + + Shortages shortagesCurrentlyKnownBy(ShortagePredictionProcess process) { + process.known + } + + void processEmitsNewShortage(ShortagePredictionProcess process, Shortages shortages) { + process.events.emit(new NewShortage(DemandChanged, shortages)) + } + + void processEmitsShortageSolved(ShortagePredictionProcess process) { + process.events.emit(new ShortageSolved(refNo)) + } +} diff --git a/shared-kernel-model/src/main/java/pl/com/bottega/factory/demand/forecasting/DemandEvents.java b/shared-kernel-model/src/main/java/pl/com/bottega/factory/demand/forecasting/DemandEvents.java index 9ee786f..1e7da58 100644 --- a/shared-kernel-model/src/main/java/pl/com/bottega/factory/demand/forecasting/DemandEvents.java +++ b/shared-kernel-model/src/main/java/pl/com/bottega/factory/demand/forecasting/DemandEvents.java @@ -19,6 +19,10 @@ public interface DemandEvents { this.results = Collections.unmodifiableMap(results); } + public String getRefNo() { + return id.getRefNo(); + } + @Value public static class Change { Demand previous; diff --git a/shortages-prediction-model/src/main/java/pl/com/bottega/factory/shortages/prediction/monitoring/ShortagePredictionProcessRepository.java b/shortages-prediction-model/src/main/java/pl/com/bottega/factory/shortages/prediction/monitoring/ShortagePredictionProcessRepository.java index 3036065..f238c5a 100644 --- a/shortages-prediction-model/src/main/java/pl/com/bottega/factory/shortages/prediction/monitoring/ShortagePredictionProcessRepository.java +++ b/shortages-prediction-model/src/main/java/pl/com/bottega/factory/shortages/prediction/monitoring/ShortagePredictionProcessRepository.java @@ -1,12 +1,10 @@ package pl.com.bottega.factory.shortages.prediction.monitoring; -import pl.com.bottega.factory.product.management.RefNoId; - /** * Created by michal on 03.02.2017. */ public interface ShortagePredictionProcessRepository { - ShortagePredictionProcess get(RefNoId refNo); + ShortagePredictionProcess get(String refNo); void save(ShortagePredictionProcess model); } diff --git a/shortages-prediction-model/src/main/java/pl/com/bottega/factory/shortages/prediction/notification/NotificationOfShortage.java b/shortages-prediction-model/src/main/java/pl/com/bottega/factory/shortages/prediction/notification/NotificationOfShortage.java index 4bcbeec..6e74741 100644 --- a/shortages-prediction-model/src/main/java/pl/com/bottega/factory/shortages/prediction/notification/NotificationOfShortage.java +++ b/shortages-prediction-model/src/main/java/pl/com/bottega/factory/shortages/prediction/notification/NotificationOfShortage.java @@ -15,7 +15,7 @@ import java.time.LocalDateTime; import java.util.Map; @AllArgsConstructor -class NotificationOfShortage implements ShortageEvents { +public class NotificationOfShortage implements ShortageEvents { private final QualityTasks qualityTasks; private final Clock clock;