ProductDemand persistence + draft of events projection

This commit is contained in:
Michał Michaluk
2017-12-04 16:14:10 +01:00
parent 8a91308560
commit 39a810e4b3
11 changed files with 250 additions and 11 deletions

View File

@@ -2,7 +2,9 @@ package pl.com.bottega.factory.demand.forecasting;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import pl.com.bottega.tools.TechnicalId;
import javax.persistence.*; import javax.persistence.*;
import java.time.LocalDate; import java.time.LocalDate;
@@ -22,4 +24,76 @@ public class DemandEntity {
@Column @Column
LocalDate date; 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;
}
}
} }

View File

@@ -2,12 +2,18 @@ package pl.com.bottega.factory.demand.forecasting;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import pl.com.bottega.factory.demand.forecasting.projection.CurrentDemandsProjection;
@Lazy @Lazy
@Component @Component
class DemandEventsMapping implements DemandEvents { class DemandEventsMapping implements DemandEvents {
CurrentDemandsProjection demands;
//ShortagePredictionMapping predictions;
@Override @Override
public void emit(DemandedLevelsChanged event) { public void emit(DemandedLevelsChanged event) {
demands.emit(event);
//predictions.emit(event);
} }
} }

View File

@@ -2,10 +2,22 @@ package pl.com.bottega.factory.demand.forecasting;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import org.springframework.stereotype.Component; 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.DemandDao;
import pl.com.bottega.factory.demand.forecasting.persistence.ProductDemandDao; 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.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 @Component
@AllArgsConstructor @AllArgsConstructor
@@ -13,13 +25,62 @@ class DemandRepository {
private Clock clock; private Clock clock;
private DemandEventsMapping events; private DemandEventsMapping events;
private EntityManager em;
private ProductDemandDao rootDao; private ProductDemandDao rootDao;
private DemandDao demandDao; private DemandDao demandDao;
ProductDemand get(String refNo) { ProductDemand get(String refNo) {
return null; ProductDemandEntity root = rootDao.findByRefNo(refNo);
RefNoId id = root.createId();
Map<LocalDate, DemandEntity> 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<LocalDate, DemandEntity> 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) { 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);
}
}
} }
} }

View File

@@ -1,11 +1,7 @@
package pl.com.bottega.factory.demand.forecasting; package pl.com.bottega.factory.demand.forecasting;
import org.springframework.stereotype.Service;
import javax.transaction.Transactional; import javax.transaction.Transactional;
@Service
@Transactional
public class DemandService { public class DemandService {
private DemandRepository repository; private DemandRepository repository;
@@ -13,9 +9,9 @@ public class DemandService {
public void process(Document document) { public void process(Document document) {
ProductDemand model = repository.get(document.getRefNo()); ProductDemand model = repository.get(document.getRefNo());
model.process(document); model.process(document);
repository.save(model);
} }
@Transactional
public void adjust(AdjustDemand adjustDemand) { public void adjust(AdjustDemand adjustDemand) {
ProductDemand model = repository.get(adjustDemand.getRefNo()); ProductDemand model = repository.get(adjustDemand.getRefNo());
model.adjust(adjustDemand); model.adjust(adjustDemand);

View File

@@ -2,7 +2,10 @@ package pl.com.bottega.factory.demand.forecasting;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import pl.com.bottega.factory.product.management.RefNoId;
import pl.com.bottega.tools.TechnicalId;
import javax.persistence.*; import javax.persistence.*;
@@ -20,4 +23,28 @@ public class ProductDemandEntity {
@Column @Column
String refNo; 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;
}
}
} }

View File

@@ -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<CurrentDemandEntity> findRefNoFromDate(String refNo, Instant now);
}

View File

@@ -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;
}
}

View File

@@ -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) {
}
}

View File

@@ -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<DeliveryForecastEntity> findRefNoFrom(String refNo, Instant instant, int daysAhead);
void delete(String refNo, LocalDate date);
}

View File

@@ -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;
}
}

View File

@@ -5,9 +5,9 @@ import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.annotation.Commit import org.springframework.test.annotation.Commit
import pl.com.bottega.factory.demand.forecasting.persistence.DemandDao import pl.com.bottega.factory.demand.forecasting.persistence.DemandDao
import pl.com.bottega.factory.demand.forecasting.persistence.ProductDemandDao import pl.com.bottega.factory.demand.forecasting.persistence.ProductDemandDao
import spock.lang.PendingFeature
import spock.lang.Specification import spock.lang.Specification
import javax.persistence.EntityManager
import javax.transaction.Transactional import javax.transaction.Transactional
import java.time.Clock import java.time.Clock
import java.time.Instant import java.time.Instant
@@ -22,6 +22,8 @@ class DemandRepositoryTest extends Specification {
def clock = Clock.fixed(Instant.now(), ZoneId.systemDefault()) def clock = Clock.fixed(Instant.now(), ZoneId.systemDefault())
def events = Mock(DemandEventsMapping) def events = Mock(DemandEventsMapping)
@Autowired @Autowired
EntityManager em
@Autowired
ProductDemandDao rootDao ProductDemandDao rootDao
@Autowired @Autowired
DemandDao demandDao DemandDao demandDao
@@ -33,10 +35,9 @@ class DemandRepositoryTest extends Specification {
def setup() { def setup() {
demandDao.deleteAllInBatch() demandDao.deleteAllInBatch()
rootDao.deleteAllInBatch() rootDao.deleteAllInBatch()
repository = new DemandRepository(clock, events, rootDao, demandDao) repository = new DemandRepository(clock, events, em, rootDao, demandDao)
} }
@PendingFeature
def "persists new demand"() { def "persists new demand"() {
given: given:
rootDao.save(new ProductDemandEntity("3009000")) rootDao.save(new ProductDemandEntity("3009000"))
@@ -52,7 +53,6 @@ class DemandRepositoryTest extends Specification {
demandDao.findAll().size() == 1 demandDao.findAll().size() == 1
} }
@PendingFeature
def "updates existing demand"() { def "updates existing demand"() {
given: given:
def root = rootDao.save(new ProductDemandEntity("3009000")) def root = rootDao.save(new ProductDemandEntity("3009000"))
@@ -73,7 +73,6 @@ class DemandRepositoryTest extends Specification {
demand.every { it.getAdjustmentLevel() == 2000 } demand.every { it.getAdjustmentLevel() == 2000 }
} }
@PendingFeature
def "doesn't fetch historical data"() { def "doesn't fetch historical data"() {
given: given:
def root = rootDao.save(new ProductDemandEntity("3009000")) def root = rootDao.save(new ProductDemandEntity("3009000"))