feature/reorder_cronjob #27

Merged
Seil0 merged 11 commits from feature/reorder_cronjob into master 2020-05-29 16:20:05 +02:00
37 changed files with 1356 additions and 6 deletions

View File

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

View File

@ -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]);
Review

Seit a2f20938cd gibt es getArticleStock() in WarehouseBookingPositionSlotEntryRepository. Kann man das hier verwendet werden?

Seit a2f20938cd gibt es getArticleStock() in WarehouseBookingPositionSlotEntryRepository. Kann man das hier verwendet werden?
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;
Review

Ist redundant -> return n - i - l;

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,6 @@
package org.hso.ecommerce.api.data;
public class SupplierDiscount {
public int minimumDailySalesVolumeNetCent;
public int percentDiscount;
}

View File

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

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), //
Review

Für was sind die //?

Für was sind die `//`?
Review

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();
}
}

View File

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

View File

@ -39,4 +39,8 @@ public class BookingReason {
public BookingReason(CustomerPayment customerPayment) {
this.customerPayment = customerPayment;
}
public BookingReason(SupplierOrder supplierOrder) {
this.supplierOrder = supplierOrder;
}
}

View File

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

View File

@ -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();

View File

@ -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> {
Review

Was macht dieses Repository?

Was macht dieses Repository?
Review

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.
}

View File

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

View File

@ -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> {
Review

Was macht dieses Repository?

Was macht dieses Repository?
Review

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.
}

View File

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

View File

@ -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> {
Review

Was macht dieses Repository?

Was macht dieses Repository?
Review

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.
}

32
supplier/.gitignore vendored Normal file
View File

@ -0,0 +1,32 @@
HELP.md
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
Review

Der wrapper gehört ins Repo.

Der wrapper gehört ins Repo.
Review

@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 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?
Review

Das nicht "!" excluded das exclude. Ist das Default .gitignore vom .

Das nicht "!" excluded das exclude. Ist das Default .gitignore vom <Generator Name>.
Review

*Default vom generator

*Default vom generator
Review

Der Ordner gradle/wrapper fehlt aber im supplier Projekt.

Der Ordner `gradle/wrapper ` fehlt aber im supplier Projekt.
Review

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/

29
supplier/build.gradle Normal file
View File

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

26
supplier/config/bank.json Normal file
View File

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

View File

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

Binary file not shown.

View File

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

185
supplier/gradlew vendored Executable file
View File

@ -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" "$@"

104
supplier/gradlew.bat vendored Normal file
View File

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

1
supplier/settings.gradle Normal file
View File

@ -0,0 +1 @@
rootProject.name = 'supplier'

View File

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

View File

@ -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);
Outdated
Review

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,6 @@
package org.hso.ecommerce.supplier.data;
public class SupplierDiscount {
public int minimumDailySalesVolumeNetCent;
public int percentDiscount;
}

View File

@ -0,0 +1,2 @@
server.address=::1
server.port=8081