public class AdjustDemandDto {
    private String productRefNo;
    private LocalDate atDay;
    private long level;
    private DeliverySchema deliverySchema;
    private String note;

    public AdjustDemandDto() {
    }

    public String getProductRefNo() {
        return this.productRefNo;
    }

    public LocalDate getAtDay() {
        return this.atDay;
    }

    public long getLevel() {
        return this.level;
    }

    public DeliverySchema getDeliverySchema() {
        return this.deliverySchema;
    }

    public String getNote() {
        return this.note;
    }

    public void setProductRefNo(String productRefNo) {
        this.productRefNo = productRefNo;
    }

    public void setAtDay(LocalDate atDay) {
        this.atDay = atDay;
    }

    public void setLevel(long level) {
        this.level = level;
    }

    public void setDeliverySchema(DeliverySchema deliverySchema) {
        this.deliverySchema = deliverySchema;
    }

    public void setNote(String note) {
        this.note = note;
    }

    public boolean equals(Object o) {
        if (o == this) return true;
        if (!(o instanceof AdjustDemandDto)) return false;
        final AdjustDemandDto other = (AdjustDemandDto) o;
        if (!other.canEqual((Object) this)) return false;
        final Object this$productRefNo = this.getProductRefNo();
        final Object other$productRefNo = other.getProductRefNo();
        if (this$productRefNo == null ? other$productRefNo != null : !this$productRefNo.equals(other$productRefNo))
            return false;
        final Object this$atDay = this.getAtDay();
        final Object other$atDay = other.getAtDay();
        if (this$atDay == null ? other$atDay != null : !this$atDay.equals(other$atDay)) return false;
        if (this.getLevel() != other.getLevel()) return false;
        final Object this$deliverySchema = this.getDeliverySchema();
        final Object other$deliverySchema = other.getDeliverySchema();
        if (this$deliverySchema == null ? other$deliverySchema != null : !this$deliverySchema.equals(other$deliverySchema))
            return false;
        final Object this$note = this.getNote();
        final Object other$note = other.getNote();
        if (this$note == null ? other$note != null : !this$note.equals(other$note)) return false;
        return true;
    }

    public int hashCode() {
        final int PRIME = 59;
        int result = 1;
        final Object $productRefNo = this.getProductRefNo();
        result = result * PRIME + ($productRefNo == null ? 43 : $productRefNo.hashCode());
        final Object $atDay = this.getAtDay();
        result = result * PRIME + ($atDay == null ? 43 : $atDay.hashCode());
        final long $level = this.getLevel();
        result = result * PRIME + (int) ($level >>> 32 ^ $level);
        final Object $deliverySchema = this.getDeliverySchema();
        result = result * PRIME + ($deliverySchema == null ? 43 : $deliverySchema.hashCode());
        final Object $note = this.getNote();
        result = result * PRIME + ($note == null ? 43 : $note.hashCode());
        return result;
    }

    protected boolean canEqual(Object other) {
        return other instanceof AdjustDemandDto;
    }

    public String toString() {
        return "api.AdjustDemandDto(productRefNo=" + this.getProductRefNo() + ", atDay=" + this.getAtDay() + ", level=" + this.getLevel() + ", deliverySchema=" + this.getDeliverySchema() + ", note=" + this.getNote() + ")";
    }
} public interface LogisticService {

    void adjustDemand(AdjustDemandDto adjustment);

    void processCallof(Object document);

    StockForecastDto getStockForecast(String productRefNo);
} public class StockForecastDto {
} public interface DemandDao {
    DemandEntity getCurrent(String productRefNo, LocalDate atDay);

    DemandEntity get(LocalDate callofDate, String productRefNo, LocalDate atDay);

    List<DemandEntity> findFrom(LocalDateTime localDateTime, String productRefNo);
} public interface ProductionDao {
    ProductionEntity get(long id);

    List<ProductionEntity> findFromTime(String productRefNo, LocalDateTime start);

    List<ProductionEntity> findFromTime(long id, LocalDateTime start);

    void save(ProductionEntity production);
} public interface ShortageDao {
    void save(List<ShortageEntity> shortages);

    List<ShortageEntity> getForProduct(String refNo);

    void delete(String productRefNo);
} public class DemandEntity {

    private long id;
    private LocalDate callofDate;
    private String productRefNo;
    private LocalDate atDay;
    private OriginalDemandEntity original;
    private List<ManualAdjustmentEntity> adjustment = new LinkedList<>();

    public DemandEntity() {
    }

    public long getId() {
        return this.id;
    }

    public LocalDate getCallofDate() {
        return this.callofDate;
    }

    public String getProductRefNo() {
        return this.productRefNo;
    }

    public LocalDate getDay() {
        return this.atDay;
    }

    public OriginalDemandEntity getOriginal() {
        return this.original;
    }

    public List<ManualAdjustmentEntity> getAdjustment() {
        return this.adjustment;
    }

    public void setId(long id) {
        this.id = id;
    }

    public void setCallofDate(LocalDate callofDate) {
        this.callofDate = callofDate;
    }

    public void setProductRefNo(String productRefNo) {
        this.productRefNo = productRefNo;
    }

    public void setAtDay(LocalDate atDay) {
        this.atDay = atDay;
    }

    public void setOriginal(OriginalDemandEntity original) {
        this.original = original;
    }

    public void setAdjustment(List<ManualAdjustmentEntity> adjustment) {
        this.adjustment = adjustment;
    }

    public boolean equals(Object o) {
        if (o == this) return true;
        if (!(o instanceof DemandEntity)) return false;
        final DemandEntity other = (DemandEntity) o;
        if (!other.canEqual((Object) this)) return false;
        if (this.getId() != other.getId()) return false;
        final Object this$callofDate = this.getCallofDate();
        final Object other$callofDate = other.getCallofDate();
        if (this$callofDate == null ? other$callofDate != null : !this$callofDate.equals(other$callofDate))
            return false;
        final Object this$productRefNo = this.getProductRefNo();
        final Object other$productRefNo = other.getProductRefNo();
        if (this$productRefNo == null ? other$productRefNo != null : !this$productRefNo.equals(other$productRefNo))
            return false;
        final Object this$atDay = this.getDay();
        final Object other$atDay = other.getDay();
        if (this$atDay == null ? other$atDay != null : !this$atDay.equals(other$atDay)) return false;
        final Object this$original = this.getOriginal();
        final Object other$original = other.getOriginal();
        if (this$original == null ? other$original != null : !this$original.equals(other$original)) return false;
        final Object this$adjustment = this.getAdjustment();
        final Object other$adjustment = other.getAdjustment();
        if (this$adjustment == null ? other$adjustment != null : !this$adjustment.equals(other$adjustment))
            return false;
        return true;
    }

    public int hashCode() {
        final int PRIME = 59;
        int result = 1;
        final long $id = this.getId();
        result = result * PRIME + (int) ($id >>> 32 ^ $id);
        final Object $callofDate = this.getCallofDate();
        result = result * PRIME + ($callofDate == null ? 43 : $callofDate.hashCode());
        final Object $productRefNo = this.getProductRefNo();
        result = result * PRIME + ($productRefNo == null ? 43 : $productRefNo.hashCode());
        final Object $atDay = this.getDay();
        result = result * PRIME + ($atDay == null ? 43 : $atDay.hashCode());
        final Object $original = this.getOriginal();
        result = result * PRIME + ($original == null ? 43 : $original.hashCode());
        final Object $adjustment = this.getAdjustment();
        result = result * PRIME + ($adjustment == null ? 43 : $adjustment.hashCode());
        return result;
    }

    protected boolean canEqual(Object other) {
        return other instanceof DemandEntity;
    }

    public String toString() {
        return "entities.DemandEntity(id=" + this.getId() + ", callofDate=" + this.getCallofDate() + ", productRefNo=" + this.getProductRefNo() + ", atDay=" + this.getDay() + ", original=" + this.getOriginal() + ", adjustment=" + this.getAdjustment() + ")";
    }

} public class ManualAdjustmentEntity {
    private long level;
    private String note;
    private DeliverySchema deliverySchema;

    public ManualAdjustmentEntity() {
    }

    public long getLevel() {
        return this.level;
    }

    public String getNote() {
        return this.note;
    }

    public DeliverySchema getDeliverySchema() {
        return this.deliverySchema;
    }

    public void setLevel(long level) {
        this.level = level;
    }

    public void setNote(String note) {
        this.note = note;
    }

    public void setDeliverySchema(DeliverySchema deliverySchema) {
        this.deliverySchema = deliverySchema;
    }

    public boolean equals(Object o) {
        if (o == this) return true;
        if (!(o instanceof ManualAdjustmentEntity)) return false;
        final ManualAdjustmentEntity other = (ManualAdjustmentEntity) o;
        if (!other.canEqual((Object) this)) return false;
        if (this.getLevel() != other.getLevel()) return false;
        final Object this$note = this.getNote();
        final Object other$note = other.getNote();
        if (this$note == null ? other$note != null : !this$note.equals(other$note)) return false;
        final Object this$deliverySchema = this.getDeliverySchema();
        final Object other$deliverySchema = other.getDeliverySchema();
        if (this$deliverySchema == null ? other$deliverySchema != null : !this$deliverySchema.equals(other$deliverySchema))
            return false;
        return true;
    }

    public int hashCode() {
        final int PRIME = 59;
        int result = 1;
        final long $level = this.getLevel();
        result = result * PRIME + (int) ($level >>> 32 ^ $level);
        final Object $note = this.getNote();
        result = result * PRIME + ($note == null ? 43 : $note.hashCode());
        final Object $deliverySchema = this.getDeliverySchema();
        result = result * PRIME + ($deliverySchema == null ? 43 : $deliverySchema.hashCode());
        return result;
    }

    protected boolean canEqual(Object other) {
        return other instanceof ManualAdjustmentEntity;
    }

    public String toString() {
        return "entities.ManualAdjustmentEntity(level=" + this.getLevel() + ", note=" + this.getNote() + ", deliverySchema=" + this.getDeliverySchema() + ")";
    }
} public class ShortageEntity {
    private String refNo;
    private LocalDate found;
    private long missing;
    private LocalDate atDay;

    public ShortageEntity() {
    }

    public String getRefNo() {
        return this.refNo;
    }

    public LocalDate getFound() {
        return this.found;
    }

    public long getMissing() {
        return this.missing;
    }

    public LocalDate getAtDay() {
        return this.atDay;
    }

    public void setRefNo(String refNo) {
        this.refNo = refNo;
    }

    public void setFound(LocalDate found) {
        this.found = found;
    }

    public void setMissing(long missing) {
        this.missing = missing;
    }

    public void setAtDay(LocalDate atDay) {
        this.atDay = atDay;
    }

    public boolean equals(Object o) {
        if (o == this) return true;
        if (!(o instanceof ShortageEntity)) return false;
        final ShortageEntity other = (ShortageEntity) o;
        if (!other.canEqual((Object) this)) return false;
        final Object this$refNo = this.getRefNo();
        final Object other$refNo = other.getRefNo();
        if (this$refNo == null ? other$refNo != null : !this$refNo.equals(other$refNo)) return false;
        final Object this$found = this.getFound();
        final Object other$found = other.getFound();
        if (this$found == null ? other$found != null : !this$found.equals(other$found)) return false;
        if (this.getMissing() != other.getMissing()) return false;
        final Object this$atDay = this.getAtDay();
        final Object other$atDay = other.getAtDay();
        if (this$atDay == null ? other$atDay != null : !this$atDay.equals(other$atDay)) return false;
        return true;
    }

    public int hashCode() {
        final int PRIME = 59;
        int result = 1;
        final Object $refNo = this.getRefNo();
        result = result * PRIME + ($refNo == null ? 43 : $refNo.hashCode());
        final Object $found = this.getFound();
        result = result * PRIME + ($found == null ? 43 : $found.hashCode());
        final long $missing = this.getMissing();
        result = result * PRIME + (int) ($missing >>> 32 ^ $missing);
        final Object $atDay = this.getAtDay();
        result = result * PRIME + ($atDay == null ? 43 : $atDay.hashCode());
        return result;
    }

    protected boolean canEqual(Object other) {
        return other instanceof ShortageEntity;
    }

    public String toString() {
        return "entities.ShortageEntity(refNo=" + this.getRefNo() + ", found=" + this.getFound() + ", missing=" + this.getMissing() + ", atDay=" + this.getAtDay() + ")";
    }
} public class CurrentStock {
    private long level;
    private long locked;

    public CurrentStock(long level, long locked) {
        this.level = level;
        this.locked = locked;
    }

    public long getLevel() {
        return this.level;
    }

    public long getLocked() {
        return this.locked;
    }

    public boolean equals(Object o) {
        if (o == this) return true;
        if (!(o instanceof CurrentStock)) return false;
        final CurrentStock other = (CurrentStock) o;
        if (this.getLevel() != other.getLevel()) return false;
        if (this.getLocked() != other.getLocked()) return false;
        return true;
    }

    public int hashCode() {
        final int PRIME = 59;
        int result = 1;
        final long $level = this.getLevel();
        result = result * PRIME + (int) ($level >>> 32 ^ $level);
        final long $locked = this.getLocked();
        result = result * PRIME + (int) ($locked >>> 32 ^ $locked);
        return result;
    }

    public String toString() {
        return "external.CurrentStock(level=" + this.getLevel() + ", locked=" + this.getLocked() + ")";
    }
} public interface JiraService {
    void increasePriorityFor(String productRefNo);
} public interface NotificationsService {
    void alertPlanner(List<ShortageEntity> shortages);

    void softNotifyPlanner(List<ShortageEntity> shortages);

    void markOnPlan(List<ShortageEntity> shortages);
} public interface StockService {
    CurrentStock getCurrentStock(String productRefNo);
} public class ShortageFinder {

    /**
     * Production at day of expected delivery is quite complex:
     * We are able to produce and deliver just in time at same day
     * but depending on delivery time or scheme of multiple deliveries,
     * we need to plan properly to have right amount of parts ready before delivery time.
     * <p/>
     * Typical schemas are:
     * <li>Delivery at prod day start</li>
     * <li>Delivery till prod day end</li>
     * <li>Delivery during specified shift</li>
     * <li>Multiple deliveries at specified times</li>
     * Schema changes the way how we calculate shortages.
     * Pick of schema depends on customer demand on daily basis and for each product differently.
     * Some customers includes that information in callof document,
     * other stick to single schema per product. By manual adjustments of demand,
     * customer always specifies desired delivery schema
     * (increase amount in scheduled transport or organize extra transport at given time)
     *
     * TODO algorithm is finding wrong shortages, when more productions is planned in a single day
     */
    public static List<ShortageEntity> findShortages(LocalDate today, int daysAhead, CurrentStock stock,
                                                     List<ProductionEntity> productions, List<DemandEntity> demands) {

        ProductionOutputsFactory productionOutputsFactory = new ProductionOutputsFactory(productions);
        DemandsFactory demandsFactory = new DemandsFactory(demands);
        ShortagePredictionRepository repository = new ShortagePredictionRepository(stock, productionOutputsFactory, demandsFactory);

        ShortagePredictionAlgo algorithm = repository.get(today, daysAhead);

        return algorithm.findShortages();
    }

    private ShortageFinder() {
    }

}
import api.AdjustDemandDto;
import api.LogisticService;
import api.StockForecastDto;
import dao.DemandDao;
import dao.ProductionDao;
import dao.ShortageDao;
import entities.DemandEntity;
import entities.ManualAdjustmentEntity;
import entities.ShortageEntity;
import external.CurrentStock;
import external.JiraService;
import external.NotificationsService;
import external.StockService;
import tools.ShortageFinder;
import java.time.Clock;
import java.time.LocalDate;
import java.util.List;
package services.impl;



public class LogisticServiceImpl implements LogisticService {

    //Inject all
    private DemandDao demandDao;
    private ShortageDao shortageDao;
    private StockService stockService;
    private ProductionDao productionDao;