Merge branch 'master' into feature/user
This commit is contained in:
		@ -0,0 +1,83 @@
 | 
			
		||||
package org.hso.ecommerce.action.cronjob;
 | 
			
		||||
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.HashMap;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Objects;
 | 
			
		||||
 | 
			
		||||
import org.hso.ecommerce.api.SupplierService;
 | 
			
		||||
import org.hso.ecommerce.api.data.Article;
 | 
			
		||||
import org.hso.ecommerce.api.data.Supplier;
 | 
			
		||||
 | 
			
		||||
public class ReadSupplierDataAction {
 | 
			
		||||
    private List<org.hso.ecommerce.entities.supplier.Supplier> suppliers;
 | 
			
		||||
 | 
			
		||||
    public static class ArticleIdentifier {
 | 
			
		||||
        public final String manufacturer;
 | 
			
		||||
        public final String articleNumber;
 | 
			
		||||
 | 
			
		||||
        public ArticleIdentifier(String manufacturer, String articleNumber) {
 | 
			
		||||
            this.manufacturer = manufacturer;
 | 
			
		||||
            this.articleNumber = articleNumber;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public int hashCode() {
 | 
			
		||||
            return Objects.hash(manufacturer, articleNumber);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @Override
 | 
			
		||||
        public boolean equals(Object other) {
 | 
			
		||||
            if (!(other instanceof ArticleIdentifier)) {
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
            ArticleIdentifier otherId = (ArticleIdentifier) other;
 | 
			
		||||
            return this.manufacturer.equals(otherId.manufacturer) && this.articleNumber.equals(otherId.articleNumber);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public ReadSupplierDataAction(List<org.hso.ecommerce.entities.supplier.Supplier> suppliers) {
 | 
			
		||||
        this.suppliers = suppliers;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static class Offer {
 | 
			
		||||
        public final org.hso.ecommerce.entities.supplier.Supplier dbSupplier;
 | 
			
		||||
        public final Supplier apiSupplier;
 | 
			
		||||
 | 
			
		||||
        public Offer(org.hso.ecommerce.entities.supplier.Supplier dbSupplier, Supplier apiSupplier) {
 | 
			
		||||
            this.dbSupplier = dbSupplier;
 | 
			
		||||
            this.apiSupplier = apiSupplier;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static class Result {
 | 
			
		||||
        public final ArrayList<Supplier> supplierData;
 | 
			
		||||
        public final HashMap<ArticleIdentifier, Offer> cheapestOffer;
 | 
			
		||||
 | 
			
		||||
        public Result(ArrayList<Supplier> supplierData, HashMap<ArticleIdentifier, Offer> cheapestOffer) {
 | 
			
		||||
            this.supplierData = supplierData;
 | 
			
		||||
            this.cheapestOffer = cheapestOffer;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Result finish() {
 | 
			
		||||
        ArrayList<Supplier> suppliers = new ArrayList<>();
 | 
			
		||||
        HashMap<ArticleIdentifier, Integer> price = new HashMap<>();
 | 
			
		||||
        HashMap<ArticleIdentifier, Offer> cheapest = new HashMap<>();
 | 
			
		||||
        for (org.hso.ecommerce.entities.supplier.Supplier supplier : this.suppliers) {
 | 
			
		||||
            SupplierService service = new SupplierService(supplier.apiUrl);
 | 
			
		||||
            Supplier apiSupplier = service.getSupplier();
 | 
			
		||||
            suppliers.add(apiSupplier);
 | 
			
		||||
            for (Article article : apiSupplier.articles) {
 | 
			
		||||
                ArticleIdentifier identifier = new ArticleIdentifier(article.manufacturer, article.articleNumber);
 | 
			
		||||
                Integer previousPrice = price.get(identifier);
 | 
			
		||||
                if (previousPrice == null || article.pricePerUnitNet < previousPrice) {
 | 
			
		||||
                    price.put(identifier, article.pricePerUnitNet);
 | 
			
		||||
                    cheapest.put(identifier, new Offer(supplier, apiSupplier));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return new Result(suppliers, cheapest);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,107 @@
 | 
			
		||||
package org.hso.ecommerce.action.cronjob;
 | 
			
		||||
 | 
			
		||||
import java.sql.Timestamp;
 | 
			
		||||
import java.util.HashMap;
 | 
			
		||||
 | 
			
		||||
import org.hso.ecommerce.action.cronjob.ReadSupplierDataAction.ArticleIdentifier;
 | 
			
		||||
import org.hso.ecommerce.action.cronjob.ReadSupplierDataAction.Offer;
 | 
			
		||||
import org.hso.ecommerce.api.SupplierService;
 | 
			
		||||
import org.hso.ecommerce.api.data.Order;
 | 
			
		||||
import org.hso.ecommerce.api.data.OrderConfirmation;
 | 
			
		||||
import org.hso.ecommerce.entities.shop.Article;
 | 
			
		||||
import org.hso.ecommerce.entities.supplier.ArticleOffer;
 | 
			
		||||
import org.hso.ecommerce.entities.supplier.SupplierOrder;
 | 
			
		||||
import org.slf4j.Logger;
 | 
			
		||||
import org.slf4j.LoggerFactory;
 | 
			
		||||
 | 
			
		||||
public class ReorderAction {
 | 
			
		||||
    private static final Logger log = LoggerFactory.getLogger(ReorderAction.class);
 | 
			
		||||
 | 
			
		||||
    private Article article;
 | 
			
		||||
    private Integer[] orderedAmounts;
 | 
			
		||||
    private Integer undeliveredReorders;
 | 
			
		||||
    private int amountInStock;
 | 
			
		||||
    private HashMap<ArticleIdentifier, Offer> cheapestOffer;
 | 
			
		||||
    private HashMap<ArticleIdentifier, ArticleOffer> articleOffers;
 | 
			
		||||
 | 
			
		||||
    public ReorderAction(
 | 
			
		||||
            Article article, Integer[] orderedAmounts,
 | 
			
		||||
            Integer undeliveredReorders,
 | 
			
		||||
            int amountInStock,
 | 
			
		||||
            HashMap<ArticleIdentifier, Offer> cheapestOffer,
 | 
			
		||||
            HashMap<ArticleIdentifier, ArticleOffer> articleOffers
 | 
			
		||||
    ) {
 | 
			
		||||
        this.article = article;
 | 
			
		||||
        this.orderedAmounts = orderedAmounts;
 | 
			
		||||
        this.undeliveredReorders = undeliveredReorders;
 | 
			
		||||
        this.amountInStock = amountInStock;
 | 
			
		||||
        this.cheapestOffer = cheapestOffer;
 | 
			
		||||
        this.articleOffers = articleOffers;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private int null_to_zero(Integer input) {
 | 
			
		||||
        return input == null ? 0 : input;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private int calculateAmountToReorder() {
 | 
			
		||||
        // Algorithm as described in the documentation
 | 
			
		||||
        int a = null_to_zero(orderedAmounts[0]);
 | 
			
		||||
        int b = null_to_zero(orderedAmounts[1]);
 | 
			
		||||
        int c = null_to_zero(orderedAmounts[2]);
 | 
			
		||||
 | 
			
		||||
        int x = Math.max(Math.max(a, b), c);
 | 
			
		||||
        int y = Math.min(Math.min(a, b), c);
 | 
			
		||||
 | 
			
		||||
        int n = 6 * x - 2 * y;
 | 
			
		||||
        if (n < 3) {
 | 
			
		||||
            n = 3;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        int i = null_to_zero(undeliveredReorders);
 | 
			
		||||
        int l = amountInStock;
 | 
			
		||||
 | 
			
		||||
        return n - i - l;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public SupplierOrder finish() {
 | 
			
		||||
        if (!article.shouldReorder) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        int amount = calculateAmountToReorder();
 | 
			
		||||
        if (amount <= 0) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ArticleIdentifier identifier = new ArticleIdentifier(article.related.manufacturer, article.related.articleNumber);
 | 
			
		||||
        Offer offer = cheapestOffer.get(identifier);
 | 
			
		||||
        if (offer == null) {
 | 
			
		||||
            log.info("Could not order \"" + article.title + "\" because there is no supplier delivering it.");
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ArticleOffer articleOffer = articleOffers.get(identifier);
 | 
			
		||||
        org.hso.ecommerce.api.data.Article apiArticle = offer.apiSupplier.findArticle(identifier.manufacturer,
 | 
			
		||||
                identifier.articleNumber);
 | 
			
		||||
        if (apiArticle.pricePerUnitNet > article.reorderMaxPrice) {
 | 
			
		||||
            log.info("Could not order \"" + article.title + "\" because it is currently too expensive.");
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Order order = new Order();
 | 
			
		||||
        order.manufacturer = articleOffer.manufacturer;
 | 
			
		||||
        order.articleNumber = articleOffer.articleNumber;
 | 
			
		||||
        order.quantity = amount;
 | 
			
		||||
        order.maxTotalPriceCentNet = apiArticle.pricePerUnitNet * amount;
 | 
			
		||||
 | 
			
		||||
        OrderConfirmation confirm = new SupplierService(offer.dbSupplier.apiUrl).order(order);
 | 
			
		||||
        SupplierOrder createdOrder = new SupplierOrder();
 | 
			
		||||
        createdOrder.created = new Timestamp(System.currentTimeMillis());
 | 
			
		||||
        createdOrder.supplier = offer.dbSupplier;
 | 
			
		||||
        createdOrder.ordered = articleOffer;
 | 
			
		||||
        createdOrder.numberOfUnits = confirm.quantity;
 | 
			
		||||
        createdOrder.pricePerUnitNetCent = confirm.pricePerUnitNetCent;
 | 
			
		||||
        createdOrder.totalPriceNet = confirm.totalPriceNetCharged;
 | 
			
		||||
        return createdOrder;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,59 @@
 | 
			
		||||
package org.hso.ecommerce.action.cronjob;
 | 
			
		||||
 | 
			
		||||
import java.util.HashMap;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Map.Entry;
 | 
			
		||||
 | 
			
		||||
import org.hso.ecommerce.action.cronjob.ReadSupplierDataAction.ArticleIdentifier;
 | 
			
		||||
import org.hso.ecommerce.action.cronjob.ReadSupplierDataAction.Offer;
 | 
			
		||||
import org.hso.ecommerce.api.data.Article;
 | 
			
		||||
import org.hso.ecommerce.entities.supplier.ArticleOffer;
 | 
			
		||||
 | 
			
		||||
public class UpdateOffersAction {
 | 
			
		||||
    private List<ArticleOffer> offers;
 | 
			
		||||
    private HashMap<ArticleIdentifier, Offer> cheapestOffer;
 | 
			
		||||
 | 
			
		||||
    public UpdateOffersAction(List<ArticleOffer> offers, HashMap<ArticleIdentifier, Offer> cheapestOffer) {
 | 
			
		||||
        this.offers = offers;
 | 
			
		||||
        this.cheapestOffer = cheapestOffer;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private HashMap<ArticleIdentifier, ArticleOffer> mapOffers() {
 | 
			
		||||
        HashMap<ArticleIdentifier, ArticleOffer> map = new HashMap<>();
 | 
			
		||||
        for (ArticleOffer offer : offers) {
 | 
			
		||||
            ArticleIdentifier identifier = new ArticleIdentifier(offer.manufacturer, offer.articleNumber);
 | 
			
		||||
            map.put(identifier, offer);
 | 
			
		||||
        }
 | 
			
		||||
        return map;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public List<ArticleOffer> finish() {
 | 
			
		||||
        HashMap<ArticleIdentifier, ArticleOffer> availableOffers = mapOffers();
 | 
			
		||||
 | 
			
		||||
        // Reset all advertise-flags first. They are set again below.
 | 
			
		||||
        for (ArticleOffer offer : availableOffers.values()) {
 | 
			
		||||
            offer.shouldBeAdvertised = false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for (Entry<ArticleIdentifier, Offer> cheapestOffer : cheapestOffer.entrySet()) {
 | 
			
		||||
            String manufacturer = cheapestOffer.getKey().manufacturer;
 | 
			
		||||
            String articleNumber = cheapestOffer.getKey().articleNumber;
 | 
			
		||||
            ArticleOffer currentOffer = availableOffers.get(cheapestOffer.getKey());
 | 
			
		||||
            if (currentOffer == null) {
 | 
			
		||||
                currentOffer = new ArticleOffer();
 | 
			
		||||
                currentOffer.manufacturer = manufacturer;
 | 
			
		||||
                currentOffer.articleNumber = articleNumber;
 | 
			
		||||
                offers.add(currentOffer);
 | 
			
		||||
            }
 | 
			
		||||
            Article currentOfferedArticle = cheapestOffer.getValue().apiSupplier.findArticle(manufacturer,
 | 
			
		||||
                    articleNumber);
 | 
			
		||||
            currentOffer.vatPercent = currentOfferedArticle.vatPercent;
 | 
			
		||||
 | 
			
		||||
            // Set advertise-flag if any supplier wants it to be set
 | 
			
		||||
            if (currentOfferedArticle.shouldBeAdvertised) {
 | 
			
		||||
                currentOffer.shouldBeAdvertised = true;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return offers;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,27 @@
 | 
			
		||||
package org.hso.ecommerce.api;
 | 
			
		||||
 | 
			
		||||
import org.hso.ecommerce.api.data.Order;
 | 
			
		||||
import org.hso.ecommerce.api.data.OrderConfirmation;
 | 
			
		||||
import org.hso.ecommerce.api.data.Supplier;
 | 
			
		||||
import org.springframework.web.client.RestTemplate;
 | 
			
		||||
 | 
			
		||||
public class SupplierService {
 | 
			
		||||
 | 
			
		||||
    private final String url;
 | 
			
		||||
 | 
			
		||||
    public SupplierService(String url) {
 | 
			
		||||
        this.url = url;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Supplier getSupplier() {
 | 
			
		||||
        RestTemplate restTemplate = new RestTemplate();
 | 
			
		||||
 | 
			
		||||
        return restTemplate.getForObject(url, Supplier.class);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public OrderConfirmation order(Order order) {
 | 
			
		||||
        RestTemplate restTemplate = new RestTemplate();
 | 
			
		||||
 | 
			
		||||
        return restTemplate.postForObject(url + "/order", order, OrderConfirmation.class);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,11 @@
 | 
			
		||||
package org.hso.ecommerce.api.data;
 | 
			
		||||
 | 
			
		||||
public class Article {
 | 
			
		||||
    public String title;
 | 
			
		||||
    public String manufacturer;
 | 
			
		||||
    public String articleNumber;
 | 
			
		||||
 | 
			
		||||
    public int vatPercent;
 | 
			
		||||
    public int pricePerUnitNet;
 | 
			
		||||
    public boolean shouldBeAdvertised;
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,9 @@
 | 
			
		||||
package org.hso.ecommerce.api.data;
 | 
			
		||||
 | 
			
		||||
public class Order {
 | 
			
		||||
    public String manufacturer;
 | 
			
		||||
    public String articleNumber;
 | 
			
		||||
 | 
			
		||||
    public int quantity;
 | 
			
		||||
    public int maxTotalPriceCentNet;
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,12 @@
 | 
			
		||||
package org.hso.ecommerce.api.data;
 | 
			
		||||
 | 
			
		||||
public class OrderConfirmation {
 | 
			
		||||
    public String manufacturer;
 | 
			
		||||
    public String articleNumber;
 | 
			
		||||
 | 
			
		||||
    public int quantity;
 | 
			
		||||
 | 
			
		||||
    public int pricePerUnitNetCent;
 | 
			
		||||
    public int discountNetCent;
 | 
			
		||||
    public int totalPriceNetCharged;
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,21 @@
 | 
			
		||||
package org.hso.ecommerce.api.data;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
public class Supplier {
 | 
			
		||||
 | 
			
		||||
    public String id;
 | 
			
		||||
    public String name;
 | 
			
		||||
    public SupplierDiscount discount;
 | 
			
		||||
    public List<Article> articles;
 | 
			
		||||
 | 
			
		||||
    public Article findArticle(String manufacturer, String articleNumber) {
 | 
			
		||||
        for (Article a : articles) {
 | 
			
		||||
            if (a.manufacturer.equals(manufacturer) && a.articleNumber.equals(articleNumber)) {
 | 
			
		||||
                return a;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,6 @@
 | 
			
		||||
package org.hso.ecommerce.api.data;
 | 
			
		||||
 | 
			
		||||
public class SupplierDiscount {
 | 
			
		||||
    public int minimumDailySalesVolumeNetCent;
 | 
			
		||||
    public int percentDiscount;
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,331 @@
 | 
			
		||||
package org.hso.ecommerce.controller.cronjob;
 | 
			
		||||
 | 
			
		||||
import java.sql.Timestamp;
 | 
			
		||||
import java.util.Calendar;
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.GregorianCalendar;
 | 
			
		||||
import java.util.HashMap;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.Map.Entry;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.PostConstruct;
 | 
			
		||||
 | 
			
		||||
import org.hso.ecommerce.action.booking.CreateBookingAction;
 | 
			
		||||
import org.hso.ecommerce.action.cronjob.ReadSupplierDataAction;
 | 
			
		||||
import org.hso.ecommerce.action.cronjob.ReadSupplierDataAction.ArticleIdentifier;
 | 
			
		||||
import org.hso.ecommerce.action.cronjob.ReorderAction;
 | 
			
		||||
import org.hso.ecommerce.action.cronjob.UpdateOffersAction;
 | 
			
		||||
import org.hso.ecommerce.entities.booking.Booking;
 | 
			
		||||
import org.hso.ecommerce.entities.booking.BookingAccountEntry;
 | 
			
		||||
import org.hso.ecommerce.entities.booking.BookingReason;
 | 
			
		||||
import org.hso.ecommerce.entities.cron.BackgroundJob;
 | 
			
		||||
import org.hso.ecommerce.entities.shop.Article;
 | 
			
		||||
import org.hso.ecommerce.entities.supplier.ArticleOffer;
 | 
			
		||||
import org.hso.ecommerce.entities.supplier.Supplier;
 | 
			
		||||
import org.hso.ecommerce.entities.supplier.SupplierOrder;
 | 
			
		||||
import org.hso.ecommerce.repos.booking.BookingAccountEntryRepository;
 | 
			
		||||
import org.hso.ecommerce.repos.booking.BookingRepository;
 | 
			
		||||
import org.hso.ecommerce.repos.cronjob.BackgroundJobRepository;
 | 
			
		||||
import org.hso.ecommerce.repos.shop.ArticleRepository;
 | 
			
		||||
import org.hso.ecommerce.repos.shop.CustomerOderRepository;
 | 
			
		||||
import org.hso.ecommerce.repos.supplier.ArticleOfferRepository;
 | 
			
		||||
import org.hso.ecommerce.repos.supplier.SupplierOrderRepository;
 | 
			
		||||
import org.hso.ecommerce.repos.supplier.SupplierRepository;
 | 
			
		||||
import org.hso.ecommerce.repos.warehouse.WarehouseBookingPositionSlotEntryRepository;
 | 
			
		||||
import org.slf4j.Logger;
 | 
			
		||||
import org.slf4j.LoggerFactory;
 | 
			
		||||
import org.springframework.beans.factory.annotation.Autowired;
 | 
			
		||||
import org.springframework.stereotype.Component;
 | 
			
		||||
 | 
			
		||||
interface ICronjob {
 | 
			
		||||
    /**
 | 
			
		||||
     * Calculate the earliest cronjob execution time that happens after the given reference time.
 | 
			
		||||
     *
 | 
			
		||||
     * @param reference Position in time to start searching. The implementor is allowed to modify the reference time.
 | 
			
		||||
     * @return A new Calendar instance (or the same) containing the time for next execution.
 | 
			
		||||
     */
 | 
			
		||||
    Calendar nextExecution(Calendar reference);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Calculate the latest cronjob execution time that happens before or exactly at the given refernce time.
 | 
			
		||||
     *
 | 
			
		||||
     * @param reference Position in time to start searching. The implementor is allowed to modify the reference time.
 | 
			
		||||
     * @return A new Calendar instance (or the same) containing the time of the last execution.
 | 
			
		||||
     */
 | 
			
		||||
    Calendar previousExecution(Calendar reference);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Execute this cronjob.
 | 
			
		||||
     *
 | 
			
		||||
     * @param time       The point in time this execution was scheduled. In case of a missed cronjob, the actual time of
 | 
			
		||||
     *                   this call might be much later.
 | 
			
		||||
     * @param controller Back-reference that allows to use repositories.
 | 
			
		||||
     */
 | 
			
		||||
    void executeAt(Calendar time, CronjobController controller);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Component
 | 
			
		||||
class Reorder implements ICronjob {
 | 
			
		||||
    @Override
 | 
			
		||||
    public Calendar nextExecution(Calendar reference) {
 | 
			
		||||
        if (reference.get(Calendar.HOUR_OF_DAY) >= 8) {
 | 
			
		||||
            reference.add(Calendar.DAY_OF_MONTH, 1);
 | 
			
		||||
        }
 | 
			
		||||
        reference.set(Calendar.HOUR_OF_DAY, 8);
 | 
			
		||||
        reference.set(Calendar.MINUTE, 0);
 | 
			
		||||
        reference.set(Calendar.SECOND, 0);
 | 
			
		||||
        reference.set(Calendar.MILLISECOND, 0);
 | 
			
		||||
        return reference;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public Calendar previousExecution(Calendar reference) {
 | 
			
		||||
        if (reference.get(Calendar.HOUR_OF_DAY) < 8) {
 | 
			
		||||
            reference.add(Calendar.DAY_OF_MONTH, -1);
 | 
			
		||||
        }
 | 
			
		||||
        reference.set(Calendar.HOUR_OF_DAY, 8);
 | 
			
		||||
        reference.set(Calendar.MINUTE, 0);
 | 
			
		||||
        reference.set(Calendar.SECOND, 0);
 | 
			
		||||
        reference.set(Calendar.MILLISECOND, 0);
 | 
			
		||||
        return reference;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Calculates the amount of ordered articles by customers for the given article type in the time between begin and
 | 
			
		||||
     * end.
 | 
			
		||||
     *
 | 
			
		||||
     * @param article The article to search orders for.
 | 
			
		||||
     * @param begin   The start time for the search (included)
 | 
			
		||||
     * @param end     The end time for the search (excluded)
 | 
			
		||||
     * @return The number of articles that were ordered by customers in the given range.
 | 
			
		||||
     */
 | 
			
		||||
    private Integer getOrderedAmounts(Article article, Calendar begin, Calendar end, CronjobController controller) {
 | 
			
		||||
        return controller.customerOrderRepository.countOrdersOfArticleInTimespan(
 | 
			
		||||
                article.id,
 | 
			
		||||
                new Timestamp(begin.getTimeInMillis()), new Timestamp(end.getTimeInMillis()));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Calculates the amount of ordered articles by customers for the given article type in the three days before the
 | 
			
		||||
     * given reference time. The return-array contains 3 fields: Index 0: Orders 72 to 48 hours ago; Index 1: Orders 48
 | 
			
		||||
     * to 24 hours ago; Index 2: Orders 24 to 0 hours ago.
 | 
			
		||||
     *
 | 
			
		||||
     * @param article The article for which the customer orders are checked.
 | 
			
		||||
     * @param time    The reference time to use for calculation of the last orders.
 | 
			
		||||
     * @return A 3-element array containing the orders of the last three days.
 | 
			
		||||
     */
 | 
			
		||||
    private Integer[] getOrderedAmounts(Article article, Calendar time, CronjobController controller) {
 | 
			
		||||
        Calendar oneDayBefore = (Calendar) time.clone();
 | 
			
		||||
        oneDayBefore.add(Calendar.DAY_OF_MONTH, -1);
 | 
			
		||||
        Calendar twoDaysBefore = (Calendar) time.clone();
 | 
			
		||||
        twoDaysBefore.add(Calendar.DAY_OF_MONTH, -2);
 | 
			
		||||
        Calendar threeDaysBefore = (Calendar) time.clone();
 | 
			
		||||
        threeDaysBefore.add(Calendar.DAY_OF_MONTH, -3);
 | 
			
		||||
 | 
			
		||||
        return new Integer[] { //
 | 
			
		||||
                getOrderedAmounts(article, threeDaysBefore, twoDaysBefore, controller), //
 | 
			
		||||
                getOrderedAmounts(article, twoDaysBefore, oneDayBefore, controller), //
 | 
			
		||||
                getOrderedAmounts(article, oneDayBefore, time, controller), //
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private HashMap<ArticleIdentifier, ArticleOffer> mapArticleOffers(List<ArticleOffer> articleOffers) {
 | 
			
		||||
        HashMap<ArticleIdentifier, ArticleOffer> map = new HashMap<>();
 | 
			
		||||
        for (ArticleOffer articleOffer : articleOffers) {
 | 
			
		||||
            ArticleIdentifier identifier = new ArticleIdentifier(articleOffer.manufacturer, articleOffer.articleNumber);
 | 
			
		||||
            map.put(identifier, articleOffer);
 | 
			
		||||
        }
 | 
			
		||||
        return map;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void executeAt(Calendar time, CronjobController controller) {
 | 
			
		||||
        List<Supplier> suppliers = controller.supplierRepository.findAll();
 | 
			
		||||
        ReadSupplierDataAction.Result supplierData = new ReadSupplierDataAction(suppliers).finish();
 | 
			
		||||
 | 
			
		||||
        // Save the new offers in the database
 | 
			
		||||
        List<ArticleOffer> allOffers = controller.articleOfferRepository.findAll();
 | 
			
		||||
        allOffers = new UpdateOffersAction(allOffers, supplierData.cheapestOffer).finish();
 | 
			
		||||
        controller.articleOfferRepository.saveAll(allOffers);
 | 
			
		||||
 | 
			
		||||
        HashMap<ArticleIdentifier, ArticleOffer> mappedOffers = mapArticleOffers(allOffers);
 | 
			
		||||
 | 
			
		||||
        // Reorder
 | 
			
		||||
        List<Article> allArticles = controller.articleRepository.findAll();
 | 
			
		||||
        for (Article article : allArticles) {
 | 
			
		||||
            Integer[] orderedAmounts = getOrderedAmounts(article, time, controller);
 | 
			
		||||
 | 
			
		||||
            Integer undeliveredReorders = controller.supplierOrderRepository
 | 
			
		||||
                    .countUndeliveredReorders(article.related.articleNumber);
 | 
			
		||||
 | 
			
		||||
            int amountInStock = controller.warehouseBookingPositionSlotEntryRepository.getArticleStock(article.id)
 | 
			
		||||
                    .orElse(0);
 | 
			
		||||
 | 
			
		||||
            ReorderAction action = new ReorderAction(article, orderedAmounts,
 | 
			
		||||
                    undeliveredReorders,
 | 
			
		||||
                    amountInStock,
 | 
			
		||||
                    supplierData.cheapestOffer, mappedOffers);
 | 
			
		||||
            SupplierOrder order = action.finish();
 | 
			
		||||
            if (order != null) {
 | 
			
		||||
                controller.supplierOrderRepository.save(order);
 | 
			
		||||
 | 
			
		||||
                // Create bookings for this order
 | 
			
		||||
                int netPrice = order.totalPriceNet;
 | 
			
		||||
                int vatPercent = order.ordered.vatPercent;
 | 
			
		||||
                int vatAmount = netPrice * vatPercent / 100;
 | 
			
		||||
                int grossPrice = netPrice + vatAmount;
 | 
			
		||||
 | 
			
		||||
                // Obligation towards the supplier
 | 
			
		||||
                BookingAccountEntry mainAccount = controller.bookingAccountEntryRepository.getByMain()
 | 
			
		||||
                        .orElseGet(BookingAccountEntry::newMain);
 | 
			
		||||
                BookingAccountEntry supplierAccount = controller.bookingAccountEntryRepository
 | 
			
		||||
                        .getBySupplier(order.supplier.id)
 | 
			
		||||
                        .orElseGet(() -> BookingAccountEntry.newSupplier(order.supplier));
 | 
			
		||||
                BookingReason obligationReason = new BookingReason(order);
 | 
			
		||||
                Booking obligationBooking = new CreateBookingAction(mainAccount,
 | 
			
		||||
                        supplierAccount,
 | 
			
		||||
                        obligationReason,
 | 
			
		||||
                        grossPrice).finish();
 | 
			
		||||
                controller.bookingRepository.save(obligationBooking);
 | 
			
		||||
 | 
			
		||||
                // Input Tax
 | 
			
		||||
                BookingAccountEntry vatAccount = controller.bookingAccountEntryRepository.getByVat()
 | 
			
		||||
                        .orElseGet(BookingAccountEntry::newVat);
 | 
			
		||||
                mainAccount = controller.bookingAccountEntryRepository.getByMain().get();
 | 
			
		||||
                BookingReason inputTaxReason = new BookingReason(order);
 | 
			
		||||
                Booking inputTaxBooking = new CreateBookingAction(vatAccount, mainAccount, inputTaxReason, vatAmount)
 | 
			
		||||
                        .finish();
 | 
			
		||||
                controller.bookingRepository.save(inputTaxBooking);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class ScheduledCronjob {
 | 
			
		||||
    public final Calendar executionTime;
 | 
			
		||||
    public final ICronjob cronjob;
 | 
			
		||||
    public final BackgroundJob model;
 | 
			
		||||
 | 
			
		||||
    public ScheduledCronjob(Calendar executionTime, ICronjob cronjob, BackgroundJob model) {
 | 
			
		||||
        this.executionTime = executionTime;
 | 
			
		||||
        this.cronjob = cronjob;
 | 
			
		||||
        this.model = model;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Component
 | 
			
		||||
class CronjobController {
 | 
			
		||||
    private static final Logger log = LoggerFactory.getLogger(CronjobController.class);
 | 
			
		||||
 | 
			
		||||
    private static final Map<String, ICronjob> cronjobs = getCronjobs();
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    private final BackgroundJobRepository cronjobRepository = null;
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    final ArticleRepository articleRepository = null;
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    final ArticleOfferRepository articleOfferRepository = null;
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    final CustomerOderRepository customerOrderRepository = null;
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    final BookingRepository bookingRepository = null;
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    final BookingAccountEntryRepository bookingAccountEntryRepository = null;
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    final WarehouseBookingPositionSlotEntryRepository warehouseBookingPositionSlotEntryRepository = null;
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    final SupplierRepository supplierRepository = null;
 | 
			
		||||
 | 
			
		||||
    @Autowired
 | 
			
		||||
    final SupplierOrderRepository supplierOrderRepository = null;
 | 
			
		||||
 | 
			
		||||
    private static Map<String, ICronjob> getCronjobs() {
 | 
			
		||||
        HashMap<String, ICronjob> map = new HashMap<>();
 | 
			
		||||
 | 
			
		||||
        // Register all existing cronjobs
 | 
			
		||||
        map.put(BackgroundJob.JOB_REORDER, new Reorder());
 | 
			
		||||
 | 
			
		||||
        return Collections.unmodifiableMap(map);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private ScheduledCronjob getNextCronjob() {
 | 
			
		||||
        Calendar currentTime = new GregorianCalendar();
 | 
			
		||||
        Iterable<BackgroundJob> jobs = cronjobRepository.findAll();
 | 
			
		||||
        HashMap<String, BackgroundJob> alreadyExecuted = new HashMap<>();
 | 
			
		||||
        for (BackgroundJob job : jobs) {
 | 
			
		||||
            alreadyExecuted.put(job.jobName, job);
 | 
			
		||||
        }
 | 
			
		||||
        ScheduledCronjob earliestJob = null;
 | 
			
		||||
        for (Entry<String, ICronjob> entry : cronjobs.entrySet()) {
 | 
			
		||||
            ScheduledCronjob resultingJob;
 | 
			
		||||
            BackgroundJob dbEntry = alreadyExecuted.get(entry.getKey());
 | 
			
		||||
            if (dbEntry != null) {
 | 
			
		||||
                Calendar previousExecution = new GregorianCalendar();
 | 
			
		||||
                previousExecution.setTimeInMillis(dbEntry.lastExecution.getTime());
 | 
			
		||||
                Calendar followingSchedule = entry.getValue().nextExecution((Calendar) previousExecution.clone());
 | 
			
		||||
                Calendar lastSchedule = entry.getValue().previousExecution((Calendar) currentTime.clone());
 | 
			
		||||
                if (lastSchedule.getTimeInMillis() > followingSchedule.getTimeInMillis()) {
 | 
			
		||||
                    // This happens, if more than one execution was missed.
 | 
			
		||||
                    // In this case, run the job only once.
 | 
			
		||||
                    followingSchedule = lastSchedule;
 | 
			
		||||
                }
 | 
			
		||||
                resultingJob = new ScheduledCronjob(followingSchedule, entry.getValue(), dbEntry);
 | 
			
		||||
            } else {
 | 
			
		||||
                // This cronjob has never been executed before.
 | 
			
		||||
                Calendar lastScheduleTime = entry.getValue().previousExecution((Calendar) currentTime.clone());
 | 
			
		||||
                BackgroundJob model = new BackgroundJob();
 | 
			
		||||
                model.jobName = entry.getKey();
 | 
			
		||||
                resultingJob = new ScheduledCronjob(lastScheduleTime, entry.getValue(), model);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Look for the job with earliest executionTime - it will run next
 | 
			
		||||
            if (earliestJob == null
 | 
			
		||||
                    || resultingJob.executionTime.getTimeInMillis() < earliestJob.executionTime.getTimeInMillis()) {
 | 
			
		||||
                earliestJob = resultingJob;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return earliestJob;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void runCronjobExecutionLoop() {
 | 
			
		||||
        Thread.currentThread().setName("Cronjob");
 | 
			
		||||
        try {
 | 
			
		||||
            while (true) {
 | 
			
		||||
                ScheduledCronjob nextJob = getNextCronjob();
 | 
			
		||||
                if (nextJob == null) {
 | 
			
		||||
                    // In case there are no cronjobs
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                long waitingTime = nextJob.executionTime.getTimeInMillis() - System.currentTimeMillis();
 | 
			
		||||
                if (waitingTime > 0) {
 | 
			
		||||
                    Thread.sleep(waitingTime);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                try {
 | 
			
		||||
                    nextJob.cronjob.executeAt(nextJob.executionTime, this);
 | 
			
		||||
                } catch (Throwable t) {
 | 
			
		||||
                    log.error("Failed to execute cronjob " + nextJob.cronjob.getClass() + ":");
 | 
			
		||||
                    t.printStackTrace();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                nextJob.model.lastExecution = new Timestamp(nextJob.executionTime.getTimeInMillis());
 | 
			
		||||
                cronjobRepository.save(nextJob.model);
 | 
			
		||||
            }
 | 
			
		||||
        } catch (InterruptedException e) {
 | 
			
		||||
            log.error("The cronjob execution thread has been interrupted");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @PostConstruct
 | 
			
		||||
    public void onPostConstruct() {
 | 
			
		||||
        new Thread(this::runCronjobExecutionLoop).start();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -83,7 +83,7 @@ public class InternArticleController {
 | 
			
		||||
			@RequestParam(value = "price_netto", required = true) String pricenetto,
 | 
			
		||||
			@RequestParam(value = "reorderMaxPrice", required = true) String reorderMaxPrice,
 | 
			
		||||
			@RequestParam(value = "autobuy", required = true) Boolean shouldReorder,
 | 
			
		||||
			@RequestParam(value = "categories", required = true) String categories,
 | 
			
		||||
			@RequestParam(value = "categorie", required = true) String categories,
 | 
			
		||||
			@RequestParam(value = "img", required = true) MultipartFile imgFile) {
 | 
			
		||||
 | 
			
		||||
		Article tmpArticle = articleRepository.findArticleById(id); // get the old article
 | 
			
		||||
@ -93,7 +93,7 @@ public class InternArticleController {
 | 
			
		||||
		tmpArticle.categories.clear();
 | 
			
		||||
 | 
			
		||||
		// loop through all categories strings and create a new category if a new one;
 | 
			
		||||
		// also adds the categorys to the article
 | 
			
		||||
		// also adds the categories to the article
 | 
			
		||||
		for (String category : separatedCategories) {
 | 
			
		||||
			tmpArticle.categories.add(categoryRepository.findCategoryByName(category.trim())
 | 
			
		||||
					.orElseGet(() -> new Category(category.trim())));
 | 
			
		||||
 | 
			
		||||
@ -16,10 +16,10 @@ public class BookingAccountEntry {
 | 
			
		||||
 | 
			
		||||
    public int newSumCent;
 | 
			
		||||
 | 
			
		||||
    @ManyToOne(optional = true, cascade = CascadeType.ALL)
 | 
			
		||||
    @ManyToOne(optional = true, cascade = CascadeType.MERGE)
 | 
			
		||||
    public User userAccount;
 | 
			
		||||
 | 
			
		||||
    @ManyToOne(optional = true, cascade = CascadeType.ALL)
 | 
			
		||||
    @ManyToOne(optional = true, cascade = CascadeType.MERGE)
 | 
			
		||||
    public Supplier supplierAccount;
 | 
			
		||||
 | 
			
		||||
    public boolean isMainAccount;
 | 
			
		||||
@ -46,6 +46,14 @@ public class BookingAccountEntry {
 | 
			
		||||
        return e;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static BookingAccountEntry newSupplier(Supplier supplier) {
 | 
			
		||||
        BookingAccountEntry e = new BookingAccountEntry();
 | 
			
		||||
        e.supplierAccount = supplier;
 | 
			
		||||
        e.newSumCent = 0;
 | 
			
		||||
 | 
			
		||||
        return e;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static BookingAccountEntry newMain() {
 | 
			
		||||
        BookingAccountEntry e = new BookingAccountEntry();
 | 
			
		||||
        e.isMainAccount = true;
 | 
			
		||||
 | 
			
		||||
@ -39,4 +39,8 @@ public class BookingReason {
 | 
			
		||||
    public BookingReason(CustomerPayment customerPayment) {
 | 
			
		||||
        this.customerPayment = customerPayment;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public BookingReason(SupplierOrder supplierOrder) {
 | 
			
		||||
        this.supplierOrder = supplierOrder;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -7,8 +7,8 @@ import javax.validation.constraints.NotNull;
 | 
			
		||||
@Table(name = "background_jobs")
 | 
			
		||||
public class BackgroundJob {
 | 
			
		||||
 | 
			
		||||
    public final String JOB_DASHBOARD = "Dashboard";
 | 
			
		||||
    public final String JOB_REORDER = "SupplierOrder";
 | 
			
		||||
    public static final String JOB_DASHBOARD = "Dashboard";
 | 
			
		||||
    public static final String JOB_REORDER = "SupplierOrder";
 | 
			
		||||
 | 
			
		||||
    @Id
 | 
			
		||||
    @GeneratedValue(strategy = GenerationType.IDENTITY)
 | 
			
		||||
 | 
			
		||||
@ -1,18 +1,21 @@
 | 
			
		||||
package org.hso.ecommerce.repos.booking;
 | 
			
		||||
 | 
			
		||||
import java.util.Optional;
 | 
			
		||||
 | 
			
		||||
import org.hso.ecommerce.entities.booking.BookingAccountEntry;
 | 
			
		||||
import org.springframework.data.jpa.repository.JpaRepository;
 | 
			
		||||
import org.springframework.data.jpa.repository.Query;
 | 
			
		||||
import org.springframework.stereotype.Repository;
 | 
			
		||||
 | 
			
		||||
import java.util.Optional;
 | 
			
		||||
 | 
			
		||||
@Repository
 | 
			
		||||
public interface BookingAccountEntryRepository extends JpaRepository<BookingAccountEntry, Long> {
 | 
			
		||||
 | 
			
		||||
    @Query(value = "SELECT * FROM booking_account_entries as e WHERE e.user_account_id = :user ORDER BY e.id DESC LIMIT 1", nativeQuery = true)
 | 
			
		||||
    Optional<BookingAccountEntry> getByUser(Long user);
 | 
			
		||||
 | 
			
		||||
    @Query(value = "SELECT * FROM booking_account_entries as e WHERE e.supplier_account_id = :supplier ORDER BY e.id DESC LIMIT 1", nativeQuery = true)
 | 
			
		||||
    Optional<BookingAccountEntry> getBySupplier(Long supplier);
 | 
			
		||||
 | 
			
		||||
    @Query(value = "SELECT * FROM booking_account_entries as e WHERE e.is_main_account = 1 ORDER BY e.id DESC LIMIT 1", nativeQuery = true)
 | 
			
		||||
    Optional<BookingAccountEntry> getByMain();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,10 @@
 | 
			
		||||
package org.hso.ecommerce.repos.cronjob;
 | 
			
		||||
 | 
			
		||||
import org.hso.ecommerce.entities.cron.BackgroundJob;
 | 
			
		||||
import org.springframework.data.jpa.repository.JpaRepository;
 | 
			
		||||
import org.springframework.stereotype.Repository;
 | 
			
		||||
 | 
			
		||||
@Repository
 | 
			
		||||
public interface BackgroundJobRepository extends JpaRepository<BackgroundJob, Long> {
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,8 @@
 | 
			
		||||
package org.hso.ecommerce.repos.supplier;
 | 
			
		||||
 | 
			
		||||
import org.hso.ecommerce.entities.supplier.ArticleOffer;
 | 
			
		||||
import org.springframework.data.jpa.repository.JpaRepository;
 | 
			
		||||
 | 
			
		||||
public interface ArticleOfferRepository extends JpaRepository<ArticleOffer, Long> {
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,14 @@
 | 
			
		||||
package org.hso.ecommerce.repos.supplier;
 | 
			
		||||
 | 
			
		||||
import org.hso.ecommerce.entities.supplier.SupplierOrder;
 | 
			
		||||
import org.springframework.data.jpa.repository.JpaRepository;
 | 
			
		||||
import org.springframework.data.jpa.repository.Query;
 | 
			
		||||
import org.springframework.stereotype.Repository;
 | 
			
		||||
 | 
			
		||||
@Repository
 | 
			
		||||
public interface SupplierOrderRepository extends JpaRepository<SupplierOrder, Long> {
 | 
			
		||||
 | 
			
		||||
    @Query("SELECT SUM(so.numberOfUnits) FROM SupplierOrder so JOIN so.ordered ao WHERE ao.articleNumber = :articleNumber AND so.delivered IS NULL")
 | 
			
		||||
    Integer countUndeliveredReorders(String articleNumber);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,10 @@
 | 
			
		||||
package org.hso.ecommerce.repos.supplier;
 | 
			
		||||
 | 
			
		||||
import org.hso.ecommerce.entities.supplier.Supplier;
 | 
			
		||||
import org.springframework.data.jpa.repository.JpaRepository;
 | 
			
		||||
import org.springframework.stereotype.Repository;
 | 
			
		||||
 | 
			
		||||
@Repository
 | 
			
		||||
public interface SupplierRepository extends JpaRepository<Supplier, Long> {
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -24,21 +24,21 @@
 | 
			
		||||
            <form class="detailgrid" action="#" th:action="@{/intern/articles/{id}/saveChanges(id = ${ArticleID.id})}" th:object="${ArticleID}" method="POST" enctype="multipart/form-data">
 | 
			
		||||
               <p class="m">
 | 
			
		||||
                  <label for="title">Titel</label>
 | 
			
		||||
                  <input class=" full-width" type="text" id="title" name="title" required="required" pattern="[A-Za-z0-9]{1,20}" th:value="${ArticleID.title}"/>
 | 
			
		||||
                  <input class=" full-width" type="text" id="title" name="title" required="required" pattern=".+" th:value="${ArticleID.title}"/>
 | 
			
		||||
               </p>
 | 
			
		||||
               <p class="s">
 | 
			
		||||
                  <label for="ref-article">Refernzierter Artikel</label>
 | 
			
		||||
                  <label for="ref_disabled">Refernzierter Artikel</label>
 | 
			
		||||
                  <input class="" type="text" id="ref_disabled" th:value="${ArticleID.offer_id}" disabled/>
 | 
			
		||||
                  
 | 
			
		||||
                  <input type="hidden" id="ref_hidden" th:value="${ArticleID.offer_id}" name="ref-article" />
 | 
			
		||||
                  
 | 
			
		||||
               <td><a th:href="${'/intern/supplierOffers/#q=' + {ArticleID.id}}">Details</a></td>
 | 
			
		||||
               <a th:href="${'/intern/supplierOffers/#q=' + ArticleID.id}">Details</a>
 | 
			
		||||
               </p>
 | 
			
		||||
               <div class="spacer"></div>
 | 
			
		||||
               <div class="m">
 | 
			
		||||
                  <p>
 | 
			
		||||
                     <label for="img">Bild Hochladen</label>
 | 
			
		||||
                     <input class="full-width" type="file" id="image" name="img"/>
 | 
			
		||||
                     <input class="full-width" type="file" id="img" name="img"/>
 | 
			
		||||
                  </p>
 | 
			
		||||
                  <p>
 | 
			
		||||
                     <img th:src="@{/shop/articles/{id}/image.jpg(id=${ArticleID.id})}" class="m"/>
 | 
			
		||||
@ -47,14 +47,14 @@
 | 
			
		||||
               <div class="s">
 | 
			
		||||
                  <p>
 | 
			
		||||
                     <label for="price">Preis (Netto)</label>
 | 
			
		||||
                     <input class="" type="number" step="0.01" name="price_netto" th:value="${ArticleID.price_netto}"/> EUR <br/>
 | 
			
		||||
                     <input class="" type="number" id="price" step="0.01" name="price_netto" required th:value="${ArticleID.price_netto}"/> EUR <br/>
 | 
			
		||||
                     (19% Mwst.)
 | 
			
		||||
                     <!-- Info von article ref--> <br/>
 | 
			
		||||
                     = <span th:text="${ArticleID.price}"></span> EUR Brutto
 | 
			
		||||
                  </p>
 | 
			
		||||
                  <p>
 | 
			
		||||
                     <label for="max-price-buy">Maximaler Einkaufspreis (Netto)</label>
 | 
			
		||||
                     <input class="" type="number" id="reorderMaxPrice" step="0.01" name="reorderMaxPrice" th:value="${ArticleID.reorderMaxPrice}"/> EUR
 | 
			
		||||
                     <label for="reorderMaxPrice">Maximaler Einkaufspreis (Netto)</label>
 | 
			
		||||
                     <input class="" type="number" id="reorderMaxPrice" step="0.01" required name="reorderMaxPrice" th:value="${ArticleID.reorderMaxPrice}"/> EUR
 | 
			
		||||
                  </p>
 | 
			
		||||
                  <div>
 | 
			
		||||
                     <fieldset>
 | 
			
		||||
@ -66,18 +66,18 @@
 | 
			
		||||
                  </div>
 | 
			
		||||
               </div>
 | 
			
		||||
               <div class="m">
 | 
			
		||||
                  <label for="tags">Kategorien</label>
 | 
			
		||||
                  <label for="categorie">Kategorien</label>
 | 
			
		||||
                  <p>
 | 
			
		||||
                     Bitte jede Kategorien in eine eigene Zeile
 | 
			
		||||
                  </p>
 | 
			
		||||
                  <textarea name="categories" id="categories" class="full-width" rows="6"th:inline="text">[[${ArticleID.categorie}]]
 | 
			
		||||
                  <textarea name="categorie" id="categorie" class="full-width" required rows="6" th:inline="text" th:field="${ArticleID.categorie}">
 | 
			
		||||
 | 
			
		||||
               </textarea>
 | 
			
		||||
   </textarea>
 | 
			
		||||
               </div>
 | 
			
		||||
               <div class="s">
 | 
			
		||||
                  <p>
 | 
			
		||||
                     <label for="price">Einheiten pro Lagerplatz</label>
 | 
			
		||||
                     <input class="" type="number" id="units-per-slot" name="units-per-slot" th:value="${ArticleID.warehouseUnitsPerSlot}"/>                    
 | 
			
		||||
                     <label for="units-per-slot">Einheiten pro Lagerplatz</label>
 | 
			
		||||
                     <input class="" type="number" id="units-per-slot" required name="units-per-slot" th:value="${ArticleID.warehouseUnitsPerSlot}"/>                    
 | 
			
		||||
                  </p>
 | 
			
		||||
                  <p>
 | 
			
		||||
                     <b>Lagerbestand: <span th:text="${ArticleID.stock}"></span></b>
 | 
			
		||||
@ -88,12 +88,12 @@
 | 
			
		||||
                     <!-- TODO: set link g-->
 | 
			
		||||
                  </p>
 | 
			
		||||
                  <p>
 | 
			
		||||
                     <a href="/todo" class="button smaller">Lagerbuchung</a>
 | 
			
		||||
                     <a href="/intern/warehouse/" class="button smaller">Lagerbuchung</a>
 | 
			
		||||
                  </p>
 | 
			
		||||
               </div>
 | 
			
		||||
               <p class="l">
 | 
			
		||||
                  <label for="description">Beschreibung</label>
 | 
			
		||||
                  <textarea name="description" id="description" class="full-width" rows="15" th:inline="text">[[${ArticleID.description}]]
 | 
			
		||||
 				<textarea name="description" id="description" class="full-width" required th:field="${ArticleID.description}" rows="15" th:inline="text">
 | 
			
		||||
                  </textarea>
 | 
			
		||||
               </p>
 | 
			
		||||
               <div class="l">
 | 
			
		||||
 | 
			
		||||
@ -29,7 +29,7 @@
 | 
			
		||||
               <tr>
 | 
			
		||||
                  <th colspan="9">
 | 
			
		||||
                     <input type="text" placeholder="Filtern" class="smaller jsFilterTable full-width"
 | 
			
		||||
                        data-target-id="main-table"></input>
 | 
			
		||||
                            data-target-id="main-table"/>
 | 
			
		||||
                  </th>
 | 
			
		||||
               </tr>
 | 
			
		||||
               <thead>
 | 
			
		||||
@ -53,10 +53,10 @@
 | 
			
		||||
                     <td><span th:text="${article.price_netto}"></span> €</td>
 | 
			
		||||
                     <td><span th:text="${article.categorie}"></span></td>
 | 
			
		||||
                     <td><span th:text="${article.stock}"></span></td>
 | 
			
		||||
                     <td><a th:href="${'/intern/supplierOffers/#q=' + {article.title}}" th:text="${article.offer_id}"></a></td>
 | 
			
		||||
                     <td><a th:href="${'/intern/supplierOffers/#q=' + article.title}" th:text="${article.offer_id}"></a></td>
 | 
			
		||||
                     <td><a th:href="@{/intern/articles/{id}(id = ${article.id})}" th:text="${article.id}"></a></td>
 | 
			
		||||
                     <td>
 | 
			
		||||
                        <form th:action="@{/intern/articles/{id}(id = ${article.id})}"><input type="submit" value="Bearbeiten" /></form>
 | 
			
		||||
                        <form th:action="@{/intern/articles/{id}(id = ${article.id})}"><input class="button smaller" type="submit" value="Bearbeiten" /></form>
 | 
			
		||||
                     </td>
 | 
			
		||||
                  </tr>
 | 
			
		||||
               </tbody>
 | 
			
		||||
 | 
			
		||||
@ -54,7 +54,7 @@
 | 
			
		||||
                        <!-- ELSE -->
 | 
			
		||||
                        <div th:unless="${article.offerIsListed}">
 | 
			
		||||
                           <form class="detailgrid" action="#" th:action="@{/intern/articles/addArticle/{id}(id = ${article.offer_id})}" method="POST">
 | 
			
		||||
                              <input type="submit" value="Hinzufügen" />
 | 
			
		||||
                              <input class="button smaller" type="submit" value="Hinzufügen" />
 | 
			
		||||
                           </form>
 | 
			
		||||
                        </div>
 | 
			
		||||
                     </td>
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								prototype/src/main/resources/test.db
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								prototype/src/main/resources/test.db
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
		Reference in New Issue
	
	Block a user