improved code coverage and bug fix
This commit is contained in:
@@ -17,7 +17,7 @@ interface DeliveriesSuggestion {
|
||||
timesAndFractions.entrySet().stream()
|
||||
.map(e -> new Delivery(
|
||||
refNo,
|
||||
date.atTime(e.getKey()), ((long) ((double) demand.getLevel() / e.getValue())))
|
||||
date.atTime(e.getKey()), ((long) ((double) demand.getLevel() * e.getValue())))
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
package pl.com.bottega.factory.delivery.planning
|
||||
|
||||
import pl.com.bottega.factory.demand.forecasting.Demand
|
||||
import spock.lang.PendingFeature
|
||||
import spock.lang.Specification
|
||||
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalDateTime
|
||||
import java.time.LocalTime
|
||||
import java.util.stream.Collectors
|
||||
|
||||
import static pl.com.bottega.factory.demand.forecasting.Demand.Schema.AtDayStart
|
||||
|
||||
class DeliveriesSuggestionSpec extends Specification {
|
||||
|
||||
def final refNo = "3009000"
|
||||
def final date = LocalDate.now()
|
||||
def final midnight = LocalTime.of(0, 0)
|
||||
def final sevenAM = LocalTime.of(7, 0)
|
||||
def final nineAM = LocalTime.of(9, 0)
|
||||
|
||||
def "Times and fractions - all at once"() {
|
||||
given:
|
||||
def suggestion = DeliveriesSuggestion.timesAndFractions([(sevenAM): 1.0d])
|
||||
|
||||
when:
|
||||
def deliveries = suggestion.deliveriesFor(refNo, date, atDayStart(2000))
|
||||
.collect(Collectors.toList())
|
||||
|
||||
then:
|
||||
deliveries == [deliveryAt(date.atTime(sevenAM), 2000)]
|
||||
}
|
||||
|
||||
def "Times and fractions - half twice"() {
|
||||
given:
|
||||
def suggestion = DeliveriesSuggestion.timesAndFractions([
|
||||
(sevenAM): 0.5d,
|
||||
(nineAM): 0.5d,
|
||||
])
|
||||
|
||||
when:
|
||||
def deliveries = suggestion.deliveriesFor(refNo, date, atDayStart(2000))
|
||||
.collect(Collectors.toList())
|
||||
|
||||
then:
|
||||
deliveries == [
|
||||
deliveryAt(date.atTime(sevenAM), 1000),
|
||||
deliveryAt(date.atTime(nineAM), 1000),
|
||||
]
|
||||
}
|
||||
|
||||
def "Times and fractions - not represented number"() {
|
||||
given:
|
||||
def suggestion = DeliveriesSuggestion.timesAndFractions([
|
||||
(midnight): 1/3d,
|
||||
(sevenAM): 1/3d,
|
||||
(nineAM): 1/3d,
|
||||
])
|
||||
|
||||
when:
|
||||
def deliveries = suggestion.deliveriesFor(refNo, date, atDayStart(2000))
|
||||
.collect(Collectors.toList())
|
||||
|
||||
then:
|
||||
deliveries == [
|
||||
deliveryAt(date.atTime(midnight), 666),
|
||||
deliveryAt(date.atTime(sevenAM), 666),
|
||||
deliveryAt(date.atTime(nineAM), 666),
|
||||
]
|
||||
}
|
||||
|
||||
@PendingFeature
|
||||
def "Times and fractions - do not lost parts on not represented number"() {
|
||||
given:
|
||||
def suggestion = DeliveriesSuggestion.timesAndFractions([
|
||||
(midnight): 1/3d,
|
||||
(sevenAM): 1/3d,
|
||||
(nineAM): 1/3d,
|
||||
])
|
||||
|
||||
when:
|
||||
def deliveries = suggestion.deliveriesFor(refNo, date, atDayStart(2000))
|
||||
.collect(Collectors.toList())
|
||||
|
||||
then:
|
||||
deliveries == [
|
||||
deliveryAt(date.atTime(midnight), 666),
|
||||
deliveryAt(date.atTime(sevenAM), 666),
|
||||
deliveryAt(date.atTime(nineAM), 668),
|
||||
]
|
||||
}
|
||||
|
||||
def deliveryAt(LocalDateTime time, Integer level) {
|
||||
new Delivery(refNo, time, level)
|
||||
}
|
||||
|
||||
def atDayStart(long level) {
|
||||
Demand.of(level, AtDayStart)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package pl.com.bottega.factory.delivery.planning
|
||||
|
||||
import pl.com.bottega.factory.demand.forecasting.Demand
|
||||
import spock.lang.Specification
|
||||
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalDateTime
|
||||
import java.time.LocalTime
|
||||
import java.util.stream.Collectors
|
||||
|
||||
import static pl.com.bottega.factory.delivery.planning.DeliveriesSuggestion.timesAndFractions
|
||||
import static pl.com.bottega.factory.demand.forecasting.Demand.Schema.AtDayStart
|
||||
|
||||
class DeliveryAutoPlannerSpec extends Specification {
|
||||
|
||||
def final refNo = "3009000"
|
||||
def final date = LocalDate.now()
|
||||
def final midnight = LocalTime.of(0, 0)
|
||||
def final sevenAM = LocalTime.of(7, 0)
|
||||
|
||||
def "Picks dummy suggestion"() {
|
||||
given:
|
||||
def planner = deliveryDefinitions([:])
|
||||
|
||||
when:
|
||||
def deliveries = planner.propose(date, atDayStart(2000))
|
||||
.collect(Collectors.toList())
|
||||
|
||||
then:
|
||||
deliveries == [deliveryAt(date.atTime(midnight), 2000)]
|
||||
}
|
||||
|
||||
def "Suggests according to defined policy"() {
|
||||
given:
|
||||
def planner = deliveryDefinitions([
|
||||
(AtDayStart): timesAndFractions([(sevenAM): 1.0d])
|
||||
])
|
||||
|
||||
when:
|
||||
def deliveries = planner.propose(date, atDayStart(2000))
|
||||
.collect(Collectors.toList())
|
||||
|
||||
then:
|
||||
deliveries == [deliveryAt(date.atTime(sevenAM), 2000)]
|
||||
}
|
||||
|
||||
def deliveryDefinitions(Map<Demand.Schema, DeliveriesSuggestion> suggestions) {
|
||||
new DeliveryAutoPlanner(refNo, suggestions)
|
||||
}
|
||||
|
||||
def deliveryAt(LocalDateTime time, Integer level) {
|
||||
new Delivery(refNo, time, level)
|
||||
}
|
||||
|
||||
def atDayStart(long level) {
|
||||
Demand.of(level, AtDayStart)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package pl.com.bottega.factory.demand.forecasting
|
||||
|
||||
import spock.lang.Specification
|
||||
|
||||
import java.time.LocalDate
|
||||
|
||||
import static pl.com.bottega.factory.demand.forecasting.ReviewDecision.PICK_NEW
|
||||
|
||||
class DemandServiceSpec extends Specification implements ProductDemandTrait {
|
||||
|
||||
def events = Mock(DemandEvents)
|
||||
def repo = Mock(ProductDemandRepository)
|
||||
def service = new DemandService(repo)
|
||||
|
||||
void setup() {
|
||||
builder = new ProductDemandBuilder(events: events)
|
||||
}
|
||||
|
||||
def "Repository interactions while product demand initialization"() {
|
||||
given:
|
||||
def refNo = "3009000"
|
||||
|
||||
when:
|
||||
service.initNewProduct(refNo)
|
||||
|
||||
then:
|
||||
1 * repo.initNewProduct(refNo)
|
||||
}
|
||||
|
||||
def "Repository interactions while document processing"() {
|
||||
given:
|
||||
def today = LocalDate.now(builder.clock)
|
||||
def demand = demanded(2800, 0)
|
||||
def document = document(today, 2000, 3500)
|
||||
repo.get(document.refNo) >> demand
|
||||
|
||||
when:
|
||||
service.process(document)
|
||||
|
||||
then:
|
||||
1 * repo.save(demand)
|
||||
}
|
||||
|
||||
def "Repository interactions while demand adjustments"() {
|
||||
given:
|
||||
def today = LocalDate.now(builder.clock)
|
||||
def demand = demanded(2800, 0)
|
||||
def adjustments = adjustments([(today): 1000])
|
||||
repo.get(adjustments.refNo) >> demand
|
||||
|
||||
when:
|
||||
service.adjust(adjustments)
|
||||
|
||||
then:
|
||||
1 * repo.save(demand)
|
||||
}
|
||||
|
||||
def "Repository interactions while review processing"() {
|
||||
given:
|
||||
def today = LocalDate.now(builder.clock)
|
||||
def demand = demand(0, 2800)
|
||||
.stronglyAdjusted((today): 3500)
|
||||
.build()
|
||||
def review = review(today, 0, 3500, 2800)
|
||||
repo.get(review.refNo) >> demand
|
||||
|
||||
when:
|
||||
service.review(review, PICK_NEW)
|
||||
|
||||
then:
|
||||
1 * repo.save(demand)
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
version: "2"
|
||||
|
||||
volumes:
|
||||
data:
|
||||
driver: local
|
||||
|
||||
services:
|
||||
db:
|
||||
build: ./database/
|
||||
volumes:
|
||||
- data:/var/lib/postgresql/data
|
||||
ports:
|
||||
- "5432:5432"
|
||||
@@ -3,7 +3,7 @@ package pl.com.bottega.factory.shortages.prediction.calculation
|
||||
import spock.lang.Specification
|
||||
|
||||
class ShortageCalculationAlgorithmSpec extends Specification
|
||||
implements ShortagesCalculationAssemblerTrait {
|
||||
implements ShortagesCalculationTrait {
|
||||
|
||||
void setup() {
|
||||
TimeGrammar.apply()
|
||||
|
||||
@@ -3,7 +3,7 @@ package pl.com.bottega.factory.shortages.prediction.calculation
|
||||
import spock.lang.Specification
|
||||
|
||||
class ShortageCalculationExamplesSpec extends Specification
|
||||
implements ShortagesCalculationAssemblerTrait {
|
||||
implements ShortagesCalculationTrait {
|
||||
|
||||
void setup() {
|
||||
TimeGrammar.apply()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package pl.com.bottega.factory.shortages.prediction.calculation
|
||||
|
||||
class ShortagesCalculationAssembler implements ShortagesCalculationAssemblerTrait {
|
||||
class ShortagesCalculationAssembler implements ShortagesCalculationTrait {
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import pl.com.bottega.factory.shortages.prediction.Shortage
|
||||
import java.time.Duration
|
||||
import java.time.LocalDateTime
|
||||
|
||||
trait ShortagesCalculationAssemblerTrait {
|
||||
trait ShortagesCalculationTrait {
|
||||
|
||||
LocalDateTime now = LocalDateTime.now()
|
||||
String refNo = "3009000"
|
||||
@@ -1,22 +1,14 @@
|
||||
package pl.com.bottega.factory.shortages.prediction.monitoring
|
||||
|
||||
import pl.com.bottega.factory.product.management.RefNoId
|
||||
import pl.com.bottega.factory.shortages.prediction.ConfigurationParams
|
||||
import pl.com.bottega.factory.shortages.prediction.Shortage
|
||||
import pl.com.bottega.factory.shortages.prediction.calculation.ShortageForecasts
|
||||
import pl.com.bottega.factory.shortages.prediction.calculation.ShortagesCalculationAssembler
|
||||
import spock.lang.Specification
|
||||
|
||||
import java.time.LocalDateTime
|
||||
|
||||
import static pl.com.bottega.factory.shortages.prediction.monitoring.NewShortage.After
|
||||
|
||||
class ShortagePredictionProcessSpec extends Specification {
|
||||
class ShortagePredictionProcessSpec extends Specification implements ShortagePredictionProcessTrait {
|
||||
|
||||
def refNo = new RefNoId("3009000")
|
||||
def now = LocalDateTime.now()
|
||||
def forecastAssembler = new ShortagesCalculationAssembler(refNo: refNo, now: now)
|
||||
def events = Mock(ShortageEvents)
|
||||
void setup() {
|
||||
events = Mock(ShortageEvents)
|
||||
}
|
||||
|
||||
def "Emits no events when there is still no shortages"() {
|
||||
given:
|
||||
@@ -117,64 +109,4 @@ class ShortagePredictionProcessSpec extends Specification {
|
||||
0 * events.emit(_ as NewShortage)
|
||||
0 * events.emit(_ as ShortageSolved)
|
||||
}
|
||||
|
||||
ShortagePredictionProcess predictionProcess(
|
||||
Shortage previouslyFound,
|
||||
ShortageForecasts forecastThatWillFindShortages) {
|
||||
|
||||
new ShortagePredictionProcess(
|
||||
refNo,
|
||||
previouslyFound,
|
||||
ShortageDiffPolicy.ValuesAreNotSame,
|
||||
forecastThatWillFindShortages,
|
||||
defaultConfig(),
|
||||
events
|
||||
)
|
||||
}
|
||||
|
||||
Map<LocalDateTime, Long> someShortages() {
|
||||
[(now.plusHours(5)): 500L,
|
||||
(now.plusDays(1)) : 500L]
|
||||
}
|
||||
|
||||
Map<LocalDateTime, Long> someDifferentShortages() {
|
||||
[(now.plusHours(5)): 100L,
|
||||
(now.plusDays(1)) : 900L]
|
||||
}
|
||||
|
||||
Shortage noShortagesWasPreviouslyFound() {
|
||||
null
|
||||
}
|
||||
|
||||
Shortage wasPreviouslyFound(Map<LocalDateTime, Long> shortages) {
|
||||
forecastAssembler.shortage(shortages).orElse(null)
|
||||
}
|
||||
|
||||
ShortageForecasts noShortagesWillBeFound() {
|
||||
forecastAssembler.forecastProvider(
|
||||
forecastAssembler.stock(1000),
|
||||
forecastAssembler.noDeliveries(),
|
||||
forecastAssembler.noProductions()
|
||||
)
|
||||
}
|
||||
|
||||
ShortageForecasts willFindShortages(Map<LocalDateTime, Long> shortages) {
|
||||
forecastAssembler.forecastProvider(
|
||||
forecastAssembler.stock(0),
|
||||
forecastAssembler.deliveries(shortages),
|
||||
forecastAssembler.noProductions()
|
||||
)
|
||||
}
|
||||
|
||||
ConfigurationParams defaultConfig() {
|
||||
new InMemoryConfigurationParams(daysAhead: 14)
|
||||
}
|
||||
|
||||
NewShortage newShortage(After after, Map<LocalDateTime, Long> missing) {
|
||||
new NewShortage(refNo, after, forecastAssembler.shortage(missing).get())
|
||||
}
|
||||
|
||||
ShortageSolved shortageSolved() {
|
||||
new ShortageSolved(refNo)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
package pl.com.bottega.factory.shortages.prediction.monitoring
|
||||
|
||||
import pl.com.bottega.factory.product.management.RefNoId
|
||||
import pl.com.bottega.factory.shortages.prediction.ConfigurationParams
|
||||
import pl.com.bottega.factory.shortages.prediction.Shortage
|
||||
import pl.com.bottega.factory.shortages.prediction.calculation.ShortageForecasts
|
||||
import pl.com.bottega.factory.shortages.prediction.calculation.ShortagesCalculationAssembler
|
||||
|
||||
import java.time.LocalDateTime
|
||||
|
||||
trait ShortagePredictionProcessTrait {
|
||||
|
||||
def refNo = new RefNoId("3009000")
|
||||
def now = LocalDateTime.now()
|
||||
def forecastAssembler = new ShortagesCalculationAssembler(refNo: refNo, now: now)
|
||||
ShortageEvents events
|
||||
|
||||
ShortagePredictionProcess predictionProcess(
|
||||
Shortage previouslyFound,
|
||||
ShortageForecasts forecastThatWillFindShortages) {
|
||||
|
||||
new ShortagePredictionProcess(
|
||||
refNo,
|
||||
previouslyFound,
|
||||
ShortageDiffPolicy.ValuesAreNotSame,
|
||||
forecastThatWillFindShortages,
|
||||
defaultConfig(),
|
||||
events
|
||||
)
|
||||
}
|
||||
|
||||
Map<LocalDateTime, Long> someShortages() {
|
||||
[(now.plusHours(5)): 500L,
|
||||
(now.plusDays(1)) : 500L]
|
||||
}
|
||||
|
||||
Map<LocalDateTime, Long> someDifferentShortages() {
|
||||
[(now.plusHours(5)): 100L,
|
||||
(now.plusDays(1)) : 900L]
|
||||
}
|
||||
|
||||
Shortage noShortagesWasPreviouslyFound() {
|
||||
null
|
||||
}
|
||||
|
||||
Shortage wasPreviouslyFound(Map<LocalDateTime, Long> shortages) {
|
||||
forecastAssembler.shortage(shortages).orElse(null)
|
||||
}
|
||||
|
||||
ShortageForecasts noShortagesWillBeFound() {
|
||||
forecastAssembler.forecastProvider(
|
||||
forecastAssembler.stock(1000),
|
||||
forecastAssembler.noDeliveries(),
|
||||
forecastAssembler.noProductions()
|
||||
)
|
||||
}
|
||||
|
||||
ShortageForecasts willFindShortages(Map<LocalDateTime, Long> shortages) {
|
||||
forecastAssembler.forecastProvider(
|
||||
forecastAssembler.stock(0),
|
||||
forecastAssembler.deliveries(shortages),
|
||||
forecastAssembler.noProductions()
|
||||
)
|
||||
}
|
||||
|
||||
ConfigurationParams defaultConfig() {
|
||||
new InMemoryConfigurationParams(daysAhead: 14)
|
||||
}
|
||||
|
||||
NewShortage newShortage(NewShortage.After after, Map<LocalDateTime, Long> missing) {
|
||||
new NewShortage(refNo, after, forecastAssembler.shortage(missing).get())
|
||||
}
|
||||
|
||||
ShortageSolved shortageSolved() {
|
||||
new ShortageSolved(refNo)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package pl.com.bottega.factory.shortages.prediction.monitoring
|
||||
|
||||
import pl.com.bottega.factory.demand.forecasting.DemandedLevelsChanged
|
||||
import spock.lang.Specification
|
||||
|
||||
class ShortagePredictionServiceSpec extends Specification implements ShortagePredictionProcessTrait {
|
||||
|
||||
def repo = Mock(ShortagePredictionProcessRepository)
|
||||
def service = new ShortagePredictionService(repo)
|
||||
|
||||
def "Repository interactions while processing demanded changed event"() {
|
||||
given:
|
||||
def process = predictionProcess(
|
||||
noShortagesWasPreviouslyFound(),
|
||||
noShortagesWillBeFound()
|
||||
)
|
||||
repo.get(refNo) >> process
|
||||
|
||||
when:
|
||||
service.predictShortages(new DemandedLevelsChanged(refNo, [:]))
|
||||
|
||||
then:
|
||||
1 * repo.save(process)
|
||||
0 * events.emit(_ as ShortageSolved)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user