feature/reorder_cronjob #27
|
@ -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;
|
||||
Seil0
commented
Ist redundant -> Ist redundant -> `return n - i - l;`
|
||||
}
|
||||
|
||||
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) {
|
||||
Seil0
commented
Wird nicht benutzt. Wird nicht benutzt.
|
||||
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), //
|
||||
Seil0
commented
Für was sind die Für was sind die `//`?
Lukas
commented
Damit der Auto-Formatter das nicht umbaut. Damit der Auto-Formatter das nicht umbaut.
|
||||
};
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
|
@ -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> {
|
||||
|
||||
Seil0
commented
Was macht dieses Repository? Was macht dieses Repository?
Lukas
commented
Das wird in CronjobController.java:264 genutzt. Bitte um Rückmeldung, falls das einfacher geht. Das wird in [CronjobController.java:264](https://git.mosad.xyz/localhorst/e-commerce/src/commit/ffb683bdd0891ef6c376add65ea00b10e7595145/prototype/src/main/java/org/hso/ecommerce/controller/cronjob/CronjobController.java#L264) genutzt.
Bitte um Rückmeldung, falls das einfacher geht.
|
||||
}
|
|
@ -2,10 +2,16 @@ package org.hso.ecommerce.repos.shop;
|
|||
|
||||
import org.hso.ecommerce.entities.shop.CustomerOrder;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface CustomerOderRepository extends JpaRepository<CustomerOrder, Long> {
|
||||
|
||||
@Query("SELECT SUM(cop.quantity) FROM CustomerOrderPosition cop JOIN cop.order co WHERE cop.article.id = :articleId AND co.created >= :begin AND co.created < :end")
|
||||
Integer countOrdersOfArticleInTimespan(
|
||||
long articleId, java.sql.Timestamp begin, java.sql.Timestamp end
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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> {
|
||||
|
||||
Seil0
commented
Was macht dieses Repository? Was macht dieses Repository?
Lukas
commented
Gleicher Fall: Wird in CronjobController.java:151 genutzt. Gleicher Fall: Wird in [CronjobController.java:151](https://git.mosad.xyz/localhorst/e-commerce/src/commit/ffb683bdd0891ef6c376add65ea00b10e7595145/prototype/src/main/java/org/hso/ecommerce/controller/cronjob/CronjobController.java#L151) genutzt.
|
||||
}
|
|
@ -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> {
|
||||
|
||||
Seil0
commented
Was macht dieses Repository? Was macht dieses Repository?
Lukas
commented
Gleicher Fall: Wird in CronjobController.java:147 genutzt. Gleicher Fall: Wird in [CronjobController.java:147](https://git.mosad.xyz/localhorst/e-commerce/src/commit/ffb683bdd0891ef6c376add65ea00b10e7595145/prototype/src/main/java/org/hso/ecommerce/controller/cronjob/CronjobController.java#L147) genutzt.
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
HELP.md
|
||||
.gradle
|
||||
build/
|
||||
!gradle/wrapper/gradle-wrapper.jar
|
||||
Seil0
commented
Der wrapper gehört ins Repo. Der wrapper gehört ins Repo.
Lukas
commented
@CodeSteak Die Datei gibts bei mir nicht und wird beim Ausführen auch nicht angelegt. Der Service läuft trotzdem. Was hat es mit dem Eintrag auf sich? @CodeSteak
Das kommt aus der supplier Api, die du geschreiben hast. (Hab die hier dazu gemerged)
Die Datei gibts bei mir nicht und wird beim Ausführen auch nicht angelegt. Der Service läuft trotzdem. Was hat es mit dem Eintrag auf sich?
CodeSteak
commented
Das nicht "!" excluded das exclude. Ist das Default .gitignore vom . Das nicht "!" excluded das exclude. Ist das Default .gitignore vom <Generator Name>.
CodeSteak
commented
*Default vom generator *Default vom generator
Seil0
commented
Der Ordner Der Ordner `gradle/wrapper ` fehlt aber im supplier Projekt.
CodeSteak
commented
Fail, fixed. Fail, fixed.
|
||||
!**/src/main/**
|
||||
!**/src/test/**
|
||||
|
||||
### STS ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
out/
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
|
@ -0,0 +1,29 @@
|
|||
plugins {
|
||||
id 'org.springframework.boot' version '2.2.7.RELEASE'
|
||||
id 'io.spring.dependency-management' version '1.0.9.RELEASE'
|
||||
id 'java'
|
||||
}
|
||||
|
||||
group = 'org.hso.ecommerce'
|
||||
version = '0.0.1-SNAPSHOT'
|
||||
sourceCompatibility = '11'
|
||||
|
||||
apply plugin: 'java'
|
||||
apply plugin: 'idea'
|
||||
apply plugin: 'org.springframework.boot'
|
||||
apply plugin: 'io.spring.dependency-management'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'org.springframework.boot:spring-boot-starter-web'
|
||||
testImplementation('org.springframework.boot:spring-boot-starter-test') {
|
||||
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
|
||||
}
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"id" : "bank",
|
||||
"name" : "Bank of Cheese",
|
||||
"discount" : {
|
||||
"minimumDailySalesVolumeNetCent": 100,
|
||||
"percentDiscount": 1
|
||||
},
|
||||
"articles": [
|
||||
{
|
||||
"title": "Big Mac",
|
||||
"manufacturer": "Mc Donalds",
|
||||
"articleNumber": "0x1 BIGMAC",
|
||||
"vatPercent": 7,
|
||||
"pricePerUnitNet": 700,
|
||||
"shouldBeAdvertised": true
|
||||
},
|
||||
{
|
||||
"title": "500£ Schein",
|
||||
"manufacturer": "Bank",
|
||||
"articleNumber": "500",
|
||||
"vatPercent": 0,
|
||||
"pricePerUnitNet": 50000,
|
||||
"shouldBeAdvertised": false
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"id" : "hans",
|
||||
"name" : "Hans and more",
|
||||
"discount" : {
|
||||
"minimumDailySalesVolumeNetCent": 100000,
|
||||
"percentDiscount": 2
|
||||
},
|
||||
"articles": [
|
||||
{
|
||||
"title": "Big Mac",
|
||||
"manufacturer": "Mc Donalds",
|
||||
"articleNumber": "0x1 BIGMAC",
|
||||
"vatPercent": 7,
|
||||
"pricePerUnitNet": 700,
|
||||
"shouldBeAdvertised": true
|
||||
},
|
||||
{
|
||||
"title": "Pommes",
|
||||
"manufacturer": "Mc Donalds",
|
||||
"articleNumber": "0x1 POmes",
|
||||
"vatPercent": 7,
|
||||
"pricePerUnitNet": 100,
|
||||
"shouldBeAdvertised": false
|
||||
},
|
||||
{
|
||||
"title": "Milchshake Premium 19%",
|
||||
"manufacturer": "Mc Donalds",
|
||||
"articleNumber": "0x2",
|
||||
"vatPercent": 19,
|
||||
"pricePerUnitNet": 50,
|
||||
"shouldBeAdvertised": true
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.4-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
|
@ -0,0 +1,185 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
#
|
||||
# Copyright 2015 the original author or authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=`expr $i + 1`
|
||||
done
|
||||
case $i in
|
||||
0) set -- ;;
|
||||
1) set -- "$args0" ;;
|
||||
2) set -- "$args0" "$args1" ;;
|
||||
3) set -- "$args0" "$args1" "$args2" ;;
|
||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=`save "$@"`
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
exec "$JAVACMD" "$@"
|
|
@ -0,0 +1,104 @@
|
|||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windows variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
|
@ -0,0 +1 @@
|
|||
rootProject.name = 'supplier'
|
|
@ -0,0 +1,11 @@
|
|||
package org.hso.ecommerce.supplier;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class App {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(App.class, args);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
package org.hso.ecommerce.supplier;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.hso.ecommerce.supplier.data.Supplier;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class ConfigurationReader {
|
||||
|
||||
public static List<Supplier> read() throws IOException {
|
||||
File dir = new File("./config/");
|
||||
|
||||
System.out.println("Loading Config in " + dir.getAbsolutePath());
|
||||
ArrayList<Supplier> ret = Files.list(dir.toPath()).map(path -> {
|
||||
|
||||
System.out.println("Iterating over; " + path);
|
||||
if (path.toString().endsWith(".json")) {
|
||||
try {
|
||||
String jsonData = Files.readString(path, StandardCharsets.UTF_8);
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
Supplier sup = objectMapper.readValue(jsonData, Supplier.class);
|
||||
Seil0
commented
Der Cast ist redundant. Der Cast ist redundant.
|
||||
System.out.println("Loaded " + sup.id);
|
||||
return sup;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} else {
|
||||
System.out.println("Skipping because of file extension.");
|
||||
}
|
||||
return null;
|
||||
}).collect(Collectors.toCollection(ArrayList::new));
|
||||
|
||||
ret.removeIf(Objects::isNull);
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
package org.hso.ecommerce.supplier;
|
||||
|
||||
import org.hso.ecommerce.supplier.data.Article;
|
||||
import org.hso.ecommerce.supplier.data.Order;
|
||||
import org.hso.ecommerce.supplier.data.OrderConfirmation;
|
||||
import org.hso.ecommerce.supplier.data.Supplier;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
public class RequestController {
|
||||
|
||||
private final HashMap<String, Integer> dailySalesVolumeCent = new HashMap<>();
|
||||
private final HashMap<String, Supplier> knownSuppliers = new HashMap<>();
|
||||
private final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
|
||||
|
||||
@PostConstruct
|
||||
public void init() throws IOException {
|
||||
for (Supplier s : ConfigurationReader.read()) {
|
||||
knownSuppliers.put(s.id, s);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@GetMapping("/")
|
||||
public List<Supplier> index() {
|
||||
return new ArrayList<>(knownSuppliers.values());
|
||||
}
|
||||
|
||||
@GetMapping("/{supplier}/")
|
||||
public Supplier supplier(HttpServletResponse res, @PathVariable("supplier") String supplierName) {
|
||||
Supplier s = knownSuppliers.get(supplierName);
|
||||
if(s == null) {
|
||||
res.setStatus(HttpServletResponse.SC_NOT_FOUND);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
@PostMapping("/{supplier}/order")
|
||||
public OrderConfirmation order(HttpServletResponse res, @PathVariable("supplier") String supplierName, @RequestBody Order order) {
|
||||
Supplier s = knownSuppliers.get(supplierName);
|
||||
if(s == null) {
|
||||
res.setStatus(HttpServletResponse.SC_NOT_FOUND);
|
||||
return null;
|
||||
}
|
||||
|
||||
String dateKey = simpleDateFormat.format(new Date());
|
||||
int dailyVolume = dailySalesVolumeCent.getOrDefault(dateKey,0);
|
||||
|
||||
Article a = s.findArticle(order.manufacturer, order.articleNumber);
|
||||
if(a == null) {
|
||||
res.setStatus(HttpServletResponse.SC_BAD_REQUEST);
|
||||
return null;
|
||||
}
|
||||
|
||||
int priceNet = a.pricePerUnitNet * order.quantity;
|
||||
int discount = 0;
|
||||
if(dailyVolume >= s.discount.minimumDailySalesVolumeNetCent) {
|
||||
discount = (priceNet * s.discount.percentDiscount) / 100;
|
||||
}
|
||||
|
||||
OrderConfirmation confirmation = new OrderConfirmation();
|
||||
confirmation.articleNumber = order.articleNumber;
|
||||
confirmation.discountNetCent = discount;
|
||||
confirmation.pricePerUnitNetCent = a.pricePerUnitNet;
|
||||
confirmation.manufacturer = a.manufacturer;
|
||||
confirmation.quantity = order.quantity;
|
||||
confirmation.totalPriceNetCharged = priceNet - discount;
|
||||
|
||||
if (confirmation.totalPriceNetCharged > order.maxTotalPriceCentNet) {
|
||||
res.setStatus(HttpServletResponse.SC_EXPECTATION_FAILED);
|
||||
return null;
|
||||
}
|
||||
|
||||
dailyVolume += confirmation.totalPriceNetCharged;
|
||||
dailySalesVolumeCent.put(dateKey, dailyVolume);
|
||||
|
||||
return confirmation;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package org.hso.ecommerce.supplier.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.supplier.data;
|
||||
|
||||
public class Order {
|
||||
public String manufacturer;
|
||||
public String articleNumber;
|
||||
|
||||
public int quantity;
|
||||
public int maxTotalPriceCentNet;
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package org.hso.ecommerce.supplier.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.supplier.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.supplier.data;
|
||||
|
||||
public class SupplierDiscount {
|
||||
public int minimumDailySalesVolumeNetCent;
|
||||
public int percentDiscount;
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
server.address=::1
|
||||
server.port=8081
|
Seit
a2f20938cd
gibt es getArticleStock() in WarehouseBookingPositionSlotEntryRepository. Kann man das hier verwendet werden?