Merge remote-tracking branch 'origin/master' into feature/config
2
prototype/.gitignore
vendored
@ -1,4 +1,4 @@
|
||||
./test.db
|
||||
e-commerce.db
|
||||
./build
|
||||
./gradle
|
||||
./out
|
||||
|
@ -3,7 +3,7 @@ buildscript {
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath("org.springframework.boot:spring-boot-gradle-plugin:2.2.2.RELEASE")
|
||||
classpath("org.springframework.boot:spring-boot-gradle-plugin:2.2.7.RELEASE")
|
||||
}
|
||||
}
|
||||
|
||||
@ -27,7 +27,7 @@ dependencies {
|
||||
// implementation 'org.springframework.session:spring-session-jdbc'
|
||||
implementation 'com.github.gwenn:sqlite-dialect:0.1.0'
|
||||
implementation 'org.springframework.boot:spring-boot-devtools'
|
||||
implementation 'org.xerial:sqlite-jdbc:3.28.0'
|
||||
implementation 'org.xerial:sqlite-jdbc:3.31.1'
|
||||
implementation 'org.yaml:snakeyaml:1.26'
|
||||
testCompile("org.springframework.boot:spring-boot-starter-test")
|
||||
}
|
||||
@ -36,5 +36,5 @@ group 'org.hso'
|
||||
version '0.1.0'
|
||||
|
||||
bootRun {
|
||||
args = ["--spring.profiles.active=dev"]
|
||||
args = ["--spring.profiles.active=dev --spring.config.location=classpath:/application.properties"]
|
||||
}
|
||||
|
BIN
prototype/gradle/wrapper/gradle-wrapper.jar
vendored
@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.4-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
22
prototype/gradlew
vendored
@ -1,5 +1,21 @@
|
||||
#!/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
|
||||
@ -28,7 +44,7 @@ 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=""
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
@ -109,8 +125,8 @@ if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin ; then
|
||||
# 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"`
|
||||
|
18
prototype/gradlew.bat
vendored
@ -1,3 +1,19 @@
|
||||
@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
|
||||
@ -14,7 +30,7 @@ set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@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=
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
15
prototype/scripts/addarticles.sql
Normal file
@ -0,0 +1,15 @@
|
||||
/*
|
||||
* add a supplier first
|
||||
*/
|
||||
|
||||
INSERT INTO article_offers ("manufacturer", "article_number", "price_per_unit_net", "title", "vat_percent", "should_be_advertised", "cheapest_supplier_id")
|
||||
VALUES ("McDonalds", "1", 4242, "McPizza", 7, 1, 1);
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* There is no need for the add article, you can add one form the UI on the offerd article page
|
||||
*/
|
||||
INSERT INTO articles ("related_id", "shop_price_per_unit_net_cent", "warehouse_units_per_slot", "should_reorder", "reorder_max_price", "title", "description", "image_id")
|
||||
VALUES (1, 19.99, 10, 1, 15, "Huge Hamburger", "This huge Hamburger is awesome!", NULL);
|
||||
|
6
prototype/scripts/addsupplier.sql
Normal file
@ -0,0 +1,6 @@
|
||||
|
||||
INSERT INTO suppliers ("api_url", "name", "uuid")
|
||||
VALUES ("https://api.com", "Conrad", "fdfdfg4gdfgdf4gfg");
|
||||
|
||||
|
||||
|
3
prototype/scripts/addsupplierorders.sql
Normal file
@ -0,0 +1,3 @@
|
||||
|
||||
INSERT INTO supplier_orders ("created", "delivered", "number_of_units", "price_per_unit_net_cent", "total_price_net", "ordered_id", "supplier_id")
|
||||
VALUES ('0', '0', '42', '42', '42', '1', '1');
|
@ -1,9 +1,9 @@
|
||||
/* password is 123 */
|
||||
INSERT INTO users ("created", "email", "password_hash", "gets_ads", "is_active", "is_employee", "isb2b")
|
||||
VALUES (datetime('now','localtime') ||'.0', "emp@ecom", "$2a$10$zFiqcePBmXHErD86vkI.vO1dnX20ezoVSM8xjGi59nktXYQv0o.fK", "0", "1", "1", "0");
|
||||
INSERT INTO users ("created", "email", "password_hash", "is_active", "is_employee")
|
||||
VALUES (datetime('now','localtime') ||'.0', "emp@ecom", "$2a$10$zFiqcePBmXHErD86vkI.vO1dnX20ezoVSM8xjGi59nktXYQv0o.fK", "1", "1");
|
||||
|
||||
INSERT INTO users ("created", "email", "password_hash", "gets_ads", "is_active", "is_employee", "isb2b")
|
||||
VALUES (datetime('now','localtime') ||'.0', "user@ecom", "$2a$10$zFiqcePBmXHErD86vkI.vO1dnX20ezoVSM8xjGi59nktXYQv0o.fK", "1", "1", "0", "0");
|
||||
INSERT INTO users ("created", "email", "password_hash", "is_active", "is_employee")
|
||||
VALUES (datetime('now','localtime') ||'.0', "user@ecom", "$2a$10$zFiqcePBmXHErD86vkI.vO1dnX20ezoVSM8xjGi59nktXYQv0o.fK", "1", "0");
|
||||
|
||||
INSERT INTO users ("created", "email", "password_hash", "gets_ads", "is_active", "is_employee", "isb2b")
|
||||
VALUES (datetime('now','localtime') ||'.0', "blocked@ecom", "$2a$10$zFiqcePBmXHErD86vkI.vO1dnX20ezoVSM8xjGi59nktXYQv0o.fK", "1", "0", "0", "0");
|
||||
INSERT INTO users ("created", "email", "password_hash", "is_active", "is_employee")
|
||||
VALUES (datetime('now','localtime') ||'.0', "blocked@ecom", "$2a$10$zFiqcePBmXHErD86vkI.vO1dnX20ezoVSM8xjGi59nktXYQv0o.fK", "0", "0");
|
@ -0,0 +1,31 @@
|
||||
package org.hso.ecommerce.action.booking;
|
||||
|
||||
import org.hso.ecommerce.entities.booking.Booking;
|
||||
import org.hso.ecommerce.entities.booking.BookingAccountEntry;
|
||||
import org.hso.ecommerce.entities.booking.BookingReason;
|
||||
|
||||
public class CreateBookingAction {
|
||||
|
||||
private Booking booking;
|
||||
|
||||
public CreateBookingAction(BookingAccountEntry source, BookingAccountEntry destination, BookingReason reason, int amountCent) {
|
||||
booking = new Booking();
|
||||
booking.created = new java.sql.Timestamp(System.currentTimeMillis());
|
||||
booking.reason = reason;
|
||||
booking.amountCent = amountCent;
|
||||
|
||||
assert source != null || destination != null;
|
||||
|
||||
if (source != null) {
|
||||
booking.source = source.copyAddAmount(-amountCent);
|
||||
}
|
||||
if (destination != null) {
|
||||
booking.destination = destination.copyAddAmount(amountCent);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public Booking finish() {
|
||||
return booking;
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
package org.hso.ecommerce.action.cronjob;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.hso.ecommerce.api.SupplierService;
|
||||
import org.hso.ecommerce.api.data.Article;
|
||||
import org.hso.ecommerce.api.data.Supplier;
|
||||
|
||||
public class ReadSupplierDataAction {
|
||||
private List<org.hso.ecommerce.entities.supplier.Supplier> suppliers;
|
||||
|
||||
public static class ArticleIdentifier {
|
||||
public final String manufacturer;
|
||||
public final String articleNumber;
|
||||
|
||||
public ArticleIdentifier(String manufacturer, String articleNumber) {
|
||||
this.manufacturer = manufacturer;
|
||||
this.articleNumber = articleNumber;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(manufacturer, articleNumber);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (!(other instanceof ArticleIdentifier)) {
|
||||
return false;
|
||||
}
|
||||
ArticleIdentifier otherId = (ArticleIdentifier) other;
|
||||
return this.manufacturer.equals(otherId.manufacturer) && this.articleNumber.equals(otherId.articleNumber);
|
||||
}
|
||||
}
|
||||
|
||||
public ReadSupplierDataAction(List<org.hso.ecommerce.entities.supplier.Supplier> suppliers) {
|
||||
this.suppliers = suppliers;
|
||||
}
|
||||
|
||||
public static class Offer {
|
||||
public final org.hso.ecommerce.entities.supplier.Supplier dbSupplier;
|
||||
public final Supplier apiSupplier;
|
||||
|
||||
public Offer(org.hso.ecommerce.entities.supplier.Supplier dbSupplier, Supplier apiSupplier) {
|
||||
this.dbSupplier = dbSupplier;
|
||||
this.apiSupplier = apiSupplier;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Result {
|
||||
public final ArrayList<Supplier> supplierData;
|
||||
public final HashMap<ArticleIdentifier, Offer> cheapestOffer;
|
||||
|
||||
public Result(ArrayList<Supplier> supplierData, HashMap<ArticleIdentifier, Offer> cheapestOffer) {
|
||||
this.supplierData = supplierData;
|
||||
this.cheapestOffer = cheapestOffer;
|
||||
}
|
||||
}
|
||||
|
||||
public Result finish() {
|
||||
ArrayList<Supplier> suppliers = new ArrayList<>();
|
||||
HashMap<ArticleIdentifier, Integer> price = new HashMap<>();
|
||||
HashMap<ArticleIdentifier, Offer> cheapest = new HashMap<>();
|
||||
for (org.hso.ecommerce.entities.supplier.Supplier supplier : this.suppliers) {
|
||||
SupplierService service = new SupplierService(supplier.apiUrl);
|
||||
Supplier apiSupplier = service.getSupplier();
|
||||
suppliers.add(apiSupplier);
|
||||
for (Article article : apiSupplier.articles) {
|
||||
ArticleIdentifier identifier = new ArticleIdentifier(article.manufacturer, article.articleNumber);
|
||||
Integer previousPrice = price.get(identifier);
|
||||
if (previousPrice == null || article.pricePerUnitNet < previousPrice) {
|
||||
price.put(identifier, article.pricePerUnitNet);
|
||||
cheapest.put(identifier, new Offer(supplier, apiSupplier));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new Result(suppliers, cheapest);
|
||||
}
|
||||
}
|
@ -0,0 +1,107 @@
|
||||
package org.hso.ecommerce.action.cronjob;
|
||||
|
||||
import java.sql.Timestamp;
|
||||
import java.util.HashMap;
|
||||
|
||||
import org.hso.ecommerce.action.cronjob.ReadSupplierDataAction.ArticleIdentifier;
|
||||
import org.hso.ecommerce.action.cronjob.ReadSupplierDataAction.Offer;
|
||||
import org.hso.ecommerce.api.SupplierService;
|
||||
import org.hso.ecommerce.api.data.Order;
|
||||
import org.hso.ecommerce.api.data.OrderConfirmation;
|
||||
import org.hso.ecommerce.entities.shop.Article;
|
||||
import org.hso.ecommerce.entities.supplier.ArticleOffer;
|
||||
import org.hso.ecommerce.entities.supplier.SupplierOrder;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class ReorderAction {
|
||||
private static final Logger log = LoggerFactory.getLogger(ReorderAction.class);
|
||||
|
||||
private Article article;
|
||||
private Integer[] orderedAmounts;
|
||||
private Integer undeliveredReorders;
|
||||
private int amountInStock;
|
||||
private HashMap<ArticleIdentifier, Offer> cheapestOffer;
|
||||
private HashMap<ArticleIdentifier, ArticleOffer> articleOffers;
|
||||
|
||||
public ReorderAction(
|
||||
Article article, Integer[] orderedAmounts,
|
||||
Integer undeliveredReorders,
|
||||
int amountInStock,
|
||||
HashMap<ArticleIdentifier, Offer> cheapestOffer,
|
||||
HashMap<ArticleIdentifier, ArticleOffer> articleOffers
|
||||
) {
|
||||
this.article = article;
|
||||
this.orderedAmounts = orderedAmounts;
|
||||
this.undeliveredReorders = undeliveredReorders;
|
||||
this.amountInStock = amountInStock;
|
||||
this.cheapestOffer = cheapestOffer;
|
||||
this.articleOffers = articleOffers;
|
||||
}
|
||||
|
||||
private int null_to_zero(Integer input) {
|
||||
return input == null ? 0 : input;
|
||||
}
|
||||
|
||||
private int calculateAmountToReorder() {
|
||||
// Algorithm as described in the documentation
|
||||
int a = null_to_zero(orderedAmounts[0]);
|
||||
int b = null_to_zero(orderedAmounts[1]);
|
||||
int c = null_to_zero(orderedAmounts[2]);
|
||||
|
||||
int x = Math.max(Math.max(a, b), c);
|
||||
int y = Math.min(Math.min(a, b), c);
|
||||
|
||||
int n = 6 * x - 2 * y;
|
||||
if (n < 3) {
|
||||
n = 3;
|
||||
}
|
||||
|
||||
int i = null_to_zero(undeliveredReorders);
|
||||
int l = amountInStock;
|
||||
|
||||
return n - i - l;
|
||||
}
|
||||
|
||||
public SupplierOrder finish() {
|
||||
if (!article.shouldReorder) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int amount = calculateAmountToReorder();
|
||||
if (amount <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ArticleIdentifier identifier = new ArticleIdentifier(article.related.manufacturer, article.related.articleNumber);
|
||||
Offer offer = cheapestOffer.get(identifier);
|
||||
if (offer == null) {
|
||||
log.info("Could not order \"" + article.title + "\" because there is no supplier delivering it.");
|
||||
return null;
|
||||
}
|
||||
|
||||
ArticleOffer articleOffer = articleOffers.get(identifier);
|
||||
org.hso.ecommerce.api.data.Article apiArticle = offer.apiSupplier.findArticle(identifier.manufacturer,
|
||||
identifier.articleNumber);
|
||||
if (apiArticle.pricePerUnitNet > article.reorderMaxPrice) {
|
||||
log.info("Could not order \"" + article.title + "\" because it is currently too expensive.");
|
||||
return null;
|
||||
}
|
||||
|
||||
Order order = new Order();
|
||||
order.manufacturer = articleOffer.manufacturer;
|
||||
order.articleNumber = articleOffer.articleNumber;
|
||||
order.quantity = amount;
|
||||
order.maxTotalPriceCentNet = apiArticle.pricePerUnitNet * amount;
|
||||
|
||||
OrderConfirmation confirm = new SupplierService(offer.dbSupplier.apiUrl).order(order);
|
||||
SupplierOrder createdOrder = new SupplierOrder();
|
||||
createdOrder.created = new Timestamp(System.currentTimeMillis());
|
||||
createdOrder.supplier = offer.dbSupplier;
|
||||
createdOrder.ordered = articleOffer;
|
||||
createdOrder.numberOfUnits = confirm.quantity;
|
||||
createdOrder.pricePerUnitNetCent = confirm.pricePerUnitNetCent;
|
||||
createdOrder.totalPriceNet = confirm.totalPriceNetCharged;
|
||||
return createdOrder;
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
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 and supplier relations first. They are set again below.
|
||||
for (ArticleOffer offer : availableOffers.values()) {
|
||||
offer.shouldBeAdvertised = false;
|
||||
offer.cheapestSupplier = null;
|
||||
}
|
||||
|
||||
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.title = currentOfferedArticle.title;
|
||||
currentOffer.vatPercent = currentOfferedArticle.vatPercent;
|
||||
currentOffer.cheapestSupplier = cheapestOffer.getValue().dbSupplier;
|
||||
currentOffer.pricePerUnitNet = currentOfferedArticle.pricePerUnitNet;
|
||||
|
||||
// Set advertise-flag if any supplier wants it to be set
|
||||
if (currentOfferedArticle.shouldBeAdvertised) {
|
||||
currentOffer.shouldBeAdvertised = true;
|
||||
}
|
||||
}
|
||||
return offers;
|
||||
}
|
||||
}
|
@ -0,0 +1,201 @@
|
||||
package org.hso.ecommerce.action.shop;
|
||||
|
||||
import org.hso.ecommerce.action.booking.CreateBookingAction;
|
||||
import org.hso.ecommerce.entities.booking.*;
|
||||
import org.hso.ecommerce.entities.shop.Address;
|
||||
import org.hso.ecommerce.entities.shop.Article;
|
||||
import org.hso.ecommerce.entities.shop.CustomerOrder;
|
||||
import org.hso.ecommerce.entities.shop.CustomerOrderPosition;
|
||||
import org.hso.ecommerce.entities.user.User;
|
||||
import org.hso.ecommerce.entities.warehouse.WarehouseBooking;
|
||||
import org.hso.ecommerce.entities.warehouse.WarehouseBookingPosition;
|
||||
import org.hso.ecommerce.entities.warehouse.WarehouseBookingPositionSlotEntry;
|
||||
|
||||
import java.sql.Timestamp;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
public class CreateOrderAction {
|
||||
|
||||
private User user;
|
||||
Address destination;
|
||||
|
||||
private int expectedTotalGrossCent;
|
||||
|
||||
private int totalNetCent;
|
||||
private int totalVatCent;
|
||||
private PaymentMethod method;
|
||||
|
||||
private BookingAccountEntry latestUserBooking;
|
||||
private BookingAccountEntry latestVatBooking;
|
||||
private BookingAccountEntry latestMainBooking;
|
||||
|
||||
private List<OrderItem> orderItems = new ArrayList<>();
|
||||
|
||||
public CreateOrderAction(
|
||||
User user,
|
||||
int expectedTotalGrossCent,
|
||||
Address destination,
|
||||
PaymentMethod method,
|
||||
BookingAccountEntry latestUserBooking,
|
||||
BookingAccountEntry latestVatBooking,
|
||||
BookingAccountEntry latestMainBooking
|
||||
) {
|
||||
this.user = user;
|
||||
this.expectedTotalGrossCent = expectedTotalGrossCent;
|
||||
this.destination = destination;
|
||||
this.method = method;
|
||||
|
||||
this.latestUserBooking = latestUserBooking;
|
||||
assert latestUserBooking.userAccount.id == user.id;
|
||||
|
||||
this.latestVatBooking = latestVatBooking;
|
||||
assert latestVatBooking.isVATAccount;
|
||||
|
||||
this.latestMainBooking = latestMainBooking;
|
||||
assert latestMainBooking.isMainAccount;
|
||||
}
|
||||
|
||||
public void addArticle(Article article, int quantity, List<WarehouseBookingPositionSlotEntry> availableSlots) {
|
||||
for (WarehouseBookingPositionSlotEntry slot : availableSlots) {
|
||||
assert slot.article.id == article.id;
|
||||
}
|
||||
|
||||
orderItems.add(new OrderItem(article, availableSlots, quantity));
|
||||
|
||||
totalNetCent += article.shopPricePerUnitNetCent * quantity;
|
||||
totalVatCent += article.getVat() * quantity;
|
||||
}
|
||||
|
||||
|
||||
public Result finish() throws ArticleNotInStockException {
|
||||
CustomerOrder order = createOrder();
|
||||
CustomerPayment payment = createPayment();
|
||||
|
||||
List<Booking> bookingList = new ArrayList<>();
|
||||
Booking purchaseBooking = new CreateBookingAction(
|
||||
latestUserBooking, latestMainBooking, new BookingReason(order), order.totalGrossCent).finish();
|
||||
Booking paymentBooking = new CreateBookingAction(
|
||||
null, purchaseBooking.source /* userAccount */, new BookingReason(payment), order.totalGrossCent).finish();
|
||||
Booking vatBooking = new CreateBookingAction(
|
||||
purchaseBooking.destination /* mainAccount */, latestVatBooking, new BookingReason(order), order.totalVatCent).finish();
|
||||
bookingList.add(purchaseBooking);
|
||||
bookingList.add(paymentBooking);
|
||||
bookingList.add(vatBooking);
|
||||
|
||||
WarehouseBooking warehouseBooking = createWarehouseBooking(order);
|
||||
|
||||
return new Result(
|
||||
order,
|
||||
warehouseBooking,
|
||||
bookingList
|
||||
);
|
||||
}
|
||||
|
||||
private WarehouseBooking createWarehouseBooking(CustomerOrder order) throws ArticleNotInStockException {
|
||||
WarehouseBooking booking = new WarehouseBooking();
|
||||
booking.created = new Timestamp(new Date().getTime());
|
||||
booking.reason = new BookingReason(order);
|
||||
|
||||
for (OrderItem item : orderItems) {
|
||||
int needed = item.quantity;
|
||||
|
||||
for (WarehouseBookingPositionSlotEntry slot : item.availableSlots) {
|
||||
int remove = Math.min(slot.newSumSlot, needed);
|
||||
needed -= remove;
|
||||
|
||||
WarehouseBookingPosition bookingPosition = new WarehouseBookingPosition();
|
||||
|
||||
bookingPosition.article = item.article;
|
||||
bookingPosition.amount = -remove;
|
||||
bookingPosition.slotEntry = slot.copyAddAmount(-remove, item.article);
|
||||
bookingPosition.booking = booking;
|
||||
|
||||
booking.positions.add(bookingPosition);
|
||||
|
||||
if (needed == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (needed > 0) {
|
||||
throw new ArticleNotInStockException(item.article);
|
||||
}
|
||||
}
|
||||
|
||||
return booking;
|
||||
}
|
||||
|
||||
private CustomerPayment createPayment() {
|
||||
CustomerPayment payment = new CustomerPayment();
|
||||
payment.amountCent = totalNetCent + totalVatCent;
|
||||
payment.payment = method;
|
||||
return payment;
|
||||
}
|
||||
|
||||
|
||||
private CustomerOrder createOrder() {
|
||||
assert totalNetCent + totalVatCent == expectedTotalGrossCent;
|
||||
|
||||
CustomerOrder customerOrder = new CustomerOrder();
|
||||
customerOrder.customer = user;
|
||||
customerOrder.destination = destination;
|
||||
|
||||
for (OrderItem item : orderItems) {
|
||||
CustomerOrderPosition position = new CustomerOrderPosition();
|
||||
position.article = item.article;
|
||||
position.pricePerUnit = item.article.shopPricePerUnitNetCent;
|
||||
position.quantity = item.quantity;
|
||||
|
||||
position.order = customerOrder;
|
||||
|
||||
customerOrder.positions.add(position);
|
||||
}
|
||||
|
||||
customerOrder.created = new Timestamp(new Date().getTime());
|
||||
|
||||
customerOrder.totalNetCent = totalNetCent;
|
||||
customerOrder.totalVatCent = totalVatCent;
|
||||
customerOrder.totalGrossCent = totalNetCent + totalVatCent;
|
||||
|
||||
return customerOrder;
|
||||
}
|
||||
|
||||
public static class Result {
|
||||
public final CustomerOrder customerOrder;
|
||||
public final WarehouseBooking warehouseBooking;
|
||||
public final List<Booking> bookings;
|
||||
|
||||
Result(CustomerOrder customerOrder, WarehouseBooking warehouseBooking, List<Booking> bookings) {
|
||||
this.customerOrder = customerOrder;
|
||||
this.warehouseBooking = warehouseBooking;
|
||||
this.bookings = bookings;
|
||||
}
|
||||
}
|
||||
|
||||
private static class OrderItem {
|
||||
List<WarehouseBookingPositionSlotEntry> availableSlots;
|
||||
int quantity;
|
||||
Article article;
|
||||
|
||||
public OrderItem(Article article, List<WarehouseBookingPositionSlotEntry> availableSlots, int quantity) {
|
||||
this.article = article;
|
||||
this.availableSlots = availableSlots;
|
||||
this.quantity = quantity;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ArticleNotInStockException extends Exception {
|
||||
private Article article;
|
||||
|
||||
public ArticleNotInStockException(Article article) {
|
||||
super("The quantity of article '" + article.title + "' is not in stock.");
|
||||
this.article = article;
|
||||
}
|
||||
|
||||
public Article getArticle() {
|
||||
return article;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package org.hso.ecommerce.action.shop;
|
||||
|
||||
import org.hso.ecommerce.entities.shop.CustomerOrder;
|
||||
|
||||
public class EnableTrackingAction {
|
||||
|
||||
public static void addTrackingInfo(CustomerOrder customerOrder) {
|
||||
// TODO:
|
||||
customerOrder.trackingId = "555-NASE";
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package org.hso.ecommerce.action.shop;
|
||||
|
||||
import org.hso.ecommerce.entities.shop.Article;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class GetRandomArticlesAction {
|
||||
|
||||
public static List<Article> getRandomArticles(int quantity, List<Article> advertisedArticles) {
|
||||
List<Article> randomisedArticles = new ArrayList<Article>();
|
||||
int loopcount = Math.min(quantity, advertisedArticles.size());
|
||||
for (int i = 0; i < loopcount; i++) {
|
||||
int index = (int) (Math.random() * advertisedArticles.size());
|
||||
randomisedArticles.add(advertisedArticles.remove(index));
|
||||
}
|
||||
return randomisedArticles;
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
package org.hso.ecommerce.action.shop;
|
||||
|
||||
import org.hso.ecommerce.entities.shop.Article;
|
||||
import org.hso.ecommerce.repos.shop.ArticleRepository;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class SearchByTermAction {
|
||||
|
||||
public static List<Article> searchByTerm(String sourceTerm, ArticleRepository repository) {
|
||||
|
||||
List<String> terms = Arrays.asList(sourceTerm.split(" "));
|
||||
List<Article> resultArticles = new ArrayList<>();
|
||||
|
||||
terms.forEach(term -> {
|
||||
List<Article> titleArticles = repository.getArticlesByTermInTitle(term); //search in Title
|
||||
titleArticles.forEach(article -> {
|
||||
if(!resultArticles.contains(article)){
|
||||
resultArticles.add(article);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
terms.forEach(term -> {
|
||||
List<Article> descArticles = repository.getArticlesByTermInDescription(term); //search by Term
|
||||
descArticles.forEach(article -> {
|
||||
if(!resultArticles.contains(article)){
|
||||
resultArticles.add(article);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
return resultArticles;
|
||||
}
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
package org.hso.ecommerce.action.somepackage;
|
||||
|
||||
public class DemoAction {
|
||||
// TODO: remove me.
|
||||
// mksubpackage
|
||||
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
package org.hso.ecommerce.action.user;
|
||||
|
||||
import org.hso.ecommerce.entities.booking.PaymentMethod;
|
||||
import org.hso.ecommerce.entities.user.User;
|
||||
import org.hso.ecommerce.repos.user.UserRepository;
|
||||
|
||||
public class UpdateUserSettingsAction {
|
||||
|
||||
private User user;
|
||||
private UserRepository repository;
|
||||
|
||||
public UpdateUserSettingsAction(User user, UserRepository repository) {
|
||||
this.user = user;
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
public UpdateResult updateEmail(String newMail) {
|
||||
UpdateResult result = new UpdateResult(false);
|
||||
if (!newMail.contains("@")) {
|
||||
result.errorString = "Ändern der Email-Addresse nicht möglich. Bitte versuchen Sie es erneut.";
|
||||
} else {
|
||||
this.user.email = newMail;
|
||||
this.repository.save(this.user);
|
||||
result.updated = true;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public UpdateResult updatePassword(String oldPassword, String password1, String password2) {
|
||||
UpdateResult result = new UpdateResult(false);
|
||||
if (this.user.validatePassword(oldPassword)) {
|
||||
if (password1.equals(password2)) {
|
||||
if (!password1.equals(oldPassword)) {
|
||||
this.user.setPassword(password1);
|
||||
this.repository.save(this.user);
|
||||
result.updated = true;
|
||||
} else {
|
||||
result.errorString = "Das neue Passwort entspricht dem alten Passwort.";
|
||||
}
|
||||
} else {
|
||||
result.errorString = "Die beiden neuen Passwörter stimmen nicht überein. Bitte versuchen Sie es erneut.";
|
||||
}
|
||||
} else {
|
||||
result.errorString = "Das eingegebene alte Passwort stimmt nicht mit dem momentan gespeicherten Passwort überein. Bitte versuchen Sie es erneut.";
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public UpdateResult updateShippingInfo(String salutation, String name, String address) {
|
||||
this.user.salutation = salutation;
|
||||
this.user.name = name;
|
||||
this.user.defaultDeliveryAddress.addressString = address;
|
||||
this.repository.save(this.user);
|
||||
return new UpdateResult(true);
|
||||
}
|
||||
|
||||
public UpdateResult updatePaymentInfo(String creditCardNumber) {
|
||||
UpdateResult result = new UpdateResult(false);
|
||||
if (creditCardNumber.matches("[0-9]+")) {
|
||||
this.user.defaultPayment = PaymentMethod.fromCreditCardNumber(creditCardNumber);
|
||||
this.repository.save(this.user);
|
||||
result.updated = true;
|
||||
} else {
|
||||
result.errorString = "Kreditkartennummer darf nur Zahlen enthalten. Bitte versuchen Sie es erneut.";
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public class UpdateResult {
|
||||
public boolean updated; //if true worked, if false not worked
|
||||
public String errorString;
|
||||
|
||||
public UpdateResult(boolean updated, String errorString) {
|
||||
this.updated = updated;
|
||||
this.errorString = errorString;
|
||||
}
|
||||
|
||||
public UpdateResult(boolean updated) {
|
||||
this.updated = updated;
|
||||
this.errorString = "";
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
package org.hso.ecommerce.action.warehouse;
|
||||
|
||||
import org.hso.ecommerce.entities.warehouse.WarehouseBookingPositionSlotEntry;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
public class CalculateWarehouseStatsAction {
|
||||
|
||||
private List<WarehouseBookingPositionSlotEntry> entryList;
|
||||
|
||||
public CalculateWarehouseStatsAction(List<WarehouseBookingPositionSlotEntry> everyCurrentEntry) {
|
||||
this.entryList = everyCurrentEntry;
|
||||
}
|
||||
|
||||
public WarehouseStats finish() {
|
||||
int numArticles = calculateNumArticles();
|
||||
double efficiency = calculateEfficiency();
|
||||
double ratioUsedSlots = calculateRatioSlotsUsed();
|
||||
|
||||
return new WarehouseStats(numArticles, efficiency, ratioUsedSlots);
|
||||
}
|
||||
|
||||
private double calculateRatioSlotsUsed() {
|
||||
int used = 0;
|
||||
|
||||
for (WarehouseBookingPositionSlotEntry entry : entryList) {
|
||||
if (entry.newSumSlot > 0) {
|
||||
used++;
|
||||
}
|
||||
}
|
||||
|
||||
return ((double) used) / entryList.size();
|
||||
}
|
||||
|
||||
private double calculateEfficiency() {
|
||||
double e = 0;
|
||||
|
||||
for (WarehouseBookingPositionSlotEntry entry : entryList) {
|
||||
if (entry.newSumSlot > 0) {
|
||||
e += entry.newSumSlot / (double) entry.article.warehouseUnitsPerSlot;
|
||||
} else {
|
||||
e += 0;
|
||||
}
|
||||
}
|
||||
|
||||
return e / entryList.size();
|
||||
}
|
||||
|
||||
private int calculateNumArticles() {
|
||||
HashSet<Long> articleIds = new HashSet<>();
|
||||
|
||||
for (WarehouseBookingPositionSlotEntry entry : entryList) {
|
||||
if (entry.newSumSlot > 0) {
|
||||
articleIds.add(entry.article.id);
|
||||
}
|
||||
}
|
||||
|
||||
return articleIds.size();
|
||||
}
|
||||
|
||||
private static class WarehouseStats {
|
||||
public int numArticles;
|
||||
public double efficiency;
|
||||
public double ratioUsedSlots;
|
||||
|
||||
WarehouseStats(int numArticles, double efficiency, double ratioUsedSlots) {
|
||||
this.numArticles = numArticles;
|
||||
this.efficiency = efficiency;
|
||||
this.ratioUsedSlots = ratioUsedSlots;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
package org.hso.ecommerce.action.warehouse;
|
||||
|
||||
import org.hso.ecommerce.entities.booking.BookingReason;
|
||||
import org.hso.ecommerce.entities.shop.Article;
|
||||
import org.hso.ecommerce.entities.warehouse.WarehouseBooking;
|
||||
import org.hso.ecommerce.entities.warehouse.WarehouseBookingPosition;
|
||||
import org.hso.ecommerce.entities.warehouse.WarehouseBookingPositionSlotEntry;
|
||||
|
||||
import java.sql.Timestamp;
|
||||
import java.util.Date;
|
||||
import java.util.Optional;
|
||||
|
||||
public class CreateManuelBookingAction {
|
||||
|
||||
private Article article;
|
||||
private int amount;
|
||||
private Optional<WarehouseBookingPositionSlotEntry> source;
|
||||
private Optional<WarehouseBookingPositionSlotEntry> destination;
|
||||
private String reason;
|
||||
|
||||
public CreateManuelBookingAction(Article article, int amount, Optional<WarehouseBookingPositionSlotEntry> source, Optional<WarehouseBookingPositionSlotEntry> destination, String reason) {
|
||||
this.article = article;
|
||||
this.amount = amount;
|
||||
this.source = source;
|
||||
this.destination = destination;
|
||||
this.reason = reason;
|
||||
}
|
||||
|
||||
public WarehouseBooking finish() throws ArticleSlotConstraintFailedException {
|
||||
WarehouseBooking booking = new WarehouseBooking();
|
||||
booking.created = new Timestamp(new Date().getTime());
|
||||
booking.reason = new BookingReason(reason);
|
||||
|
||||
if (source.isPresent()) {
|
||||
|
||||
if (source.get().article.id != article.id) {
|
||||
throw new ArticleSlotConstraintArticleTypeFailedException();
|
||||
}
|
||||
|
||||
WarehouseBookingPosition bookingPosition = new WarehouseBookingPosition();
|
||||
bookingPosition.booking = booking;
|
||||
|
||||
bookingPosition.article = article;
|
||||
bookingPosition.amount = -amount;
|
||||
bookingPosition.slotEntry = source.get().copyAddAmount(-amount, article);
|
||||
|
||||
if (bookingPosition.slotEntry.newSumSlot < 0 || bookingPosition.slotEntry.newSumSlot > article.warehouseUnitsPerSlot) {
|
||||
throw new ArticleSlotConstraintFailedException("The quantity of article can only be set in bounds.");
|
||||
}
|
||||
|
||||
booking.positions.add(bookingPosition);
|
||||
}
|
||||
|
||||
if (destination.isPresent()) {
|
||||
|
||||
if (destination.get().article.id != article.id && destination.get().newSumSlot > 0) {
|
||||
throw new ArticleSlotConstraintArticleTypeFailedException();
|
||||
}
|
||||
|
||||
WarehouseBookingPosition bookingPosition = new WarehouseBookingPosition();
|
||||
bookingPosition.booking = booking;
|
||||
|
||||
bookingPosition.article = article;
|
||||
bookingPosition.amount = amount;
|
||||
bookingPosition.slotEntry = destination.get().copyAddAmount(amount, article);
|
||||
|
||||
if (bookingPosition.slotEntry.newSumSlot < 0 || bookingPosition.slotEntry.newSumSlot > article.warehouseUnitsPerSlot) {
|
||||
throw new ArticleSlotConstraintFailedException("The quantity of article can only be set in bounds.");
|
||||
}
|
||||
|
||||
booking.positions.add(bookingPosition);
|
||||
}
|
||||
|
||||
return booking;
|
||||
}
|
||||
|
||||
public static class ArticleSlotConstraintFailedException extends Exception {
|
||||
public ArticleSlotConstraintFailedException(String s) {
|
||||
super(s);
|
||||
}
|
||||
|
||||
public ArticleSlotConstraintFailedException() {
|
||||
super("The quantity of article can only be set in bounds.");
|
||||
}
|
||||
}
|
||||
|
||||
public static class ArticleSlotConstraintArticleTypeFailedException extends ArticleSlotConstraintFailedException {
|
||||
public ArticleSlotConstraintArticleTypeFailedException() {
|
||||
super("The Article in the slot entry does not match.");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
package org.hso.ecommerce.action.warehouse;
|
||||
|
||||
import org.hso.ecommerce.entities.booking.BookingReason;
|
||||
import org.hso.ecommerce.entities.shop.Article;
|
||||
import org.hso.ecommerce.entities.supplier.SupplierOrder;
|
||||
import org.hso.ecommerce.entities.warehouse.WarehouseBooking;
|
||||
import org.hso.ecommerce.entities.warehouse.WarehouseBookingPosition;
|
||||
import org.hso.ecommerce.entities.warehouse.WarehouseBookingPositionSlotEntry;
|
||||
|
||||
import java.sql.Timestamp;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
public class SupplierOrderArrivedAction {
|
||||
|
||||
private final Article article;
|
||||
private final ArrayList<WarehouseBookingPositionSlotEntry> warehouseCandidates;
|
||||
private final SupplierOrder order;
|
||||
|
||||
public SupplierOrderArrivedAction(List<WarehouseBookingPositionSlotEntry> warehouseCandidates, SupplierOrder order, Article article) {
|
||||
this.warehouseCandidates = new ArrayList<>(warehouseCandidates);
|
||||
this.order = order;
|
||||
this.article = article;
|
||||
}
|
||||
|
||||
public Result finish() throws NoSpaceInWarehouseException {
|
||||
// Sort for most filled slot first;
|
||||
warehouseCandidates.sort((b, a) -> Integer.compare(a.newSumSlot, b.newSumSlot));
|
||||
|
||||
int needed = order.numberOfUnits;
|
||||
|
||||
WarehouseBooking booking = new WarehouseBooking();
|
||||
booking.created = new Timestamp(new Date().getTime());
|
||||
booking.reason = new BookingReason(order);
|
||||
|
||||
for (WarehouseBookingPositionSlotEntry entry : warehouseCandidates) {
|
||||
int canBeAdded = article.warehouseUnitsPerSlot - entry.newSumSlot;
|
||||
|
||||
if (canBeAdded == 0) {
|
||||
// this slot is full, skip
|
||||
continue;
|
||||
}
|
||||
|
||||
int willBeAdded = Math.min(canBeAdded, needed);
|
||||
needed -= willBeAdded;
|
||||
|
||||
WarehouseBookingPosition bookingPosition = new WarehouseBookingPosition();
|
||||
|
||||
bookingPosition.article = article;
|
||||
bookingPosition.amount = willBeAdded;
|
||||
bookingPosition.slotEntry = entry.copyAddAmount(willBeAdded, article);
|
||||
bookingPosition.booking = booking;
|
||||
|
||||
booking.positions.add(bookingPosition);
|
||||
|
||||
if (needed == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (needed > 0) {
|
||||
throw new NoSpaceInWarehouseException(article);
|
||||
}
|
||||
|
||||
order.delivered = new Timestamp(new Date().getTime());
|
||||
return new Result(order, booking);
|
||||
}
|
||||
|
||||
public static class Result extends Exception {
|
||||
|
||||
private final SupplierOrder order;
|
||||
private final WarehouseBooking booking;
|
||||
|
||||
public Result(SupplierOrder order, WarehouseBooking booking) {
|
||||
this.order = order;
|
||||
this.booking = booking;
|
||||
}
|
||||
|
||||
public SupplierOrder getOrder() {
|
||||
return order;
|
||||
}
|
||||
|
||||
public WarehouseBooking getBooking() {
|
||||
return booking;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class NoSpaceInWarehouseException extends Exception {
|
||||
private Article article;
|
||||
|
||||
public NoSpaceInWarehouseException(Article article) {
|
||||
super("The quantity of article '" + article.title + "' does not fit in warehouse.");
|
||||
this.article = article;
|
||||
}
|
||||
|
||||
public Article getArticle() {
|
||||
return article;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
@ -3,6 +3,7 @@ package org.hso.ecommerce.app;
|
||||
import org.hso.ecommerce.components.ErrorDemoInterceptor;
|
||||
import org.hso.ecommerce.components.InfoDemoInterceptor;
|
||||
import org.hso.ecommerce.components.LoginIntercepter;
|
||||
import org.hso.ecommerce.components.ShoppingCartInterceptor;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
@ -23,5 +24,7 @@ public class Config implements WebMvcConfigurer {
|
||||
registry.addInterceptor(buildLoginIntercepter());
|
||||
registry.addInterceptor(new ErrorDemoInterceptor());
|
||||
registry.addInterceptor(new InfoDemoInterceptor());
|
||||
registry.addInterceptor(new ShoppingCartInterceptor());
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,7 @@
|
||||
package org.hso.ecommerce.app;
|
||||
|
||||
import org.hso.ecommerce.repos.user.UserRepository;
|
||||
import org.hso.ecommerce.entities.user.User;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
import java.util.Optional;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
/**
|
||||
* TODO clean up this class
|
||||
@ -17,260 +9,13 @@ import java.util.Optional;
|
||||
@Controller
|
||||
public class RequestController {
|
||||
|
||||
@Autowired
|
||||
private final UserRepository userRepository = null;
|
||||
@GetMapping("/intern/customerOrders/")
|
||||
public String internCustomerOrder() {
|
||||
return "intern/customerOrders/index";
|
||||
}
|
||||
|
||||
static int notSoRandom = 0;
|
||||
|
||||
@GetMapping("/")
|
||||
public String home() {
|
||||
return "redirect:/shop/";
|
||||
}
|
||||
|
||||
@GetMapping("/login")
|
||||
public String login() {
|
||||
return "login";
|
||||
}
|
||||
|
||||
@PostMapping("/login")
|
||||
public String loginPost(
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
@RequestParam("username") String username,
|
||||
@RequestParam("password") String password,
|
||||
HttpSession session
|
||||
) {
|
||||
String gto = (String) session.getAttribute("afterLogin");
|
||||
|
||||
Optional<User> user = userRepository.findByEmail(username);
|
||||
if (user.isEmpty()) {
|
||||
request.setAttribute("error", "Email Adresse falsch.");
|
||||
response.setStatus(HttpServletResponse.SC_EXPECTATION_FAILED);
|
||||
return "login";
|
||||
}
|
||||
|
||||
if (!user.get().validatePassword(password)) {
|
||||
request.setAttribute("error", "Passwort falsch.");
|
||||
response.setStatus(HttpServletResponse.SC_EXPECTATION_FAILED);
|
||||
return "login";
|
||||
}
|
||||
|
||||
session.setAttribute("userId", user.get().getId());
|
||||
|
||||
if (gto != null && gto.startsWith("/")) {
|
||||
return "redirect:" + gto;
|
||||
} else {
|
||||
return "redirect:/";
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping("/logout")
|
||||
public String logoutPost(HttpServletResponse response,
|
||||
HttpSession session
|
||||
) {
|
||||
session.removeAttribute("userId");
|
||||
return "redirect:/";
|
||||
}
|
||||
|
||||
@GetMapping("/register")
|
||||
public String register() {
|
||||
return "register";
|
||||
}
|
||||
|
||||
@PostMapping("/register")
|
||||
public String registerPost(
|
||||
@RequestParam("username") String username,
|
||||
@RequestParam("password") String password,
|
||||
@RequestParam("password2") String password2,
|
||||
@RequestParam("type") String type
|
||||
) {
|
||||
|
||||
return "redirect:/";
|
||||
}
|
||||
|
||||
@GetMapping("/shop/")
|
||||
public String shop() {
|
||||
return "shop/index";
|
||||
}
|
||||
|
||||
@GetMapping("/shop/search")
|
||||
public String shopSearch() {
|
||||
return "shop/search";
|
||||
}
|
||||
|
||||
@GetMapping("/shop/checkout")
|
||||
public String shopCheckout(HttpSession session, HttpServletRequest request) {
|
||||
session.setAttribute("afterLogin", request.getRequestURI());
|
||||
return "shop/checkout";
|
||||
}
|
||||
|
||||
@PostMapping("/shop/checkoutFinish")
|
||||
public String shopCheckoutFinish() {
|
||||
return "shop/checkoutFinish";
|
||||
}
|
||||
|
||||
@GetMapping("/shop/checkoutFinish")
|
||||
public String shopCheckoutFinishGET() {
|
||||
return "shop/checkoutFinish";
|
||||
}
|
||||
|
||||
@GetMapping("/shop/articles/{id}")
|
||||
public String shopArticlesById() {
|
||||
return "shop/articles/id";
|
||||
}
|
||||
|
||||
@PostMapping("/shop/articles/{id}")
|
||||
public String shopArticlesByIdBuy(HttpSession session,
|
||||
@RequestAttribute(value = "user", required = false) User customer,
|
||||
@PathVariable("id") Integer id,
|
||||
@RequestParam("fastcheckout") Boolean fastcheckout
|
||||
) {
|
||||
if (customer != null) {
|
||||
if (!fastcheckout) {
|
||||
return "shop/articles/post_add";
|
||||
} else {
|
||||
return "shop/checkout";
|
||||
}
|
||||
} else {
|
||||
session.setAttribute("afterLogin", "/shop/articles/" + id);
|
||||
return "redirect:/login";
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("/about")
|
||||
public String about() {
|
||||
return "about";
|
||||
}
|
||||
|
||||
@GetMapping("/terms")
|
||||
public String terms() {
|
||||
return "terms";
|
||||
}
|
||||
|
||||
@GetMapping("/privacy")
|
||||
public String privacy() {
|
||||
return "privacy";
|
||||
}
|
||||
|
||||
|
||||
@GetMapping("/intern/")
|
||||
public String intern() {
|
||||
return "intern/index";
|
||||
}
|
||||
|
||||
@GetMapping("/intern/listedArticles/")
|
||||
public String internListedArticles() {
|
||||
return "intern/listedArticles/index";
|
||||
}
|
||||
|
||||
@GetMapping("/intern/listedArticles/{id}")
|
||||
public String internListedArticlesId() {
|
||||
return "intern/listedArticles/id";
|
||||
}
|
||||
|
||||
|
||||
@GetMapping("/intern/articles/")
|
||||
public String internArticles() {
|
||||
return "intern/articles/index";
|
||||
}
|
||||
|
||||
@GetMapping("/intern/articles/{id}")
|
||||
public String internArticlesId() {
|
||||
return "intern/articles/id";
|
||||
}
|
||||
|
||||
@GetMapping("/intern/customers/")
|
||||
public String internCustomers() {
|
||||
return "intern/customers/index";
|
||||
}
|
||||
|
||||
@GetMapping("/intern/customers/{id}")
|
||||
public String internCustomersId() {
|
||||
return "intern/customers/id";
|
||||
}
|
||||
|
||||
@GetMapping("/intern/customerOrders/")
|
||||
public String internCustomerOrder() {
|
||||
return "intern/customerOrders/index";
|
||||
}
|
||||
|
||||
@GetMapping("/intern/customerOrders/{id}")
|
||||
public String internCustomerOrdersId() {
|
||||
return "intern/customerOrders/id";
|
||||
}
|
||||
|
||||
@GetMapping("/intern/suppliers/")
|
||||
public String internSuppliers() {
|
||||
return "intern/suppliers/index";
|
||||
}
|
||||
|
||||
@GetMapping("/intern/suppliers/{id}")
|
||||
public String internSuppliersId() {
|
||||
return "intern/suppliers/id";
|
||||
}
|
||||
|
||||
@GetMapping("/intern/supplierOrders/")
|
||||
public String internSupplierOrders() {
|
||||
return "intern/supplierOrders/index";
|
||||
}
|
||||
|
||||
@GetMapping("/intern/supplierOrders/{id}")
|
||||
public String internSupplierOrdersId() {
|
||||
return "intern/supplierOrders/id";
|
||||
}
|
||||
|
||||
@GetMapping("/intern/accounting/")
|
||||
public String accounting() {
|
||||
return "intern/accounting/index";
|
||||
}
|
||||
|
||||
@GetMapping("/intern/accounting/vat")
|
||||
public String accountingVat() {
|
||||
return "intern/accounting/vat";
|
||||
}
|
||||
|
||||
@GetMapping("/intern/accounting/main")
|
||||
public String accountingIntern() {
|
||||
return "intern/accounting/main";
|
||||
}
|
||||
|
||||
@GetMapping("/intern/accounting/addManual")
|
||||
public String accountingAddManual() {
|
||||
return "intern/accounting/addManual";
|
||||
}
|
||||
|
||||
@GetMapping("/intern/warehouse/")
|
||||
public String accountingWarehouse() {
|
||||
return "intern/warehouse/index";
|
||||
}
|
||||
|
||||
@GetMapping("/intern/warehouse/todo")
|
||||
public String accountingWarehouseTodo() {
|
||||
return "intern/warehouse/todo";
|
||||
}
|
||||
|
||||
@GetMapping("/intern/warehouse/addManual")
|
||||
public String accountingWarehouseAddManual() {
|
||||
return "intern/warehouse/addManual";
|
||||
}
|
||||
|
||||
@PostMapping("/intern/warehouse/progress/{id}")
|
||||
public String accountingWarehouseProgressIdPost(HttpServletResponse response) {
|
||||
if ((notSoRandom++) % 2 == 1) {
|
||||
return "redirect:/intern/warehouse/progress/450";
|
||||
} else {
|
||||
response.setStatus(409);
|
||||
return "intern/warehouse/error_progress_failed";
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("/intern/warehouse/progress/{id}")
|
||||
public String accountingWarehouseProgressId() {
|
||||
return "intern/warehouse/id_progress";
|
||||
}
|
||||
|
||||
@GetMapping("/intern/warehouse/slots/")
|
||||
public String accountingWarehouseSlots() {
|
||||
return "intern/warehouse/slots/index";
|
||||
}
|
||||
@GetMapping("/intern/customerOrders/{id}")
|
||||
public String internCustomerOrdersId() {
|
||||
return "intern/customerOrders/id";
|
||||
}
|
||||
}
|
||||
|
@ -1,36 +0,0 @@
|
||||
package org.hso.ecommerce.app;
|
||||
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
@Controller
|
||||
@RequestMapping("user")
|
||||
public class UserRequestController {
|
||||
|
||||
@GetMapping("/")
|
||||
public String user() {
|
||||
return "redirect:/user/settings";
|
||||
}
|
||||
|
||||
@GetMapping("/settings")
|
||||
public String userSettings() {
|
||||
return "user/settings";
|
||||
}
|
||||
|
||||
@GetMapping("/orders/")
|
||||
public String userOrdeers() {
|
||||
return "user/orders/index";
|
||||
}
|
||||
|
||||
@GetMapping("/bonuspoints")
|
||||
public String userBonuspoints() {
|
||||
return "user/bonuspoints";
|
||||
}
|
||||
|
||||
@GetMapping("/notifications/")
|
||||
public String userNotifications() {
|
||||
return "user/notifications/index";
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ public class LoginIntercepter implements HandlerInterceptor {
|
||||
|
||||
HttpSession session = request.getSession();
|
||||
Object userId = session.getAttribute("userId");
|
||||
Optional<User> user = null;
|
||||
|
||||
if (request.getRequestURI().startsWith("/user/")) {
|
||||
System.out.println("USER");
|
||||
@ -43,10 +44,24 @@ public class LoginIntercepter implements HandlerInterceptor {
|
||||
response.sendRedirect("/login");
|
||||
return false;
|
||||
}
|
||||
|
||||
user = userRepository.findById((Long) userId);
|
||||
|
||||
if(user.isPresent() && !user.get().isEmployee)
|
||||
{
|
||||
session.setAttribute("afterLogin", request.getRequestURI());
|
||||
response.sendRedirect("/");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!request.getRequestURI().startsWith("/login")) {
|
||||
session.removeAttribute("afterLogin");
|
||||
}
|
||||
|
||||
if (userId != null) {
|
||||
Optional<User> user = userRepository.findById((Long) userId);
|
||||
if (user == null)
|
||||
user = userRepository.findById((Long) userId);
|
||||
user.ifPresent(value -> request.setAttribute("user", value));
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,43 @@
|
||||
package org.hso.ecommerce.components;
|
||||
|
||||
import org.hso.ecommerce.entities.shop.ShoppingCart;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
public class ShoppingCartInterceptor implements HandlerInterceptor {
|
||||
|
||||
@Override
|
||||
public boolean preHandle(
|
||||
HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
||||
|
||||
HttpSession session = request.getSession();
|
||||
Object shoppingCart = session.getAttribute("shoppingCart");
|
||||
|
||||
if (shoppingCart == null) {
|
||||
shoppingCart = new ShoppingCart();
|
||||
}
|
||||
|
||||
request.setAttribute("shoppingCart", shoppingCart);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postHandle(
|
||||
HttpServletRequest request, HttpServletResponse response, Object handler,
|
||||
ModelAndView modelAndView) throws Exception {
|
||||
|
||||
HttpSession session = request.getSession();
|
||||
Object shoppingCart = request.getAttribute("shoppingCart");
|
||||
session.setAttribute("shoppingCart", shoppingCart);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
|
||||
Object handler, Exception exception) throws Exception {
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package org.hso.ecommerce.components;
|
||||
|
||||
import org.hso.ecommerce.entities.warehouse.Slot;
|
||||
import org.hso.ecommerce.repos.warehouse.SlotRepository;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
@Component
|
||||
public class SlotInitializer {
|
||||
|
||||
@Autowired
|
||||
private final SlotRepository slotRepository = null;
|
||||
|
||||
// TODO: use values form cfg.
|
||||
private final int NUM_SLOTS = 50;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
for (int i = 1; i <= NUM_SLOTS; i++) {
|
||||
if (!slotRepository.findBySlotNum(i).isPresent()) {
|
||||
Slot slotAdded = new Slot();
|
||||
slotAdded.slotNum = i;
|
||||
slotRepository.save(slotAdded);
|
||||
|
||||
System.out.println("Added Slot " + i + " to DB");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
package org.hso.ecommerce.controller;
|
||||
|
||||
import org.springframework.stereotype.Controller;
|
||||
|
||||
@Controller
|
||||
//@RequestMapping("...")
|
||||
public class BookingController {
|
||||
}
|
@ -1,8 +1,69 @@
|
||||
package org.hso.ecommerce.controller;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
import org.hso.ecommerce.entities.user.User;
|
||||
import org.hso.ecommerce.repos.user.UserRepository;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
||||
@Controller
|
||||
//@RequestMapping("...")
|
||||
@RequestMapping("/")
|
||||
public class LoginController {
|
||||
|
||||
@Autowired
|
||||
private final UserRepository userRepository = null;
|
||||
|
||||
@GetMapping("login")
|
||||
public String login() {
|
||||
return "login";
|
||||
}
|
||||
|
||||
@PostMapping("login")
|
||||
public String loginPost(HttpServletRequest request, HttpServletResponse response,
|
||||
@RequestParam("username") String username, @RequestParam("password") String password, HttpSession session) {
|
||||
|
||||
String gto = (String) session.getAttribute("afterLogin");
|
||||
|
||||
Optional<User> user = userRepository.findByEmail(username);
|
||||
if (!user.isPresent()) {
|
||||
request.setAttribute("error", "Die Email Adresse falsch.");
|
||||
response.setStatus(HttpServletResponse.SC_EXPECTATION_FAILED);
|
||||
return "login";
|
||||
}
|
||||
|
||||
if (!user.get().validatePassword(password)) {
|
||||
request.setAttribute("error", "Das Passwort ist falsch.");
|
||||
response.setStatus(HttpServletResponse.SC_EXPECTATION_FAILED);
|
||||
return "login";
|
||||
}
|
||||
|
||||
if (!user.get().isActive) {
|
||||
request.setAttribute("error", "Dieses Konto ist deaktiviert..");
|
||||
response.setStatus(HttpServletResponse.SC_EXPECTATION_FAILED);
|
||||
return "login";
|
||||
}
|
||||
|
||||
session.setAttribute("userId", user.get().getId());
|
||||
|
||||
if (gto != null && gto.startsWith("/")) {
|
||||
return "redirect:" + gto;
|
||||
} else {
|
||||
return "redirect:/";
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping("logout")
|
||||
public String logoutPost(HttpServletResponse response, HttpSession session) {
|
||||
session.removeAttribute("userId");
|
||||
return "redirect:/";
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,82 @@
|
||||
package org.hso.ecommerce.controller;
|
||||
|
||||
import org.hso.ecommerce.entities.booking.PaymentMethod;
|
||||
import org.hso.ecommerce.entities.shop.Address;
|
||||
import org.hso.ecommerce.entities.user.User;
|
||||
import org.hso.ecommerce.repos.user.UserRepository;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
import java.util.Optional;
|
||||
|
||||
@Controller
|
||||
public class RegisterController {
|
||||
|
||||
@Autowired
|
||||
private final UserRepository userRepository = null;
|
||||
|
||||
@PostMapping("/register")
|
||||
public String registerPost(HttpServletRequest request, HttpServletResponse response,
|
||||
@RequestParam("username") String username, @RequestParam("password") String password,
|
||||
@RequestParam("password2") String password2, @RequestParam("salutation") String salutation,
|
||||
@RequestParam("name") String name, @RequestParam("address") String address,
|
||||
@RequestParam("type") String type, // TODO store
|
||||
@RequestParam("ad") String ad, // TODO store
|
||||
HttpSession session) {
|
||||
Optional<User> user = userRepository.findByEmail(username);
|
||||
if (user.isPresent()) {
|
||||
request.setAttribute("error", "Die Email Adresse existiert bereits.");
|
||||
response.setStatus(HttpServletResponse.SC_EXPECTATION_FAILED);
|
||||
return "register";
|
||||
}
|
||||
|
||||
if (!password.equals(password2)) {
|
||||
request.setAttribute("error", "Die Passwörter stimmen nicht überein.");
|
||||
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
|
||||
return "register";
|
||||
}
|
||||
|
||||
// set values for new user
|
||||
User newUser = new User();
|
||||
newUser.email = username;
|
||||
newUser.setPassword(password);
|
||||
newUser.email = username;
|
||||
newUser.isEmployee = false;
|
||||
newUser.salutation = salutation;
|
||||
newUser.defaultPayment = PaymentMethod.fromCreditCardNumber("");
|
||||
|
||||
newUser.isActive = true;
|
||||
newUser.created = new java.sql.Timestamp(System.currentTimeMillis());
|
||||
|
||||
Address newAddress = new Address();
|
||||
newAddress.name = name;
|
||||
newAddress.addressString = address;
|
||||
newUser.defaultDeliveryAddress = newAddress;
|
||||
|
||||
userRepository.save(newUser); // save newUser
|
||||
|
||||
user = userRepository.findByEmail(username);
|
||||
session.setAttribute("userId", user.get().getId());
|
||||
|
||||
String gto = (String) session.getAttribute("afterLogin");
|
||||
|
||||
//login after register
|
||||
if (gto != null && gto.startsWith("/")) {
|
||||
return "redirect:" + gto;
|
||||
} else {
|
||||
return "redirect:/";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@GetMapping("/register")
|
||||
public String register() {
|
||||
return "register";
|
||||
}
|
||||
}
|
@ -1,8 +1,131 @@
|
||||
package org.hso.ecommerce.controller;
|
||||
|
||||
import org.hso.ecommerce.action.user.UpdateUserSettingsAction;
|
||||
import org.hso.ecommerce.entities.shop.Address;
|
||||
import org.hso.ecommerce.entities.shop.CustomerOrder;
|
||||
import org.hso.ecommerce.entities.user.User;
|
||||
import org.hso.ecommerce.repos.shop.CustomerOrderRepository;
|
||||
import org.hso.ecommerce.repos.user.UserRepository;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpSession;
|
||||
import java.util.List;
|
||||
|
||||
@Controller
|
||||
//@RequestMapping("...")
|
||||
@RequestMapping("/user")
|
||||
public class UserController {
|
||||
|
||||
@Autowired
|
||||
private final UserRepository userRepository = null;
|
||||
|
||||
@Autowired
|
||||
private final CustomerOrderRepository customerOrderRepository = null;
|
||||
|
||||
@GetMapping("/")
|
||||
public String user() {
|
||||
return "redirect:/user/settings";
|
||||
}
|
||||
|
||||
@GetMapping("/settings")
|
||||
public String userSettings(Model model,
|
||||
HttpSession session
|
||||
) {
|
||||
long userId = (long) session.getAttribute("userId");
|
||||
User user = userRepository.findById(userId).get();
|
||||
if(user.defaultDeliveryAddress == null){
|
||||
user.defaultDeliveryAddress = new Address();
|
||||
}
|
||||
model.addAttribute("user", user);
|
||||
|
||||
return "user/settings";
|
||||
}
|
||||
|
||||
@GetMapping("/orders/")
|
||||
public String userOrdeers(HttpSession session,
|
||||
Model model
|
||||
) {
|
||||
List<CustomerOrder> orders = customerOrderRepository.getOrdersByUserId((long) session.getAttribute("userId"));
|
||||
model.addAttribute("orders", orders);
|
||||
|
||||
return "user/orders/index";
|
||||
}
|
||||
|
||||
@PostMapping("/settings/changeMail")
|
||||
public String changeMail(HttpSession session,
|
||||
@RequestParam("email") String email,
|
||||
HttpServletRequest request
|
||||
) {
|
||||
User user = userRepository.findById((long) session.getAttribute("userId")).get();
|
||||
|
||||
UpdateUserSettingsAction cusa = new UpdateUserSettingsAction(user, userRepository);
|
||||
UpdateUserSettingsAction.UpdateResult result = cusa.updateEmail(email);
|
||||
if (result.updated == false) {
|
||||
request.setAttribute("error", result.errorString);
|
||||
return "user/settings";
|
||||
}
|
||||
|
||||
return "redirect:/user/settings";
|
||||
}
|
||||
|
||||
@PostMapping("/settings/changePwd")
|
||||
public String changePwd(HttpSession session,
|
||||
@RequestParam("old-password") String oldPassword,
|
||||
@RequestParam("password1") String password1,
|
||||
@RequestParam("password2") String password2,
|
||||
HttpServletRequest request
|
||||
) {
|
||||
User user = userRepository.findById((long) session.getAttribute("userId")).get();
|
||||
|
||||
UpdateUserSettingsAction cusa = new UpdateUserSettingsAction(user, userRepository);
|
||||
UpdateUserSettingsAction.UpdateResult result = cusa.updatePassword(oldPassword, password1, password2);
|
||||
if (result.updated == false) {
|
||||
request.setAttribute("error", result.errorString);
|
||||
return "user/settings";
|
||||
}
|
||||
|
||||
return "redirect:/user/settings";
|
||||
}
|
||||
|
||||
@PostMapping("/settings/changeAddress")
|
||||
public String changeAddress(HttpSession session,
|
||||
@RequestParam("salutation") String salutation,
|
||||
@RequestParam("name") String name,
|
||||
@RequestParam("address") String address,
|
||||
HttpServletRequest request
|
||||
) {
|
||||
User user = userRepository.findById((long) session.getAttribute("userId")).get();
|
||||
|
||||
UpdateUserSettingsAction cusa = new UpdateUserSettingsAction(user, userRepository);
|
||||
UpdateUserSettingsAction.UpdateResult result = cusa.updateShippingInfo(salutation, name, address);
|
||||
if (result.updated == false) {
|
||||
request.setAttribute("error", result.errorString);
|
||||
return "user/settings";
|
||||
}
|
||||
|
||||
return "redirect:/user/settings";
|
||||
}
|
||||
|
||||
@PostMapping("/settings/changePaymentInfo")
|
||||
public String changePaymentInfo(HttpSession session,
|
||||
@RequestParam("creditCardNumber") String creditCardNumber,
|
||||
HttpServletRequest request
|
||||
) {
|
||||
User user = userRepository.findById((long) session.getAttribute("userId")).get();
|
||||
|
||||
UpdateUserSettingsAction cusa = new UpdateUserSettingsAction(user, userRepository);
|
||||
UpdateUserSettingsAction.UpdateResult result = cusa.updatePaymentInfo(creditCardNumber);
|
||||
if (result.updated == false) {
|
||||
request.setAttribute("error", result.errorString);
|
||||
return "user/settings";
|
||||
}
|
||||
|
||||
return "redirect:/user/settings";
|
||||
}
|
||||
}
|
||||
|
@ -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.CustomerOrderRepository;
|
||||
import org.hso.ecommerce.repos.supplier.ArticleOfferRepository;
|
||||
import org.hso.ecommerce.repos.supplier.SupplierOrderRepository;
|
||||
import org.hso.ecommerce.repos.supplier.SupplierRepository;
|
||||
import org.hso.ecommerce.repos.warehouse.WarehouseBookingPositionSlotEntryRepository;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
interface ICronjob {
|
||||
/**
|
||||
* Calculate the earliest cronjob execution time that happens after the given reference time.
|
||||
*
|
||||
* @param reference Position in time to start searching. The implementor is allowed to modify the reference time.
|
||||
* @return A new Calendar instance (or the same) containing the time for next execution.
|
||||
*/
|
||||
Calendar nextExecution(Calendar reference);
|
||||
|
||||
/**
|
||||
* Calculate the latest cronjob execution time that happens before or exactly at the given refernce time.
|
||||
*
|
||||
* @param reference Position in time to start searching. The implementor is allowed to modify the reference time.
|
||||
* @return A new Calendar instance (or the same) containing the time of the last execution.
|
||||
*/
|
||||
Calendar previousExecution(Calendar reference);
|
||||
|
||||
/**
|
||||
* Execute this cronjob.
|
||||
*
|
||||
* @param time The point in time this execution was scheduled. In case of a missed cronjob, the actual time of
|
||||
* this call might be much later.
|
||||
* @param controller Back-reference that allows to use repositories.
|
||||
*/
|
||||
void executeAt(Calendar time, CronjobController controller);
|
||||
}
|
||||
|
||||
@Component
|
||||
class Reorder implements ICronjob {
|
||||
@Override
|
||||
public Calendar nextExecution(Calendar reference) {
|
||||
if (reference.get(Calendar.HOUR_OF_DAY) >= 8) {
|
||||
reference.add(Calendar.DAY_OF_MONTH, 1);
|
||||
}
|
||||
reference.set(Calendar.HOUR_OF_DAY, 8);
|
||||
reference.set(Calendar.MINUTE, 0);
|
||||
reference.set(Calendar.SECOND, 0);
|
||||
reference.set(Calendar.MILLISECOND, 0);
|
||||
return reference;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Calendar previousExecution(Calendar reference) {
|
||||
if (reference.get(Calendar.HOUR_OF_DAY) < 8) {
|
||||
reference.add(Calendar.DAY_OF_MONTH, -1);
|
||||
}
|
||||
reference.set(Calendar.HOUR_OF_DAY, 8);
|
||||
reference.set(Calendar.MINUTE, 0);
|
||||
reference.set(Calendar.SECOND, 0);
|
||||
reference.set(Calendar.MILLISECOND, 0);
|
||||
return reference;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the amount of ordered articles by customers for the given article type in the time between begin and
|
||||
* end.
|
||||
*
|
||||
* @param article The article to search orders for.
|
||||
* @param begin The start time for the search (included)
|
||||
* @param end The end time for the search (excluded)
|
||||
* @return The number of articles that were ordered by customers in the given range.
|
||||
*/
|
||||
private Integer getOrderedAmounts(Article article, Calendar begin, Calendar end, CronjobController controller) {
|
||||
return controller.customerOrderRepository.countOrdersOfArticleInTimespan(
|
||||
article.id,
|
||||
new Timestamp(begin.getTimeInMillis()), new Timestamp(end.getTimeInMillis()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the amount of ordered articles by customers for the given article type in the three days before the
|
||||
* given reference time. The return-array contains 3 fields: Index 0: Orders 72 to 48 hours ago; Index 1: Orders 48
|
||||
* to 24 hours ago; Index 2: Orders 24 to 0 hours ago.
|
||||
*
|
||||
* @param article The article for which the customer orders are checked.
|
||||
* @param time The reference time to use for calculation of the last orders.
|
||||
* @return A 3-element array containing the orders of the last three days.
|
||||
*/
|
||||
private Integer[] getOrderedAmounts(Article article, Calendar time, CronjobController controller) {
|
||||
Calendar oneDayBefore = (Calendar) time.clone();
|
||||
oneDayBefore.add(Calendar.DAY_OF_MONTH, -1);
|
||||
Calendar twoDaysBefore = (Calendar) time.clone();
|
||||
twoDaysBefore.add(Calendar.DAY_OF_MONTH, -2);
|
||||
Calendar threeDaysBefore = (Calendar) time.clone();
|
||||
threeDaysBefore.add(Calendar.DAY_OF_MONTH, -3);
|
||||
|
||||
return new Integer[] { //
|
||||
getOrderedAmounts(article, threeDaysBefore, twoDaysBefore, controller), //
|
||||
getOrderedAmounts(article, twoDaysBefore, oneDayBefore, controller), //
|
||||
getOrderedAmounts(article, oneDayBefore, time, controller), //
|
||||
};
|
||||
}
|
||||
|
||||
private HashMap<ArticleIdentifier, ArticleOffer> mapArticleOffers(List<ArticleOffer> articleOffers) {
|
||||
HashMap<ArticleIdentifier, ArticleOffer> map = new HashMap<>();
|
||||
for (ArticleOffer articleOffer : articleOffers) {
|
||||
ArticleIdentifier identifier = new ArticleIdentifier(articleOffer.manufacturer, articleOffer.articleNumber);
|
||||
map.put(identifier, articleOffer);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void executeAt(Calendar time, CronjobController controller) {
|
||||
List<Supplier> suppliers = controller.supplierRepository.findAll();
|
||||
ReadSupplierDataAction.Result supplierData = new ReadSupplierDataAction(suppliers).finish();
|
||||
|
||||
// Save the new offers in the database
|
||||
List<ArticleOffer> allOffers = controller.articleOfferRepository.findAll();
|
||||
allOffers = new UpdateOffersAction(allOffers, supplierData.cheapestOffer).finish();
|
||||
controller.articleOfferRepository.saveAll(allOffers);
|
||||
|
||||
HashMap<ArticleIdentifier, ArticleOffer> mappedOffers = mapArticleOffers(allOffers);
|
||||
|
||||
// Reorder
|
||||
List<Article> allArticles = controller.articleRepository.findAll();
|
||||
for (Article article : allArticles) {
|
||||
Integer[] orderedAmounts = getOrderedAmounts(article, time, controller);
|
||||
|
||||
Integer undeliveredReorders = controller.supplierOrderRepository
|
||||
.countUndeliveredReorders(article.related.articleNumber);
|
||||
|
||||
int amountInStock = controller.warehouseBookingPositionSlotEntryRepository.getArticleStock(article.id)
|
||||
.orElse(0);
|
||||
|
||||
ReorderAction action = new ReorderAction(article, orderedAmounts,
|
||||
undeliveredReorders,
|
||||
amountInStock,
|
||||
supplierData.cheapestOffer, mappedOffers);
|
||||
SupplierOrder order = action.finish();
|
||||
if (order != null) {
|
||||
controller.supplierOrderRepository.save(order);
|
||||
|
||||
// Create bookings for this order
|
||||
int netPrice = order.totalPriceNet;
|
||||
int vatPercent = order.ordered.vatPercent;
|
||||
int vatAmount = netPrice * vatPercent / 100;
|
||||
int grossPrice = netPrice + vatAmount;
|
||||
|
||||
// Obligation towards the supplier
|
||||
BookingAccountEntry mainAccount = controller.bookingAccountEntryRepository.getByMain()
|
||||
.orElseGet(BookingAccountEntry::newMain);
|
||||
BookingAccountEntry supplierAccount = controller.bookingAccountEntryRepository
|
||||
.getBySupplier(order.supplier.id)
|
||||
.orElseGet(() -> BookingAccountEntry.newSupplier(order.supplier));
|
||||
BookingReason obligationReason = new BookingReason(order);
|
||||
Booking obligationBooking = new CreateBookingAction(mainAccount,
|
||||
supplierAccount,
|
||||
obligationReason,
|
||||
grossPrice).finish();
|
||||
controller.bookingRepository.save(obligationBooking);
|
||||
|
||||
// Input Tax
|
||||
BookingAccountEntry vatAccount = controller.bookingAccountEntryRepository.getByVat()
|
||||
.orElseGet(BookingAccountEntry::newVat);
|
||||
mainAccount = controller.bookingAccountEntryRepository.getByMain().get();
|
||||
BookingReason inputTaxReason = new BookingReason(order);
|
||||
Booking inputTaxBooking = new CreateBookingAction(vatAccount, mainAccount, inputTaxReason, vatAmount)
|
||||
.finish();
|
||||
controller.bookingRepository.save(inputTaxBooking);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ScheduledCronjob {
|
||||
public final Calendar executionTime;
|
||||
public final ICronjob cronjob;
|
||||
public final BackgroundJob model;
|
||||
|
||||
public ScheduledCronjob(Calendar executionTime, ICronjob cronjob, BackgroundJob model) {
|
||||
this.executionTime = executionTime;
|
||||
this.cronjob = cronjob;
|
||||
this.model = model;
|
||||
}
|
||||
}
|
||||
|
||||
@Component
|
||||
class CronjobController {
|
||||
private static final Logger log = LoggerFactory.getLogger(CronjobController.class);
|
||||
|
||||
private static final Map<String, ICronjob> cronjobs = getCronjobs();
|
||||
|
||||
@Autowired
|
||||
private final BackgroundJobRepository cronjobRepository = null;
|
||||
|
||||
@Autowired
|
||||
final ArticleRepository articleRepository = null;
|
||||
|
||||
@Autowired
|
||||
final ArticleOfferRepository articleOfferRepository = null;
|
||||
|
||||
@Autowired
|
||||
final CustomerOrderRepository 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();
|
||||
}
|
||||
}
|
@ -1,8 +1,272 @@
|
||||
package org.hso.ecommerce.controller.intern;
|
||||
|
||||
import org.hso.ecommerce.entities.shop.Article;
|
||||
import org.hso.ecommerce.entities.shop.Category;
|
||||
import org.hso.ecommerce.entities.shop.Image;
|
||||
import org.hso.ecommerce.entities.supplier.ArticleOffer;
|
||||
import org.hso.ecommerce.repos.shop.ArticleRepository;
|
||||
import org.hso.ecommerce.repos.shop.CategoryRepository;
|
||||
import org.hso.ecommerce.repos.shop.ImageRepository;
|
||||
import org.hso.ecommerce.repos.shop.OffersRepository;
|
||||
import org.hso.ecommerce.repos.warehouse.WarehouseBookingPositionSlotEntryRepository;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.util.DigestUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.view.RedirectView;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Controller
|
||||
//@RequestMapping("...")
|
||||
@RequestMapping("intern/articles")
|
||||
public class InternArticleController {
|
||||
@Autowired
|
||||
private final ArticleRepository articleRepository = null;
|
||||
|
||||
@Autowired
|
||||
private final WarehouseBookingPositionSlotEntryRepository warehouseEntryRepository = null;
|
||||
|
||||
@Autowired
|
||||
private final CategoryRepository categoryRepository = null;
|
||||
|
||||
@Autowired
|
||||
private final OffersRepository offersRepository = null;
|
||||
|
||||
@Autowired
|
||||
private final ImageRepository imageRepository = null;
|
||||
|
||||
@GetMapping("/")
|
||||
public String internListedArticles(Model model) {
|
||||
|
||||
List<UImodelArticles> totals = new ArrayList<UImodelArticles>();
|
||||
|
||||
for (Article article : articleRepository.findAll()) {
|
||||
UImodelArticles tmp = new UImodelArticles();
|
||||
tmp.addListedArticle(article, warehouseEntryRepository.getArticleStock(article.id).orElse(0));
|
||||
totals.add(tmp);
|
||||
}
|
||||
|
||||
model.addAttribute("ListedArticles", totals);
|
||||
return "intern/listedArticles/index";
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public String internListedArticlesId(Model model, @PathVariable String id) {
|
||||
int articleid = Integer.parseInt(id);
|
||||
UImodelArticle total = new UImodelArticle();
|
||||
total.addArticle(articleRepository.findArticleById(articleid),
|
||||
warehouseEntryRepository.getArticleStock(articleid).orElse(0));
|
||||
|
||||
model.addAttribute("ArticleID", total);
|
||||
return "intern/listedArticles/id";
|
||||
}
|
||||
|
||||
@PostMapping("/{id}/saveChanges")
|
||||
public RedirectView saveChanges(@PathVariable(required = true) int id,
|
||||
@RequestParam(value = "title", required = true) String title,
|
||||
@RequestParam(value = "description", required = true) String description,
|
||||
@RequestParam(value = "units-per-slot", required = true) String warehouseUnitsPerSlot,
|
||||
@RequestParam(value = "priceNet", required = true) String pricenetto,
|
||||
@RequestParam(value = "reorderMaxPrice", required = true) String reorderMaxPrice,
|
||||
@RequestParam(value = "autobuy", required = true) Boolean shouldReorder,
|
||||
@RequestParam(value = "categorie", required = true) String categories,
|
||||
@RequestParam(value = "img", required = true) MultipartFile imgFile) {
|
||||
|
||||
Article tmpArticle = articleRepository.findArticleById(id); // get the old article
|
||||
|
||||
String[] separatedCategories = categories.split("\n");
|
||||
|
||||
tmpArticle.categories.clear();
|
||||
|
||||
// loop through all categories strings and create a new category if a new one;
|
||||
// also adds the categories to the article
|
||||
for (String category : separatedCategories) {
|
||||
tmpArticle.categories.add(categoryRepository.findCategoryByName(category.trim())
|
||||
.orElseGet(() -> new Category(category.trim())));
|
||||
}
|
||||
|
||||
tmpArticle.shouldReorder = shouldReorder;
|
||||
tmpArticle.reorderMaxPrice = (int) (Float.parseFloat(reorderMaxPrice) * 100);
|
||||
tmpArticle.shopPricePerUnitNetCent = (int) (Float.parseFloat(pricenetto) * 100);
|
||||
tmpArticle.warehouseUnitsPerSlot = Integer.parseInt(warehouseUnitsPerSlot);
|
||||
tmpArticle.title = title;
|
||||
updateImage(tmpArticle, imgFile);
|
||||
tmpArticle.description = description;
|
||||
|
||||
articleRepository.save(tmpArticle); // save updated article
|
||||
return new RedirectView("../"); // return to overview page
|
||||
|
||||
}
|
||||
|
||||
@PostMapping("/addArticle/{id}")
|
||||
public RedirectView addArticle(@PathVariable(required = true) String id) {
|
||||
// article is not already listed, create new one
|
||||
int offeredArticleID = Integer.parseInt(id);
|
||||
|
||||
Article tmpArticle = new Article();
|
||||
|
||||
ArticleOffer offeredArticle = offersRepository.findOfferedArticleById(offeredArticleID);
|
||||
|
||||
// set default values
|
||||
tmpArticle.description = "";
|
||||
tmpArticle.reorderMaxPrice = 0;
|
||||
tmpArticle.shopPricePerUnitNetCent = offeredArticle.pricePerUnitNet;
|
||||
tmpArticle.shouldReorder = false;
|
||||
tmpArticle.title = offeredArticle.title;
|
||||
tmpArticle.warehouseUnitsPerSlot = 1;
|
||||
setDefaultImage(tmpArticle);
|
||||
tmpArticle.related = offeredArticle;
|
||||
articleRepository.save(tmpArticle); // save new article
|
||||
|
||||
// return to edit article page
|
||||
return new RedirectView("../" + articleRepository.findArticleIDByRelatedID(offeredArticleID).get());
|
||||
}
|
||||
|
||||
private void setDefaultImage(Article article) {
|
||||
String defaultImagePath = "./data/img/no_product_img.jpg"; // path + name of default img
|
||||
Optional<Integer> imageID = imageRepository.findImageIDByPath(defaultImagePath); // get default img
|
||||
|
||||
if (imageID.isPresent()) {
|
||||
// default img is in DB
|
||||
article.image = imageRepository.findImageById(imageID.get()); // set default img to new article
|
||||
} else {
|
||||
// default img is not in DB
|
||||
File tmpFile = new File(defaultImagePath);
|
||||
// test if default img file exits
|
||||
if (!tmpFile.exists()) {
|
||||
// fallback if the file not exists
|
||||
// create new file
|
||||
BufferedImage bufferedImage = new BufferedImage(422, 428, BufferedImage.TYPE_INT_RGB);
|
||||
try {
|
||||
ImageIO.write(bufferedImage, "jpg", new File(defaultImagePath)); // save new file on disk
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
Image defaultImage = new Image();
|
||||
defaultImage.path = defaultImagePath; // set new file to default img
|
||||
imageRepository.save(defaultImage); // save default img
|
||||
article.image = defaultImage; // set default img to new article
|
||||
}
|
||||
}
|
||||
|
||||
private void updateImage(Article article, MultipartFile imgFile) {
|
||||
// check if a file is present
|
||||
if (imgFile.getSize() > 0) {
|
||||
try {
|
||||
// get file name based on MD5 hash
|
||||
String fileName = DigestUtils.md5DigestAsHex(imgFile.getBytes()) + ".jpg";
|
||||
// check if img is already in system
|
||||
Optional<Integer> imageID = imageRepository.findImageIDByPath("./data/img/" + fileName);
|
||||
if (imageID.isPresent()) {
|
||||
article.image = imageRepository.findImageById(imageID.get()); // add existing img to article
|
||||
} else {
|
||||
// write new img file to disk
|
||||
Files.newOutputStream(Paths.get("./data/img/", fileName)).write(imgFile.getBytes());
|
||||
// create new img
|
||||
Image newImage = new Image();
|
||||
newImage.path = "./data/img/" + fileName; // set new file to new img
|
||||
imageRepository.save(newImage); // save new img
|
||||
article.image = newImage; // set new img to article
|
||||
}
|
||||
} catch (Exception e) {
|
||||
setDefaultImage(article); // if upload failed, reset to default img
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class UImodelArticles {
|
||||
|
||||
public String imgPath;
|
||||
public String title;
|
||||
public String price;
|
||||
public String priceNet;
|
||||
public String categorie;
|
||||
public int stock;
|
||||
public long offerID;
|
||||
public long id;
|
||||
|
||||
void addListedArticle(Article article, int stock) {
|
||||
|
||||
if (article.image != null) {
|
||||
this.imgPath = article.image.path;
|
||||
}
|
||||
this.title = article.title;
|
||||
this.priceNet = String.format("%.2f", ((float) article.shopPricePerUnitNetCent / 100));
|
||||
this.price = String.format("%.2f", ((float) article.getPriceGross() / 100));
|
||||
|
||||
StringBuilder result = new StringBuilder();
|
||||
|
||||
for (Category temp : article.categories) {
|
||||
result.append(temp.name + " ");
|
||||
}
|
||||
this.categorie = result.toString();
|
||||
|
||||
this.stock = stock;
|
||||
this.offerID = article.related.id;
|
||||
this.id = article.id;
|
||||
}
|
||||
}
|
||||
|
||||
public static class UImodelArticle {
|
||||
|
||||
public String imgPath;
|
||||
public String title;
|
||||
public String price;
|
||||
public String priceNet;
|
||||
public String reorderMaxPrice;
|
||||
public String categorie;
|
||||
public int stock;
|
||||
public long offerID;
|
||||
public long id;
|
||||
public boolean shouldReorder;
|
||||
public String warehouseUnitsPerSlot;
|
||||
public String description;
|
||||
public int vatPercent;
|
||||
|
||||
public String getCategorie() {
|
||||
return categorie;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
void addArticle(Article article, int stock) {
|
||||
if (article.image != null) {
|
||||
this.imgPath = article.image.path;
|
||||
}
|
||||
this.title = article.title;
|
||||
this.priceNet = String.format("%.2f", ((float) article.shopPricePerUnitNetCent / 100));
|
||||
this.price = String.format("%.2f", ((float) article.getPriceGross() / 100));
|
||||
|
||||
StringBuilder result = new StringBuilder();
|
||||
|
||||
for (Category temp : article.categories) {
|
||||
result.append(temp.name);
|
||||
result.append("\n");
|
||||
}
|
||||
this.categorie = result.toString();
|
||||
|
||||
this.stock = stock;
|
||||
this.offerID = article.related.id;
|
||||
this.id = article.id;
|
||||
this.reorderMaxPrice = String.format("%.2f", ((float) article.reorderMaxPrice / 100));
|
||||
this.shouldReorder = article.shouldReorder;
|
||||
this.warehouseUnitsPerSlot = String.valueOf(article.warehouseUnitsPerSlot);
|
||||
this.description = article.description;
|
||||
this.vatPercent = article.related.vatPercent;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,8 +1,34 @@
|
||||
package org.hso.ecommerce.controller.intern;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.hso.ecommerce.controller.intern.accounting.AccountingController;
|
||||
import org.hso.ecommerce.entities.booking.BookingAccountEntry;
|
||||
import org.hso.ecommerce.repos.booking.BookingAccountEntryRepository;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
@Controller
|
||||
//@RequestMapping("...")
|
||||
@RequestMapping("/intern")
|
||||
public class InternIndexController {
|
||||
|
||||
@Autowired
|
||||
private final BookingAccountEntryRepository bookingAccountEntryRepository = null;
|
||||
|
||||
@GetMapping("/")
|
||||
public String intern(Model model) {
|
||||
Optional<BookingAccountEntry> mainAccount = bookingAccountEntryRepository.getByMain();
|
||||
int mainAccountBalance = mainAccount.map(entry -> entry.newSumCent).orElse(0);
|
||||
Optional<BookingAccountEntry> vatAccount = bookingAccountEntryRepository.getByVat();
|
||||
int vatAccountBalance = vatAccount.map(entry -> entry.newSumCent).orElse(0);
|
||||
|
||||
model.addAttribute("mainAccountBalance", AccountingController.fmtEuro(mainAccountBalance));
|
||||
model.addAttribute("vatAccountBalance", AccountingController.fmtEuro(vatAccountBalance));
|
||||
|
||||
return "intern/index";
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,8 +1,27 @@
|
||||
package org.hso.ecommerce.controller.intern;
|
||||
|
||||
import org.hso.ecommerce.repos.warehouse.WarehouseBookingRepository;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
@Controller
|
||||
//@RequestMapping("...")
|
||||
@RequestMapping("/intern/warehouse/")
|
||||
public class WarehouseController {
|
||||
|
||||
@Autowired
|
||||
private final WarehouseBookingRepository warehouseBookingRepository = null;
|
||||
|
||||
@GetMapping("/")
|
||||
public String accountingWarehouse(
|
||||
Model model,
|
||||
HttpServletRequest request
|
||||
) {
|
||||
model.addAttribute("bookings", warehouseBookingRepository.findAll());
|
||||
return "intern/warehouse/index";
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,223 @@
|
||||
package org.hso.ecommerce.controller.intern.accounting;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
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.repos.booking.BookingRepository;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
@Controller
|
||||
public class AccountingController {
|
||||
|
||||
@Autowired
|
||||
private BookingRepository bookingRepository = null;
|
||||
|
||||
/**
|
||||
* A description used to render the html template for the overview of all bookings
|
||||
*/
|
||||
static class TemplateBooking {
|
||||
public String datetime;
|
||||
public String amount;
|
||||
public String source;
|
||||
public String sourceAddr;
|
||||
public String sourceBalance;
|
||||
public String destination;
|
||||
public String destinationAddr;
|
||||
public String destinationBalance;
|
||||
public String reason;
|
||||
public String reference;
|
||||
public String referenceAddr;
|
||||
}
|
||||
|
||||
/**
|
||||
* A description used to render the html template for bookings on a specific account
|
||||
*/
|
||||
public static class ShortTemplateBooking {
|
||||
public String datetime;
|
||||
public String amount;
|
||||
public String source;
|
||||
public String sourceAddr;
|
||||
public String balance;
|
||||
public String reason;
|
||||
public String reference;
|
||||
public String referenceAddr;
|
||||
}
|
||||
|
||||
public static class ShortTemplateBookingResult {
|
||||
public final String balance;
|
||||
public final List<ShortTemplateBooking> bookings;
|
||||
|
||||
public ShortTemplateBookingResult(String balance, List<ShortTemplateBooking> bookings) {
|
||||
this.balance = balance;
|
||||
this.bookings = bookings;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to check which side of the booking (source or destination) is the account we are currently looking at.
|
||||
*/
|
||||
public interface AccountFilter {
|
||||
boolean matches(BookingAccountEntry account);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders template information for the bookings of one specific account.
|
||||
*
|
||||
* @param bookings The (already fetched) booking entities to display
|
||||
* @param currentAccount A filter matching the account that is to be displayed
|
||||
* @return A collection result containing the final balance and all booking entries.
|
||||
*/
|
||||
public ShortTemplateBookingResult buildShortTemplate(List<Booking> bookings, AccountFilter currentAccount) {
|
||||
List<ShortTemplateBooking> templateBookings = new ArrayList<>();
|
||||
for (Booking booking : bookings) {
|
||||
ShortTemplateBooking tb = new ShortTemplateBooking();
|
||||
tb.datetime = new SimpleDateFormat("dd.MM.yyyy HH:mm").format(booking.created);
|
||||
tb.reason = booking.reason.comment;
|
||||
tb.reference = describeReference(booking.reason);
|
||||
tb.referenceAddr = linkToReference(booking.reason);
|
||||
if (booking.destination != null && currentAccount.matches(booking.destination)) {
|
||||
// Money was transferred onto the account we are looking at.
|
||||
tb.amount = fmtEuro(booking.amountCent);
|
||||
if (booking.source != null) {
|
||||
tb.source = describeAccount(booking.source);
|
||||
tb.sourceAddr = linkAddrOfAccount(booking.source);
|
||||
} else {
|
||||
tb.source = "-";
|
||||
tb.sourceAddr = null;
|
||||
}
|
||||
tb.balance = fmtEuro(booking.destination.newSumCent);
|
||||
} else if (booking.source != null && currentAccount.matches(booking.source)) {
|
||||
// Money was transferred away from the account we are looking at.
|
||||
tb.amount = fmtEuro(-booking.amountCent); // Negative because of the transfer away
|
||||
if (booking.destination != null) {
|
||||
tb.source = describeAccount(booking.destination); // 'source' means 'the other account' in this case
|
||||
tb.sourceAddr = linkAddrOfAccount(booking.destination);
|
||||
} else {
|
||||
tb.source = "-";
|
||||
tb.sourceAddr = null;
|
||||
}
|
||||
tb.balance = fmtEuro(booking.source.newSumCent);
|
||||
} else {
|
||||
throw new RuntimeException(
|
||||
"This booking should not appear in this list, because neither source nor destination is the account we are looking at.");
|
||||
}
|
||||
templateBookings.add(tb);
|
||||
}
|
||||
|
||||
String balance = templateBookings.isEmpty() ? fmtEuro(0) : templateBookings.get(0).balance;
|
||||
return new ShortTemplateBookingResult(balance, templateBookings);
|
||||
}
|
||||
|
||||
public static String fmtEuro(long amountCent) {
|
||||
return String.format("%.2f EUR", amountCent / 100.0);
|
||||
}
|
||||
|
||||
private String describeAccount(BookingAccountEntry account) {
|
||||
if (account.isMainAccount) {
|
||||
return "Hauptkonto";
|
||||
} else if (account.isVATAccount) {
|
||||
return "Mehrwertsteuer";
|
||||
} else if (account.supplierAccount != null) {
|
||||
return "Lieferant " + account.supplierAccount.id;
|
||||
} else if (account.userAccount != null) {
|
||||
return "Kunde " + account.userAccount.id;
|
||||
} else {
|
||||
return "- unbekannt -";
|
||||
}
|
||||
}
|
||||
|
||||
private String linkAddrOfAccount(BookingAccountEntry account) {
|
||||
if (account.isMainAccount) {
|
||||
return "/intern/accounting/main";
|
||||
} else if (account.isVATAccount) {
|
||||
return "/intern/accounting/vat";
|
||||
} else if (account.supplierAccount != null) {
|
||||
return "/intern/suppliers/" + account.supplierAccount.id;
|
||||
} else if (account.userAccount != null) {
|
||||
return "/intern/customers/" + account.userAccount.id;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private String describeReference(BookingReason reason) {
|
||||
if (reason.customerOrder != null) {
|
||||
return reason.customerOrder.id + "";
|
||||
} else if (reason.customerPayment != null) {
|
||||
return "Bezahlung mit Kreditkarte " + reason.customerPayment.payment.creditCardNumber;
|
||||
} else if (reason.supplierOrder != null) {
|
||||
return "Lieferanten-Bestellung " + reason.supplierOrder.id;
|
||||
} else {
|
||||
return "-";
|
||||
}
|
||||
}
|
||||
|
||||
private String linkToReference(BookingReason reason) {
|
||||
if (reason.customerOrder != null) {
|
||||
return "/intern/customerOrders/" + reason.customerOrder.id;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("/intern/accounting/")
|
||||
public String accounting(Model model) {
|
||||
List<Booking> bookings = bookingRepository.allBookingsReverseChronologically();
|
||||
List<TemplateBooking> templateBookings = new ArrayList<>();
|
||||
for (Booking booking : bookings) {
|
||||
TemplateBooking tb = new TemplateBooking();
|
||||
tb.datetime = new SimpleDateFormat("dd.MM.yyyy HH:mm").format(booking.created);
|
||||
tb.amount = fmtEuro(booking.amountCent);
|
||||
if (booking.source != null) {
|
||||
tb.source = describeAccount(booking.source);
|
||||
tb.sourceAddr = linkAddrOfAccount(booking.source);
|
||||
tb.sourceBalance = fmtEuro(booking.source.newSumCent);
|
||||
} else {
|
||||
tb.source = "-";
|
||||
tb.sourceAddr = null;
|
||||
tb.sourceBalance = "-";
|
||||
}
|
||||
if (booking.destination != null) {
|
||||
tb.destination = describeAccount(booking.destination);
|
||||
tb.destinationAddr = linkAddrOfAccount(booking.destination);
|
||||
tb.destinationBalance = fmtEuro(booking.destination.newSumCent);
|
||||
} else {
|
||||
tb.destination = "-";
|
||||
tb.destinationAddr = null;
|
||||
tb.destinationBalance = "-";
|
||||
}
|
||||
tb.reason = booking.reason.comment;
|
||||
tb.reference = describeReference(booking.reason);
|
||||
tb.referenceAddr = linkToReference(booking.reason);
|
||||
templateBookings.add(tb);
|
||||
}
|
||||
|
||||
model.addAttribute("bookings", templateBookings);
|
||||
return "intern/accounting/index";
|
||||
}
|
||||
|
||||
@GetMapping("/intern/accounting/vat")
|
||||
public String accountingVat(Model model) {
|
||||
List<Booking> bookings = bookingRepository.vatBookingsReverseChronologically();
|
||||
ShortTemplateBookingResult result = buildShortTemplate(bookings, account -> account.isVATAccount);
|
||||
model.addAttribute("balance", result.balance);
|
||||
model.addAttribute("bookings", result.bookings);
|
||||
return "intern/accounting/vat";
|
||||
}
|
||||
|
||||
@GetMapping("/intern/accounting/main")
|
||||
public String accountingIntern(Model model) {
|
||||
List<Booking> bookings = bookingRepository.mainBookingsReverseChronologically();
|
||||
ShortTemplateBookingResult result = buildShortTemplate(bookings, account -> account.isMainAccount);
|
||||
model.addAttribute("balance", result.balance);
|
||||
model.addAttribute("bookings", result.bookings);
|
||||
return "intern/accounting/main";
|
||||
}
|
||||
}
|
@ -0,0 +1,270 @@
|
||||
package org.hso.ecommerce.controller.intern.accounting;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.hso.ecommerce.action.booking.CreateBookingAction;
|
||||
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.repos.booking.BookingAccountEntryRepository;
|
||||
import org.hso.ecommerce.repos.booking.BookingRepository;
|
||||
import org.hso.ecommerce.repos.supplier.SupplierRepository;
|
||||
import org.hso.ecommerce.repos.user.UserRepository;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
|
||||
@Controller
|
||||
public class ManualAccountingController {
|
||||
|
||||
@Autowired
|
||||
final BookingRepository bookingRepository = null;
|
||||
|
||||
@Autowired
|
||||
final BookingAccountEntryRepository bookingAccountEntryRepository = null;
|
||||
|
||||
@Autowired
|
||||
final UserRepository userRepository = null;
|
||||
|
||||
@Autowired
|
||||
final SupplierRepository supplierRepository = null;
|
||||
|
||||
/**
|
||||
* Collection for the form values used to create a manual booking
|
||||
*/
|
||||
public static class ManualAccounting {
|
||||
String amount;
|
||||
String source;
|
||||
String sourceCustomer;
|
||||
String sourceSupplier;
|
||||
String destination;
|
||||
String destinationCustomer;
|
||||
String destinationSupplier;
|
||||
String reason;
|
||||
String reasonText;
|
||||
|
||||
/**
|
||||
* Default constructor with default values for the form
|
||||
*/
|
||||
public ManualAccounting() {
|
||||
amount = "0.00";
|
||||
reason = "Manual";
|
||||
}
|
||||
|
||||
public String getAmount() {
|
||||
return amount;
|
||||
}
|
||||
|
||||
public void setAmount(String amount) {
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
public String getSource() {
|
||||
return source;
|
||||
}
|
||||
|
||||
public void setSource(String source) {
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
public String getSourceCustomer() {
|
||||
return sourceCustomer;
|
||||
}
|
||||
|
||||
public void setSourceCustomer(String sourceCustomer) {
|
||||
this.sourceCustomer = sourceCustomer;
|
||||
}
|
||||
|
||||
public String getSourceSupplier() {
|
||||
return sourceSupplier;
|
||||
}
|
||||
|
||||
public void setSourceSupplier(String sourceSupplier) {
|
||||
this.sourceSupplier = sourceSupplier;
|
||||
}
|
||||
|
||||
public String getDestination() {
|
||||
return destination;
|
||||
}
|
||||
|
||||
public void setDestination(String destination) {
|
||||
this.destination = destination;
|
||||
}
|
||||
|
||||
public String getDestinationCustomer() {
|
||||
return destinationCustomer;
|
||||
}
|
||||
|
||||
public void setDestinationCustomer(String destinationCustomer) {
|
||||
this.destinationCustomer = destinationCustomer;
|
||||
}
|
||||
|
||||
public String getDestinationSupplier() {
|
||||
return destinationSupplier;
|
||||
}
|
||||
|
||||
public void setDestinationSupplier(String destinationSupplier) {
|
||||
this.destinationSupplier = destinationSupplier;
|
||||
}
|
||||
|
||||
public String getReason() {
|
||||
return reason;
|
||||
}
|
||||
|
||||
public void setReason(String reason) {
|
||||
this.reason = reason;
|
||||
}
|
||||
|
||||
public String getReasonText() {
|
||||
return reasonText;
|
||||
}
|
||||
|
||||
public void setReasonText(String reasonText) {
|
||||
this.reasonText = reasonText;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An exception to represent errors that can be shown to the user
|
||||
*/
|
||||
public static class InvalidFormDataException extends Exception {
|
||||
public InvalidFormDataException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
}
|
||||
|
||||
private boolean sameAccountBothSides(ManualAccounting formData) {
|
||||
// No need to check for NumberFormatException because the numbers were already parsed before.
|
||||
if (!formData.getSource().equals(formData.getDestination())) {
|
||||
return false;
|
||||
} else if (formData.getSource().equals("Cust")
|
||||
&& Long.parseLong(formData.getSourceCustomer()) != Long.parseLong(formData.getDestinationCustomer())) {
|
||||
return false;
|
||||
} else if (formData.getSource().equals("Sup")
|
||||
&& Long.parseLong(formData.getSourceSupplier()) != Long.parseLong(formData.getDestinationSupplier())) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public Booking createBooking(ManualAccounting formData) throws InvalidFormDataException {
|
||||
if (formData.getSource() == null) {
|
||||
throw new InvalidFormDataException("Bitte wählen Sie ein Quellkonto aus.");
|
||||
} else if (formData.getDestination() == null) {
|
||||
throw new InvalidFormDataException("Bitte wählen Sie ein Zielkonto aus.");
|
||||
}
|
||||
BookingAccountEntry source = getAccountFromFormData(formData.getSource(), formData.getSourceCustomer(),
|
||||
formData.getSourceSupplier());
|
||||
BookingAccountEntry destination = getAccountFromFormData(formData.getDestination(),
|
||||
formData.getDestinationCustomer(), formData.getDestinationSupplier());
|
||||
|
||||
if (sameAccountBothSides(formData)) {
|
||||
throw new InvalidFormDataException("Quell- und Zielkonto dürfen nicht das selbe sein.");
|
||||
}
|
||||
|
||||
double doubleAmount;
|
||||
try {
|
||||
doubleAmount = Double.parseDouble(formData.amount);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new InvalidFormDataException("Der angegebene Betrag ist ungültig.");
|
||||
}
|
||||
int amountCent = (int) Math.round(doubleAmount * 100);
|
||||
|
||||
BookingReason reason = new BookingReason();
|
||||
if (formData.getReason().equals("Start")) {
|
||||
reason.isStartBooking = true;
|
||||
} else if (formData.getReason().equals("Manual")) {
|
||||
reason.isManuel = true;
|
||||
} else {
|
||||
throw new RuntimeException("Invalid form value for the booking reason: " + formData.getReason());
|
||||
}
|
||||
reason.comment = formData.reasonText;
|
||||
|
||||
CreateBookingAction action = new CreateBookingAction(source, destination, reason, amountCent);
|
||||
return action.finish();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the corresponding account based on user-input
|
||||
*
|
||||
* @param account The chosen value on the radio buttons
|
||||
* @param customer The given customer id from the corresponding text field
|
||||
* @param supplier the given supplier id from the corresponding text field
|
||||
* @return The account entry that can be used to create the new booking
|
||||
* @throws InvalidFormDataException If the user provided incorrect data in any way
|
||||
*/
|
||||
private BookingAccountEntry getAccountFromFormData(String account, String customer, String supplier)
|
||||
throws InvalidFormDataException {
|
||||
if (account.equals("None")) {
|
||||
return null;
|
||||
} else if (account.equals("Main")) {
|
||||
return bookingAccountEntryRepository.getByMain().orElseGet(BookingAccountEntry::newMain);
|
||||
} else if (account.equals("Vat")) {
|
||||
return bookingAccountEntryRepository.getByVat().orElseGet(BookingAccountEntry::newVat);
|
||||
} else if (account.equals("Cust")) {
|
||||
long userId;
|
||||
try {
|
||||
userId = Long.parseLong(customer);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new InvalidFormDataException("Die angegebene Kunden-Nr. ist ungültig.");
|
||||
}
|
||||
Optional<BookingAccountEntry> bookingAccount = bookingAccountEntryRepository.getByUser(userId);
|
||||
if (!bookingAccount.isPresent()) {
|
||||
bookingAccount = userRepository.findById(userId).map((user) -> BookingAccountEntry.newUser(user));
|
||||
}
|
||||
if (bookingAccount.isPresent()) {
|
||||
return bookingAccount.get();
|
||||
} else {
|
||||
throw new InvalidFormDataException("Der Kunde Nr. " + userId + " konnte nicht gefunden werden.");
|
||||
}
|
||||
} else if (account.equals("Sup")) {
|
||||
long supplierId;
|
||||
try {
|
||||
supplierId = Long.parseLong(supplier);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new InvalidFormDataException("Die angegebene Lieferanten-Nr. ist ungültig.");
|
||||
}
|
||||
Optional<BookingAccountEntry> bookingAccount = bookingAccountEntryRepository.getBySupplier(supplierId);
|
||||
if (!bookingAccount.isPresent()) {
|
||||
bookingAccount = supplierRepository.findById(supplierId)
|
||||
.map((sup) -> BookingAccountEntry.newSupplier(sup));
|
||||
}
|
||||
if (bookingAccount.isPresent()) {
|
||||
return bookingAccount.get();
|
||||
} else {
|
||||
throw new InvalidFormDataException(
|
||||
"Der Lieferant Nr. " + supplierId + " konnte nicht gefunden werden.");
|
||||
}
|
||||
} else {
|
||||
throw new RuntimeException("Invalid form value for an account: " + account);
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("/intern/accounting/addManual")
|
||||
public String accountingAddManual(Model model) {
|
||||
model.addAttribute("form_vals", new ManualAccounting());
|
||||
return "intern/accounting/addManual";
|
||||
}
|
||||
|
||||
@PostMapping("/intern/accounting/addManual")
|
||||
public String accountingAddManualSubmit(Model model, @ModelAttribute ManualAccounting formData) {
|
||||
Booking booking;
|
||||
try {
|
||||
booking = createBooking(formData);
|
||||
bookingRepository.save(booking);
|
||||
model.addAttribute("info", "Die Buchung wurde erfolgreich erstellt.");
|
||||
model.addAttribute("form_vals", new ManualAccounting()); // Reply with empty form on success
|
||||
} catch (InvalidFormDataException e) {
|
||||
model.addAttribute("error", e.getMessage());
|
||||
model.addAttribute("form_vals", formData);
|
||||
}
|
||||
return "intern/accounting/addManual";
|
||||
}
|
||||
|
||||
}
|
@ -1,8 +1,120 @@
|
||||
package org.hso.ecommerce.controller.intern.customers;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.hso.ecommerce.controller.intern.accounting.AccountingController;
|
||||
import org.hso.ecommerce.controller.intern.accounting.AccountingController.ShortTemplateBookingResult;
|
||||
import org.hso.ecommerce.entities.booking.Booking;
|
||||
import org.hso.ecommerce.entities.shop.CustomerOrder;
|
||||
import org.hso.ecommerce.entities.user.User;
|
||||
import org.hso.ecommerce.repos.booking.BookingRepository;
|
||||
import org.hso.ecommerce.repos.shop.CustomerOrderRepository;
|
||||
import org.hso.ecommerce.repos.user.UserRepository;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.Optional;
|
||||
|
||||
@Controller
|
||||
//@RequestMapping("...")
|
||||
@RequestMapping("/intern/customers")
|
||||
public class CustomersIndexController {
|
||||
|
||||
@Autowired
|
||||
private BookingRepository bookingRepository = null;
|
||||
|
||||
@Autowired
|
||||
private final CustomerOrderRepository customerOrderRepository = null;
|
||||
|
||||
@Autowired
|
||||
private final UserRepository userRepository = null;
|
||||
|
||||
@Autowired
|
||||
private AccountingController accountingController = null;
|
||||
|
||||
@GetMapping("")
|
||||
public String internCustomers(Model model) {
|
||||
List<User> users = userRepository.findAll();
|
||||
|
||||
model.addAttribute("users", users);
|
||||
|
||||
return "intern/customers/index";
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public String internCustomersId(Model model,
|
||||
@PathVariable("id") Long id,
|
||||
HttpServletResponse response,
|
||||
HttpServletRequest request
|
||||
) {
|
||||
Optional<User> optUser = userRepository.findById(id);
|
||||
if (!optUser.isPresent()) {
|
||||
request.setAttribute("error", "Der User wurde nicht gefunden.");
|
||||
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
|
||||
return "error/404";
|
||||
}
|
||||
User user = optUser.get();
|
||||
model.addAttribute("user", user);
|
||||
|
||||
List<CustomerOrder> orders = customerOrderRepository.getOrdersByUserId(id);
|
||||
model.addAttribute("orders", orders);
|
||||
|
||||
List<Booking> bookings = bookingRepository.customerBookingsReverseChronologically(id);
|
||||
ShortTemplateBookingResult result = accountingController.buildShortTemplate(
|
||||
bookings,
|
||||
account -> account.userAccount != null && account.userAccount.id == id);
|
||||
model.addAttribute("balance", result.balance);
|
||||
model.addAttribute("bookings", result.bookings);
|
||||
|
||||
return "intern/customers/id";
|
||||
}
|
||||
|
||||
@PostMapping("/{id}/changeState")
|
||||
public String changeState(@PathVariable("id") Long id,
|
||||
@RequestParam(value = "active", required = false) String active,
|
||||
@RequestParam(value = "ma", required = false) String ma
|
||||
) {
|
||||
User user = userRepository.findById(id).get();
|
||||
|
||||
if (active == null)
|
||||
user.isActive = false;
|
||||
else
|
||||
user.isActive = true;
|
||||
|
||||
if (ma == null)
|
||||
user.isEmployee = false;
|
||||
else
|
||||
user.isEmployee = true;
|
||||
|
||||
userRepository.save(user);
|
||||
|
||||
return "redirect:/intern/customers/" + id.toString();
|
||||
}
|
||||
|
||||
@PostMapping("/{id}/resetPassword")
|
||||
public String resetPassword(@PathVariable("id") Long id,
|
||||
@RequestParam("password") String password,
|
||||
@RequestParam("password2") String password2,
|
||||
HttpServletRequest request
|
||||
) {
|
||||
if (!password.equals(password2)) {
|
||||
request.setAttribute("error", "Passwörter stimmen nicht überein!");
|
||||
return "/intern/customers/id";
|
||||
}
|
||||
User user = userRepository.findById(id).get();
|
||||
if (!user.validatePassword(password)) {
|
||||
request.setAttribute("error", "Die Passwörter stimmen nicht mit dem Original überein!");
|
||||
return "/intern/customers/id";
|
||||
}
|
||||
user.setPassword("12345");
|
||||
userRepository.save(user);
|
||||
request.setAttribute("info", "Passwort wurde auf 12345 geändert!");
|
||||
|
||||
return "/intern/customers/id";
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,157 @@
|
||||
package org.hso.ecommerce.controller.intern.suppliers;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import org.hso.ecommerce.controller.intern.accounting.AccountingController;
|
||||
import org.hso.ecommerce.controller.intern.accounting.AccountingController.ShortTemplateBooking;
|
||||
import org.hso.ecommerce.controller.intern.accounting.AccountingController.ShortTemplateBookingResult;
|
||||
import org.hso.ecommerce.entities.booking.Booking;
|
||||
import org.hso.ecommerce.entities.supplier.Supplier;
|
||||
import org.hso.ecommerce.entities.supplier.SupplierOrder;
|
||||
import org.hso.ecommerce.repos.booking.BookingRepository;
|
||||
import org.hso.ecommerce.repos.supplier.SupplierOrderRepository;
|
||||
import org.hso.ecommerce.repos.supplier.SupplierRepository;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
@Controller
|
||||
//@RequestMapping("...")
|
||||
@RequestMapping("/intern/")
|
||||
public class SupplierIndexController {
|
||||
|
||||
@Autowired
|
||||
private final SupplierRepository supplierRepository = null;
|
||||
|
||||
@Autowired
|
||||
private final SupplierOrderRepository supplierOrderRepository = null;
|
||||
|
||||
@Autowired
|
||||
private final BookingRepository bookingRepository = null;
|
||||
|
||||
@Autowired
|
||||
private final AccountingController accountingController = null;
|
||||
|
||||
@GetMapping("suppliers")
|
||||
public String listSuppliers(Model model) {
|
||||
|
||||
List<UImodelSuppliers> totals = new ArrayList<UImodelSuppliers>();
|
||||
|
||||
for (Supplier supplier : supplierRepository.findAll()) {
|
||||
UImodelSuppliers tmp = new UImodelSuppliers(supplier.id, supplier.name);
|
||||
totals.add(tmp);
|
||||
}
|
||||
|
||||
model.addAttribute("suppliers", totals);
|
||||
return "intern/suppliers/index";
|
||||
}
|
||||
|
||||
@GetMapping("/suppliers/{id}")
|
||||
public String supplierDetail(Model model, @PathVariable String id) {
|
||||
|
||||
long supplierId = Long.parseLong(id);
|
||||
|
||||
// add orders from supplier to UImodel
|
||||
List<UImodelSupplierDetailOrders> orders = new ArrayList<UImodelSupplierDetailOrders>();
|
||||
for (SupplierOrder supplierOrder : supplierOrderRepository.findOrderBySupplierID(supplierId)) {
|
||||
orders.add(new UImodelSupplierDetailOrders(supplierOrder));
|
||||
}
|
||||
|
||||
// Table of bookings
|
||||
List<Booking> bookings = bookingRepository.supplierBookingsReverseChronologically(supplierId);
|
||||
ShortTemplateBookingResult bookingResult = accountingController.buildShortTemplate(bookings,
|
||||
account -> account.supplierAccount != null && account.supplierAccount.id == supplierId);
|
||||
|
||||
UImodelSupplierDetail total = new UImodelSupplierDetail(supplierRepository.findSupplierById(supplierId).name,
|
||||
bookingResult.balance, orders, bookingResult.bookings);
|
||||
|
||||
model.addAttribute("SupplierDetail", total);
|
||||
|
||||
return "intern/suppliers/id";
|
||||
}
|
||||
|
||||
public class UImodelSuppliers {
|
||||
public long id;
|
||||
public String name;
|
||||
|
||||
public UImodelSuppliers(long id, String name) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class UImodelSupplierDetail {
|
||||
|
||||
public String name;
|
||||
public String balance;
|
||||
public List<UImodelSupplierDetailOrders> orders;
|
||||
public List<ShortTemplateBooking> bookings;
|
||||
|
||||
public UImodelSupplierDetail(String name, String balance, List<UImodelSupplierDetailOrders> orders,
|
||||
List<ShortTemplateBooking> bookings
|
||||
) {
|
||||
this.name = name;
|
||||
this.balance = balance;
|
||||
this.orders = orders;
|
||||
this.bookings = bookings;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class UImodelSupplierDetailOrders {
|
||||
public long id;
|
||||
public String dateOrder;
|
||||
public String articleName;
|
||||
public long articleId;
|
||||
public String priceNet;
|
||||
public String quantity;
|
||||
public String priceTotal;
|
||||
public boolean arrived;
|
||||
|
||||
public UImodelSupplierDetailOrders(SupplierOrder order) {
|
||||
this.id = order.id;
|
||||
this.articleName = order.ordered.title;
|
||||
this.articleId = order.ordered.id;
|
||||
this.priceNet = String.format("%.2f", ((float) order.pricePerUnitNetCent / 100));
|
||||
this.quantity = String.valueOf(order.numberOfUnits);
|
||||
this.priceTotal = String.format("%.2f", ((float) order.totalPriceNet / 100));
|
||||
|
||||
Date date = new Date();
|
||||
date.setTime(order.created.getTime());
|
||||
this.dateOrder = new SimpleDateFormat("dd.MM.yyyy").format(date);
|
||||
|
||||
if (order.delivered != null) {
|
||||
arrived = true;
|
||||
} else {
|
||||
arrived = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class UImodelSupplierDetailBookings {
|
||||
|
||||
public String dateBooking;
|
||||
public String price;
|
||||
public String srcName;
|
||||
public String balance;
|
||||
public String reason;
|
||||
public long orderID;
|
||||
|
||||
public UImodelSupplierDetailBookings(Booking booking) {
|
||||
Date date = new Date();
|
||||
date.setTime(booking.reason.supplierOrder.created.getTime());
|
||||
this.dateBooking = new SimpleDateFormat("dd.MM.yyyy").format(date);
|
||||
this.price = String.format("%.2f", ((float) booking.amountCent / 100));
|
||||
this.srcName = ((booking.source.isMainAccount) ? "Hauptkonto" : booking.source.supplierAccount.name);
|
||||
this.balance = String.format("%.2f", ((float) booking.destination.newSumCent / 100));
|
||||
this.reason = booking.reason.comment;
|
||||
this.orderID = booking.reason.supplierOrder.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,77 @@
|
||||
package org.hso.ecommerce.controller.intern.suppliers;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.hso.ecommerce.entities.supplier.ArticleOffer;
|
||||
import org.hso.ecommerce.repos.shop.ArticleRepository;
|
||||
import org.hso.ecommerce.repos.shop.OffersRepository;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
@Controller
|
||||
//@RequestMapping("...")
|
||||
@RequestMapping("/intern/")
|
||||
public class SupplierOfferController {
|
||||
|
||||
@Autowired
|
||||
private final OffersRepository offersRepository = null;
|
||||
|
||||
@Autowired
|
||||
private final ArticleRepository articleRepository = null;
|
||||
|
||||
@GetMapping("supplierOffers")
|
||||
public String internListedArticles(Model model) {
|
||||
|
||||
List<UImodelOfferedArticle> totals = new ArrayList<>();
|
||||
|
||||
for (ArticleOffer article : offersRepository.findAll()) {
|
||||
UImodelOfferedArticle tmp = new UImodelOfferedArticle(article,
|
||||
articleRepository.findArticleIDByRelatedID(article.id));
|
||||
totals.add(tmp);
|
||||
}
|
||||
|
||||
model.addAttribute("OfferedArticles", totals);
|
||||
return "intern/offeredArticles/index";
|
||||
}
|
||||
|
||||
public class UImodelOfferedArticle {
|
||||
|
||||
public long offerId;
|
||||
public String title;
|
||||
public String manufacturer;
|
||||
public String articleNumber;
|
||||
public String supplierName;
|
||||
public String price;
|
||||
public String ads;
|
||||
public int listedArticleId;
|
||||
public boolean offerIsListed; // true --> offered article is listed
|
||||
|
||||
public UImodelOfferedArticle(ArticleOffer article, Optional<Integer> listedArticleId) {
|
||||
|
||||
this.offerId = article.id;
|
||||
this.title = article.title;
|
||||
this.manufacturer = article.manufacturer;
|
||||
this.articleNumber = article.articleNumber;
|
||||
if (article.cheapestSupplier != null) {
|
||||
this.supplierName = article.cheapestSupplier.name;
|
||||
} else {
|
||||
this.supplierName = "-";
|
||||
}
|
||||
this.price = String.format("%.2f", ((float) article.pricePerUnitNet / 100));
|
||||
this.ads = (article.shouldBeAdvertised) ? "Ja" : "Nein";
|
||||
|
||||
if (listedArticleId.isPresent()) {
|
||||
// this offer is listed --> show link
|
||||
this.listedArticleId = listedArticleId.get();
|
||||
offerIsListed = true;
|
||||
} else {
|
||||
// this offer is not listed
|
||||
offerIsListed = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,135 @@
|
||||
package org.hso.ecommerce.controller.intern.suppliers;
|
||||
|
||||
import org.hso.ecommerce.action.warehouse.SupplierOrderArrivedAction;
|
||||
import org.hso.ecommerce.entities.shop.Article;
|
||||
import org.hso.ecommerce.entities.supplier.SupplierOrder;
|
||||
import org.hso.ecommerce.entities.warehouse.WarehouseBookingPositionSlotEntry;
|
||||
import org.hso.ecommerce.repos.shop.ArticleRepository;
|
||||
import org.hso.ecommerce.repos.supplier.SupplierOrderRepository;
|
||||
import org.hso.ecommerce.repos.warehouse.SlotRepository;
|
||||
import org.hso.ecommerce.repos.warehouse.WarehouseBookingPositionSlotEntryRepository;
|
||||
import org.hso.ecommerce.repos.warehouse.WarehouseBookingRepository;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Controller
|
||||
//@RequestMapping("...")
|
||||
@RequestMapping("/intern/")
|
||||
public class SupplierOrderController {
|
||||
|
||||
@Autowired
|
||||
private final SupplierOrderRepository supplierOrderRepository = null;
|
||||
|
||||
@Autowired
|
||||
private final ArticleRepository articleRepository = null;
|
||||
|
||||
@Autowired
|
||||
private final WarehouseBookingPositionSlotEntryRepository warehouseBookingPositionSlotEntryRepository = null;
|
||||
|
||||
@Autowired
|
||||
private final WarehouseBookingRepository warehouseBookingRepository = null;
|
||||
|
||||
@Autowired
|
||||
private final SlotRepository slotRepository = null;
|
||||
|
||||
@GetMapping("supplierOrders")
|
||||
public String listSuppliers(Model model) {
|
||||
|
||||
List<UImodelSupplierOrder> totals = new ArrayList<UImodelSupplierOrder>();
|
||||
|
||||
for (SupplierOrder order : supplierOrderRepository.findAll()) {
|
||||
final Article article = articleRepository.findArticleByArticleOffer(order.ordered).orElse(null);
|
||||
totals.add(new UImodelSupplierOrder(order, article));
|
||||
}
|
||||
|
||||
model.addAttribute("orders", totals);
|
||||
|
||||
return "intern/supplierOrders/index";
|
||||
}
|
||||
|
||||
@PostMapping("/supplierOrders/store/{id}")
|
||||
public String storeOrder(@PathVariable("id") Long supplierOrderID, Model model, HttpServletResponse response) {
|
||||
SupplierOrder order = supplierOrderRepository.findById(supplierOrderID).orElse(null);
|
||||
if (order == null) {
|
||||
model.addAttribute("error", "Die ausgewählte Lieferung konnte nicht gefunden werden.");
|
||||
response.setStatus(HttpServletResponse.SC_EXPECTATION_FAILED);
|
||||
return listSuppliers(model);
|
||||
}
|
||||
if (order.wasDelivered()) {
|
||||
model.addAttribute("error", "Die ausgewählte Lieferung wurde schon zugestellt.");
|
||||
response.setStatus(HttpServletResponse.SC_EXPECTATION_FAILED);
|
||||
return listSuppliers(model);
|
||||
}
|
||||
|
||||
|
||||
final Article article = articleRepository.findArticleByArticleOffer(order.ordered).orElse(null);
|
||||
if (article == null) {
|
||||
model.addAttribute("error", "Der bestellte Artikel wurde nicht angelegt, er hätte nicht bestellt werden dürfen.");
|
||||
response.setStatus(HttpServletResponse.SC_EXPECTATION_FAILED);
|
||||
return listSuppliers(model);
|
||||
}
|
||||
|
||||
// Hard to do efficiently, this should be fine.
|
||||
List<WarehouseBookingPositionSlotEntry> candidates = slotRepository
|
||||
.findAll()
|
||||
.stream()
|
||||
.map(slot ->
|
||||
warehouseBookingPositionSlotEntryRepository.getBySlotNum(slot.slotNum).orElseGet(() ->
|
||||
WarehouseBookingPositionSlotEntry.empty(article, slot)
|
||||
)
|
||||
)
|
||||
.filter(entry -> entry.article.id == article.id || entry.newSumSlot == 0)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
SupplierOrderArrivedAction action = new SupplierOrderArrivedAction(candidates, order, article);
|
||||
|
||||
try {
|
||||
SupplierOrderArrivedAction.Result result = action.finish();
|
||||
supplierOrderRepository.save(result.getOrder());
|
||||
warehouseBookingRepository.save(result.getBooking());
|
||||
} catch (SupplierOrderArrivedAction.NoSpaceInWarehouseException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return "redirect:/intern/warehouse/todo";
|
||||
}
|
||||
|
||||
public class UImodelSupplierOrder {
|
||||
public long id;
|
||||
public String dateOrder;
|
||||
public String supplierName;
|
||||
public String articleName;
|
||||
public long articleId;
|
||||
public String priceNet;
|
||||
public String quantity;
|
||||
public String priceTotal;
|
||||
public boolean arrived;
|
||||
|
||||
public UImodelSupplierOrder(SupplierOrder order, Article article) {
|
||||
this.id = order.id;
|
||||
this.supplierName = order.supplier.name;
|
||||
this.articleName = article != null ? article.title : "error";
|
||||
this.articleId = article != null ? article.id : 0;
|
||||
this.priceNet = String.format("%.2f", ((float) order.pricePerUnitNetCent / 100));
|
||||
this.quantity = String.valueOf(order.numberOfUnits);
|
||||
this.priceTotal = String.format("%.2f", ((float) order.totalPriceNet / 100));
|
||||
|
||||
Date date = new Date();
|
||||
date.setTime(order.created.getTime());
|
||||
this.dateOrder = new SimpleDateFormat("dd.MM.yyyy").format(date);
|
||||
|
||||
arrived = order.delivered != null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,140 @@
|
||||
package org.hso.ecommerce.controller.intern.warehouse;
|
||||
|
||||
import org.hso.ecommerce.action.warehouse.CreateManuelBookingAction;
|
||||
import org.hso.ecommerce.entities.shop.Article;
|
||||
import org.hso.ecommerce.entities.warehouse.Slot;
|
||||
import org.hso.ecommerce.entities.warehouse.WarehouseBookingPositionSlotEntry;
|
||||
import org.hso.ecommerce.repos.shop.ArticleRepository;
|
||||
import org.hso.ecommerce.repos.warehouse.SlotRepository;
|
||||
import org.hso.ecommerce.repos.warehouse.WarehouseBookingPositionSlotEntryRepository;
|
||||
import org.hso.ecommerce.repos.warehouse.WarehouseBookingRepository;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.Optional;
|
||||
|
||||
@Controller
|
||||
@RequestMapping("/intern/warehouse/")
|
||||
public class ManuelBookingController {
|
||||
|
||||
@Autowired
|
||||
private final ArticleRepository articleRepository = null;
|
||||
|
||||
@Autowired
|
||||
private final SlotRepository slotRepository = null;
|
||||
|
||||
@Autowired
|
||||
private final WarehouseBookingPositionSlotEntryRepository warehouseBookingPositionSlotEntryRepository = null;
|
||||
|
||||
@Autowired
|
||||
private final WarehouseBookingRepository warehouseBookingRepository = null;
|
||||
|
||||
@GetMapping("addManual")
|
||||
public String warehouseAddManual(
|
||||
Model model
|
||||
) {
|
||||
|
||||
model.addAttribute("articles", articleRepository.findAll());
|
||||
model.addAttribute("slots", slotRepository.findAll());
|
||||
|
||||
return "intern/warehouse/addManual";
|
||||
}
|
||||
|
||||
@PostMapping("addManual")
|
||||
public String warehouseAddMaualPost(
|
||||
Model model,
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
@RequestParam("articleId") String articleIdText,
|
||||
@RequestParam("amount") Integer amount,
|
||||
@RequestParam("reason") String reason,
|
||||
@RequestParam("sourceIsSlot") Boolean sourceIsSlot,
|
||||
@RequestParam("sourceSlot") Integer sourceSlotNum,
|
||||
@RequestParam("destinationIsSlot") Boolean destinationIsSlot,
|
||||
@RequestParam("destinationSlot") Integer destinationSlotNum
|
||||
) {
|
||||
|
||||
// The suggestions for articleId in the UI show articles names, seperated by a " - ".
|
||||
// The Number must be extracted first.
|
||||
long articleId = -1;
|
||||
try {
|
||||
articleId = Long.parseLong(articleIdText.split(" - ", 2)[0].trim());
|
||||
} catch (NumberFormatException e) {
|
||||
model.addAttribute("error", "Die Artikel Id konnte nicht erkannt werden.");
|
||||
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
|
||||
return "intern/warehouse/addManual";
|
||||
}
|
||||
|
||||
|
||||
Optional<Article> optionalArticle = articleRepository.findById(articleId);
|
||||
Article article = null;
|
||||
if (!optionalArticle.isPresent()) {
|
||||
model.addAttribute("error", "Der Artikel konnte nicht gefunden werden.");
|
||||
response.setStatus(HttpServletResponse.SC_EXPECTATION_FAILED);
|
||||
return "intern/warehouse/addManual";
|
||||
} else {
|
||||
article = optionalArticle.get();
|
||||
}
|
||||
|
||||
if (amount <= 0) {
|
||||
model.addAttribute("error", "Eine Anzahl <= 0 kann nicht verbucht werden.");
|
||||
response.setStatus(HttpServletResponse.SC_EXPECTATION_FAILED);
|
||||
return "intern/warehouse/addManual";
|
||||
}
|
||||
|
||||
if (sourceIsSlot == false && destinationIsSlot == false) {
|
||||
model.addAttribute("error", "Jede Buchung benötigt ein Ziel oder eine Quelle.");
|
||||
response.setStatus(HttpServletResponse.SC_EXPECTATION_FAILED);
|
||||
return "intern/warehouse/addManual";
|
||||
}
|
||||
|
||||
Optional<WarehouseBookingPositionSlotEntry> sourceSlot = Optional.empty();
|
||||
if (sourceIsSlot == true) {
|
||||
sourceSlot = warehouseBookingPositionSlotEntryRepository.getBySlotNum(sourceSlotNum);
|
||||
if (!sourceSlot.isPresent()) {
|
||||
request.setAttribute("error", "Quelllagerplatz wurde nicht gefunden oder ist leer.");
|
||||
response.setStatus(HttpServletResponse.SC_EXPECTATION_FAILED);
|
||||
return "intern/warehouse/addManual";
|
||||
}
|
||||
}
|
||||
|
||||
Optional<WarehouseBookingPositionSlotEntry> destinationSlot = Optional.empty();
|
||||
if (destinationIsSlot == true) {
|
||||
Optional<Slot> slot = slotRepository.findBySlotNum(destinationSlotNum);
|
||||
if (!slot.isPresent()) {
|
||||
request.setAttribute("error", "Ziellagerplatz wurde nicht gefunden.");
|
||||
response.setStatus(HttpServletResponse.SC_EXPECTATION_FAILED);
|
||||
return "intern/warehouse/addManual";
|
||||
}
|
||||
|
||||
Article finalArticle = article;
|
||||
destinationSlot = Optional.of(warehouseBookingPositionSlotEntryRepository.getBySlotNum(destinationSlotNum).orElseGet(
|
||||
() -> WarehouseBookingPositionSlotEntry.empty(finalArticle, slot.get())
|
||||
));
|
||||
}
|
||||
|
||||
try {
|
||||
warehouseBookingRepository.save(
|
||||
new CreateManuelBookingAction(article, amount, sourceSlot, destinationSlot, reason).finish()
|
||||
);
|
||||
} catch (CreateManuelBookingAction.ArticleSlotConstraintArticleTypeFailedException e) {
|
||||
model.addAttribute("error", "Es befindet sich der falsche Artikeltyp in Quell- oder Ziellagerplatz. ");
|
||||
response.setStatus(HttpServletResponse.SC_EXPECTATION_FAILED);
|
||||
return "intern/warehouse/addManual";
|
||||
} catch (CreateManuelBookingAction.ArticleSlotConstraintFailedException e) {
|
||||
model.addAttribute("error", "Die maximale Anzahl an lagerbaren Artikeln im Ziellagerplatz wurde überschritten.");
|
||||
response.setStatus(HttpServletResponse.SC_EXPECTATION_FAILED);
|
||||
return "intern/warehouse/addManual";
|
||||
}
|
||||
|
||||
return "redirect:/intern/warehouse/todo";
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
package org.hso.ecommerce.controller.intern.warehouse;
|
||||
|
||||
import org.hso.ecommerce.action.warehouse.CalculateWarehouseStatsAction;
|
||||
import org.hso.ecommerce.entities.warehouse.WarehouseBookingPositionSlotEntry;
|
||||
import org.hso.ecommerce.repos.warehouse.SlotRepository;
|
||||
import org.hso.ecommerce.repos.warehouse.WarehouseBookingPositionSlotEntryRepository;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Controller
|
||||
@RequestMapping("/intern/warehouse/")
|
||||
public class SlotsController {
|
||||
|
||||
@Autowired
|
||||
private final WarehouseBookingPositionSlotEntryRepository warehouseBookingPositionSlotEntryRepository = null;
|
||||
|
||||
@Autowired
|
||||
private final SlotRepository slotRepository = null;
|
||||
|
||||
@GetMapping("slots/")
|
||||
public String accountingWarehouseSlots(
|
||||
Model model,
|
||||
HttpServletRequest request
|
||||
) {
|
||||
|
||||
// Doing this in a single would be hard and error prone.
|
||||
// Writing native queries should be minimized.
|
||||
// Therefore this method was prefered
|
||||
List<WarehouseBookingPositionSlotEntry> entries = slotRepository.findAll().stream().map(
|
||||
s -> warehouseBookingPositionSlotEntryRepository
|
||||
.getBySlotNum(s.slotNum)
|
||||
.orElseGet(() -> WarehouseBookingPositionSlotEntry.empty(null, s))
|
||||
).collect(Collectors.toList());
|
||||
model.addAttribute("entries", entries);
|
||||
|
||||
model.addAttribute("stats", new CalculateWarehouseStatsAction(entries).finish());
|
||||
return "intern/warehouse/slots/index";
|
||||
}
|
||||
}
|
@ -0,0 +1,112 @@
|
||||
package org.hso.ecommerce.controller.intern.warehouse;
|
||||
|
||||
import org.hso.ecommerce.action.shop.EnableTrackingAction;
|
||||
import org.hso.ecommerce.entities.warehouse.WarehouseBooking;
|
||||
import org.hso.ecommerce.repos.warehouse.WarehouseBookingRepository;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.Optional;
|
||||
|
||||
@Controller
|
||||
@RequestMapping("/intern/warehouse/")
|
||||
public class TodoController {
|
||||
|
||||
@Autowired
|
||||
private final WarehouseBookingRepository warehouseBookingRepository = null;
|
||||
|
||||
@GetMapping("todo")
|
||||
public String accountingWarehouseTodo(
|
||||
Model model
|
||||
) {
|
||||
model.addAttribute("bookings", warehouseBookingRepository.findNotDone());
|
||||
return "intern/warehouse/todo";
|
||||
}
|
||||
|
||||
|
||||
@PostMapping("progress/{id}")
|
||||
public String postProgressId(
|
||||
Model model,
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
@PathVariable("id") Long id
|
||||
) {
|
||||
Optional<WarehouseBooking> booking = warehouseBookingRepository.findById(id);
|
||||
if (!booking.isPresent()) {
|
||||
model.addAttribute("error", "Die Buchung wurde nicht gefunden.");
|
||||
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
|
||||
return "error/404";
|
||||
}
|
||||
|
||||
if (booking.get().isInProgress) {
|
||||
response.setStatus(409);
|
||||
return "intern/warehouse/error_progress_failed";
|
||||
}
|
||||
|
||||
booking.get().isInProgress = true;
|
||||
warehouseBookingRepository.save(booking.get());
|
||||
|
||||
return "redirect:/intern/warehouse/progress/" + id;
|
||||
}
|
||||
|
||||
@PostMapping("progress/{id}/finish")
|
||||
public String postProgressIdFinish(
|
||||
Model model,
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
@PathVariable("id") Long id
|
||||
) {
|
||||
Optional<WarehouseBooking> booking = warehouseBookingRepository.findById(id);
|
||||
if (!booking.isPresent()) {
|
||||
model.addAttribute("error", "Die Buchung wurde nicht gefunden.");
|
||||
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
|
||||
return "error/404";
|
||||
}
|
||||
|
||||
booking.get().isInProgress = true;
|
||||
booking.get().isDone = true;
|
||||
|
||||
// Update Delivery Date
|
||||
if (booking.get().reason.customerOrder != null) {
|
||||
EnableTrackingAction.addTrackingInfo(booking.get().reason.customerOrder);
|
||||
}
|
||||
|
||||
warehouseBookingRepository.save(booking.get());
|
||||
|
||||
return "redirect:/intern/warehouse/todo";
|
||||
}
|
||||
|
||||
@GetMapping("progress/{id}")
|
||||
public String getProgressId(Model model,
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
@PathVariable("id") Long id) {
|
||||
Optional<WarehouseBooking> booking = warehouseBookingRepository.findById(id);
|
||||
if (!booking.isPresent()) {
|
||||
model.addAttribute("error", "Die Buchung wurde nicht gefunden.");
|
||||
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
|
||||
return "error/404";
|
||||
}
|
||||
|
||||
if (booking.get().isDone) {
|
||||
model.addAttribute("info", "Die Buchung wurde schon abgeschlossen.");
|
||||
}
|
||||
|
||||
if (!booking.get().isInProgress) {
|
||||
// Only reachable if path is manipulated.
|
||||
model.addAttribute("error", "Die Buchung wurde noch nicht zugewiesen!");
|
||||
}
|
||||
|
||||
model.addAttribute("booking", booking.get());
|
||||
return "intern/warehouse/id_progress";
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -1,8 +1,118 @@
|
||||
package org.hso.ecommerce.controller.shop;
|
||||
|
||||
import org.apache.tomcat.util.http.fileupload.IOUtils;
|
||||
import org.hso.ecommerce.action.shop.GetRandomArticlesAction;
|
||||
import org.hso.ecommerce.entities.shop.Article;
|
||||
import org.hso.ecommerce.entities.shop.ShoppingCart;
|
||||
import org.hso.ecommerce.repos.shop.ArticleRepository;
|
||||
import org.hso.ecommerce.repos.shop.CategoryRepository;
|
||||
import org.hso.ecommerce.repos.warehouse.WarehouseBookingPositionSlotEntryRepository;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
|
||||
@Controller
|
||||
//@RequestMapping("...")
|
||||
@RequestMapping("/shop/articles")
|
||||
public class ShopArticleController {
|
||||
}
|
||||
|
||||
@Autowired
|
||||
private final ArticleRepository articleRepository = null;
|
||||
|
||||
@Autowired
|
||||
private final WarehouseBookingPositionSlotEntryRepository warehouseBookingPositionSlotEntryRepository = null;
|
||||
|
||||
@Autowired
|
||||
private final CategoryRepository categoryRepository = null;
|
||||
|
||||
@GetMapping("/{id}")
|
||||
public String shopArticlesById(Model model,
|
||||
@PathVariable("id") Long id,
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response
|
||||
) {
|
||||
model.addAttribute("categories", categoryRepository.getCategories()); //for sidebar
|
||||
|
||||
Article article = articleRepository.findArticleById(id);
|
||||
|
||||
if (article == null) {
|
||||
request.setAttribute("error", "Der Artikel wurde nicht gefunden.");
|
||||
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
|
||||
return "error/404";
|
||||
}
|
||||
model.addAttribute("article", article);
|
||||
|
||||
if (warehouseBookingPositionSlotEntryRepository.getByArticle(id).get(0).newSumSlot > 0) { //check if in Stock
|
||||
model.addAttribute("inStock", true);
|
||||
} else {
|
||||
model.addAttribute("inStock", false);
|
||||
}
|
||||
|
||||
List<Article> commercialArticles = GetRandomArticlesAction.getRandomArticles(3, articleRepository.getAdvertisedArticles()); //get 3 advertised Articles
|
||||
model.addAttribute("commercialArticles", commercialArticles);
|
||||
|
||||
return "shop/articles/id";
|
||||
}
|
||||
|
||||
@PostMapping("/{id}")
|
||||
public String shopArticlesByIdBuy(HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
HttpSession session,
|
||||
@RequestAttribute(value = "shoppingCart") ShoppingCart shoppingCart,
|
||||
@PathVariable("id") Long id,
|
||||
@RequestParam("quantity") Integer quantity,
|
||||
@RequestParam(value = "set_amount", required = false) Boolean setAmount,
|
||||
@RequestParam("fastcheckout") Boolean fastcheckout
|
||||
) {
|
||||
Article article = articleRepository.findArticleById(id);
|
||||
|
||||
if (article == null) {
|
||||
request.setAttribute("error", "Der Artikel wurde nicht gefunden.");
|
||||
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
|
||||
return "error/404";
|
||||
}
|
||||
|
||||
if (setAmount != null && setAmount) {
|
||||
shoppingCart.setArticleCount(article, quantity);
|
||||
} else {
|
||||
shoppingCart.addArticle(article, quantity);
|
||||
}
|
||||
|
||||
if (!fastcheckout) {
|
||||
return "shop/articles/post_add";
|
||||
} else {
|
||||
return "redirect:/shop/checkout";
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("/{id}/image.jpg")
|
||||
public void getImageAsByteArray(HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
@PathVariable("id") Long id
|
||||
) throws IOException {
|
||||
Article article = articleRepository.findArticleById(id);
|
||||
|
||||
if(article.image != null) {
|
||||
File file = new File(article.image.path);
|
||||
File allowedPath = new File("./data/img/");
|
||||
|
||||
if (file.getCanonicalPath().startsWith(allowedPath.getCanonicalPath())) {
|
||||
InputStream in = new FileInputStream(file);
|
||||
response.setContentType(MediaType.IMAGE_JPEG_VALUE);
|
||||
IOUtils.copy(in, response.getOutputStream());
|
||||
} else {
|
||||
throw new RuntimeException("Got illegal file path. DB was modified.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,8 +1,158 @@
|
||||
package org.hso.ecommerce.controller.shop;
|
||||
|
||||
import org.hso.ecommerce.action.shop.CreateOrderAction;
|
||||
import org.hso.ecommerce.entities.booking.BookingAccountEntry;
|
||||
import org.hso.ecommerce.entities.booking.PaymentMethod;
|
||||
import org.hso.ecommerce.entities.shop.Address;
|
||||
import org.hso.ecommerce.entities.shop.Article;
|
||||
import org.hso.ecommerce.entities.shop.ShoppingCart;
|
||||
import org.hso.ecommerce.entities.user.User;
|
||||
import org.hso.ecommerce.repos.booking.BookingAccountEntryRepository;
|
||||
import org.hso.ecommerce.repos.booking.BookingRepository;
|
||||
import org.hso.ecommerce.repos.shop.ArticleRepository;
|
||||
import org.hso.ecommerce.repos.shop.CustomerOrderRepository;
|
||||
import org.hso.ecommerce.repos.user.UserRepository;
|
||||
import org.hso.ecommerce.repos.warehouse.WarehouseBookingPositionSlotEntryRepository;
|
||||
import org.hso.ecommerce.repos.warehouse.WarehouseBookingRepository;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
import java.util.ArrayList;
|
||||
import java.util.TreeMap;
|
||||
|
||||
@Controller
|
||||
//@RequestMapping("...")
|
||||
@RequestMapping("/shop/")
|
||||
public class ShopCheckoutController {
|
||||
|
||||
@Autowired
|
||||
private final UserRepository userRepository = null;
|
||||
|
||||
@Autowired
|
||||
private final ArticleRepository articleRepository = null;
|
||||
|
||||
@Autowired
|
||||
private final BookingAccountEntryRepository bookingEntryRepository = null;
|
||||
|
||||
@Autowired
|
||||
private final BookingRepository bookingRepository = null;
|
||||
|
||||
@Autowired
|
||||
private final WarehouseBookingRepository warehouseBookingRepository = null;
|
||||
|
||||
@Autowired
|
||||
private final CustomerOrderRepository customerOderRepository = null;
|
||||
|
||||
@Autowired
|
||||
private final WarehouseBookingPositionSlotEntryRepository wbeseRepo = null;
|
||||
|
||||
@GetMapping("/checkout")
|
||||
public String shopCheckout(HttpSession session,
|
||||
HttpServletRequest request,
|
||||
@RequestAttribute(value = "shoppingCart") ShoppingCart shoppingCart) {
|
||||
session.setAttribute("afterLogin", request.getRequestURI());
|
||||
|
||||
CheckoutListTotals totals = new CheckoutListTotals();
|
||||
ArrayList<CheckoutListItem> items = new ArrayList<>();
|
||||
for (ShoppingCart.ShoppingCartItem item : shoppingCart.getItems()) {
|
||||
Article article = articleRepository.findById(item.getArticleId()).get();
|
||||
|
||||
totals.addItem(article, item.getAmount());
|
||||
items.add(new CheckoutListItem(item.getAmount(), article));
|
||||
}
|
||||
|
||||
request.setAttribute("checkoutItems", items);
|
||||
request.setAttribute("checkoutTotals", totals);
|
||||
|
||||
return "shop/checkout";
|
||||
}
|
||||
|
||||
public static class CheckoutListTotals {
|
||||
public int net;
|
||||
public TreeMap<Integer, Integer> vatAmounts = new TreeMap<>();
|
||||
public int total;
|
||||
|
||||
void addItem(Article article, int amount) {
|
||||
net += article.shopPricePerUnitNetCent * amount;
|
||||
Integer vatPos = vatAmounts.getOrDefault(article.related.vatPercent, 0) + article.getVat() * amount;
|
||||
vatAmounts.put(article.related.vatPercent, vatPos);
|
||||
total += article.getPriceGross() * amount;
|
||||
}
|
||||
}
|
||||
|
||||
public static class CheckoutListItem {
|
||||
public int amount;
|
||||
public Article article;
|
||||
public int total;
|
||||
|
||||
public CheckoutListItem(int amount, Article article) {
|
||||
this.amount = amount;
|
||||
this.article = article;
|
||||
this.total = amount * article.getPriceGross();
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping("/checkoutFinish")
|
||||
public String shopCheckoutFinish(
|
||||
HttpSession session,
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
@RequestAttribute(value = "user") User user,
|
||||
@RequestAttribute(value = "shoppingCart") ShoppingCart shoppingCart,
|
||||
@RequestParam("address") String address,
|
||||
@RequestParam("cardnumber") String cardnumber,
|
||||
@RequestParam("shopping_cart_revision") Integer cartRevision,
|
||||
@RequestParam("expected_total") Integer expectedPrice
|
||||
) {
|
||||
|
||||
if (shoppingCart.getRevision() != cartRevision) {
|
||||
request.setAttribute("error", "Der Warenkorb wurde zwischenzeitlich bearbeitet. Daher die Kaufvorgang nicht abgeschlossen werden. Bitte versuchen Sie es erneut.");
|
||||
response.setStatus(HttpServletResponse.SC_CONFLICT);
|
||||
return "shop/checkout";
|
||||
}
|
||||
|
||||
// Must be refetched for persitence.
|
||||
user = userRepository.findById(user.id).get();
|
||||
|
||||
CreateOrderAction action = new CreateOrderAction(
|
||||
user,
|
||||
expectedPrice,
|
||||
Address.fromString(address),
|
||||
PaymentMethod.fromCreditCardNumber(cardnumber),
|
||||
bookingEntryRepository.getByUser(user.id).orElse(BookingAccountEntry.newUser(user)),
|
||||
bookingEntryRepository.getByVat().orElse(BookingAccountEntry.newVat()),
|
||||
bookingEntryRepository.getByMain().orElse(BookingAccountEntry.newMain())
|
||||
);
|
||||
|
||||
for (ShoppingCart.ShoppingCartItem item : shoppingCart.getItems()) {
|
||||
Article article = articleRepository.findById(item.getArticleId()).get();
|
||||
action.addArticle(article, item.getAmount(), wbeseRepo.getByArticle(article.id));
|
||||
}
|
||||
|
||||
CreateOrderAction.Result result = null;
|
||||
try {
|
||||
result = action.finish();
|
||||
|
||||
customerOderRepository.save(result.customerOrder);
|
||||
bookingRepository.saveAll(result.bookings);
|
||||
warehouseBookingRepository.save(result.warehouseBooking);
|
||||
|
||||
shoppingCart.clear();
|
||||
|
||||
} catch (CreateOrderAction.ArticleNotInStockException e) {
|
||||
request.setAttribute("error", "Der Artikel '" + e.getArticle().title + "' ist leider nicht mehr in ausreichender Menge verfügbar. Bitte passen Sie die Artikelmenge an.");
|
||||
return shopCheckout(session, request, shoppingCart);
|
||||
}
|
||||
|
||||
|
||||
return "shop/checkoutFinish";
|
||||
}
|
||||
|
||||
@GetMapping("/checkoutFinish")
|
||||
public String shopCheckoutFinishGET() {
|
||||
return "shop/checkoutFinish";
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,72 @@
|
||||
package org.hso.ecommerce.controller.shop;
|
||||
|
||||
import org.hso.ecommerce.action.shop.GetRandomArticlesAction;
|
||||
import org.hso.ecommerce.entities.shop.Article;
|
||||
import org.hso.ecommerce.repos.shop.ArticleRepository;
|
||||
import org.hso.ecommerce.repos.warehouse.WarehouseBookingPositionSlotEntryRepository;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
import javax.servlet.http.HttpSession;
|
||||
import java.util.List;
|
||||
|
||||
@Controller
|
||||
//@RequestMapping("...")
|
||||
@RequestMapping("/")
|
||||
public class ShopIndexController {
|
||||
|
||||
@Autowired
|
||||
private final ArticleRepository articleRepository = null;
|
||||
|
||||
@Autowired
|
||||
private final WarehouseBookingPositionSlotEntryRepository warehouseBookingPositionSlotEntryRepository = null;
|
||||
|
||||
@GetMapping("/")
|
||||
public String home() {
|
||||
return "redirect:/shop/";
|
||||
}
|
||||
|
||||
@GetMapping("/shop/")
|
||||
public String shop(Model model, HttpSession session) {
|
||||
|
||||
List<Article> commercialArticles = GetRandomArticlesAction.getRandomArticles(8, articleRepository.getAdvertisedArticles()); //get random advertised Articles
|
||||
model.addAttribute("commercialArticles", commercialArticles);
|
||||
|
||||
boolean isLoggedIn = false;
|
||||
boolean hasOrders = false;
|
||||
|
||||
if (session != null && session.getAttribute("userId") != null) { //check if logged in
|
||||
long userId = (long) session.getAttribute("userId");
|
||||
isLoggedIn = true;
|
||||
|
||||
List<Article> suggestedArticles = articleRepository.getOrderedArticles(userId);
|
||||
suggestedArticles = suggestedArticles.size() > 3 ? suggestedArticles.subList(0, 4) : suggestedArticles; //only latest 4 ordered articles
|
||||
if (suggestedArticles.size() > 0) {
|
||||
model.addAttribute("suggestedArticles", suggestedArticles);
|
||||
hasOrders = true;
|
||||
}
|
||||
}
|
||||
|
||||
model.addAttribute("isLoggedIn", isLoggedIn);
|
||||
model.addAttribute("hasOrders", hasOrders);
|
||||
|
||||
return "shop/index";
|
||||
}
|
||||
|
||||
@GetMapping("/about")
|
||||
public String about() {
|
||||
return "about";
|
||||
}
|
||||
|
||||
@GetMapping("/terms")
|
||||
public String terms() {
|
||||
return "terms";
|
||||
}
|
||||
|
||||
@GetMapping("/privacy")
|
||||
public String privacy() {
|
||||
return "privacy";
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,50 @@
|
||||
package org.hso.ecommerce.controller.shop;
|
||||
|
||||
import org.hso.ecommerce.action.shop.SearchByTermAction;
|
||||
import org.hso.ecommerce.entities.shop.Article;
|
||||
import org.hso.ecommerce.repos.shop.ArticleRepository;
|
||||
import org.hso.ecommerce.repos.shop.CategoryRepository;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.List;
|
||||
|
||||
@Controller
|
||||
//@RequestMapping("...")
|
||||
@RequestMapping("/shop/search")
|
||||
public class ShopSearchController {
|
||||
}
|
||||
|
||||
@Autowired
|
||||
private final ArticleRepository articleRepository = null;
|
||||
|
||||
@Autowired
|
||||
private final CategoryRepository categoryRepository = null;
|
||||
|
||||
@GetMapping("")
|
||||
public String searchArticles(@RequestParam(required = false, value = "term") String term,
|
||||
@RequestParam(required = false, value = "category") String category,
|
||||
Model model,
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response
|
||||
) {
|
||||
model.addAttribute("categories", categoryRepository.getCategories()); //for sidebar
|
||||
|
||||
if (term != null) { //if search by Term
|
||||
term = term.trim();
|
||||
List<Article> articles = SearchByTermAction.searchByTerm(term, articleRepository);
|
||||
model.addAttribute("articles", articles);
|
||||
} else if (category != null) { //if search by Category
|
||||
List<Article> articles = articleRepository.getArticlesByCategory(category); //search by Category
|
||||
model.addAttribute("articles", articles);
|
||||
} else {
|
||||
request.setAttribute("error", "Es wurden keine Suchparameter angegeben.");
|
||||
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
|
||||
return "error/404";
|
||||
}
|
||||
|
||||
return "/shop/search";
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
package org.hso.ecommerce.entities.booking;
|
||||
|
||||
import javax.persistence.*;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
@Entity
|
||||
@Table(name = "bookings")
|
||||
@ -14,12 +15,15 @@ public class Booking {
|
||||
// always >= 0
|
||||
public int amountCent;
|
||||
|
||||
@ManyToOne(optional = true)
|
||||
@NotNull
|
||||
public java.sql.Timestamp created;
|
||||
|
||||
@ManyToOne(optional = true, cascade = CascadeType.ALL)
|
||||
public BookingAccountEntry source;
|
||||
|
||||
@ManyToOne(optional = true)
|
||||
@ManyToOne(optional = true, cascade = CascadeType.ALL)
|
||||
public BookingAccountEntry destination;
|
||||
|
||||
@OneToOne(optional = false)
|
||||
@OneToOne(optional = false, cascade = CascadeType.ALL)
|
||||
public BookingReason reason;
|
||||
}
|
||||
|
@ -16,13 +16,57 @@ public class BookingAccountEntry {
|
||||
|
||||
public int newSumCent;
|
||||
|
||||
@ManyToOne(optional = true)
|
||||
@ManyToOne(optional = true, cascade = CascadeType.MERGE)
|
||||
public User userAccount;
|
||||
|
||||
@ManyToOne(optional = true)
|
||||
@ManyToOne(optional = true, cascade = CascadeType.MERGE)
|
||||
public Supplier supplierAccount;
|
||||
|
||||
public boolean isMainAccount;
|
||||
public boolean isVATAccount;
|
||||
|
||||
public BookingAccountEntry copyAddAmount(int amountCent) {
|
||||
BookingAccountEntry e = new BookingAccountEntry();
|
||||
|
||||
e.userAccount = userAccount;
|
||||
e.supplierAccount = supplierAccount;
|
||||
e.isMainAccount = isMainAccount;
|
||||
e.isVATAccount = isVATAccount;
|
||||
|
||||
e.newSumCent = newSumCent + amountCent;
|
||||
|
||||
return e;
|
||||
}
|
||||
|
||||
public static BookingAccountEntry newUser(User user) {
|
||||
BookingAccountEntry e = new BookingAccountEntry();
|
||||
e.userAccount = user;
|
||||
e.newSumCent = 0;
|
||||
|
||||
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;
|
||||
e.newSumCent = 0;
|
||||
|
||||
return e;
|
||||
}
|
||||
|
||||
public static BookingAccountEntry newVat() {
|
||||
BookingAccountEntry e = new BookingAccountEntry();
|
||||
e.isVATAccount = true;
|
||||
e.newSumCent = 0;
|
||||
|
||||
return e;
|
||||
}
|
||||
}
|
||||
|
@ -22,9 +22,30 @@ public class BookingReason {
|
||||
@ManyToOne(optional = true)
|
||||
public CustomerOrder customerOrder;
|
||||
|
||||
@ManyToOne(optional = true)
|
||||
@OneToOne(optional = true, cascade = CascadeType.ALL)
|
||||
public CustomerPayment customerPayment;
|
||||
|
||||
@ManyToOne(optional = true)
|
||||
public SupplierOrder supplierOrder;
|
||||
|
||||
// Default Constructor is needed for construction by ORM
|
||||
public BookingReason() {
|
||||
}
|
||||
|
||||
public BookingReason(CustomerOrder order) {
|
||||
this.customerOrder = order;
|
||||
}
|
||||
|
||||
public BookingReason(CustomerPayment customerPayment) {
|
||||
this.customerPayment = customerPayment;
|
||||
}
|
||||
|
||||
public BookingReason(SupplierOrder supplierOrder) {
|
||||
this.supplierOrder = supplierOrder;
|
||||
}
|
||||
|
||||
public BookingReason(String comment) {
|
||||
this.isManuel = true;
|
||||
this.comment = comment;
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,16 @@
|
||||
package org.hso.ecommerce.entities.booking;
|
||||
|
||||
import javax.persistence.Embeddable;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
@Embeddable
|
||||
public class PaymentMethod {
|
||||
@NotNull
|
||||
|
||||
public String creditCardNumber;
|
||||
|
||||
public static PaymentMethod fromCreditCardNumber(String cardnumber) {
|
||||
PaymentMethod m = new PaymentMethod();
|
||||
m.creditCardNumber = cardnumber;
|
||||
|
||||
return m;
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -4,7 +4,24 @@ import javax.persistence.Embeddable;
|
||||
|
||||
@Embeddable
|
||||
public class Address {
|
||||
public String name;
|
||||
public String addressString;
|
||||
public String name = "";
|
||||
public String addressString = "";
|
||||
public String country = "DE";
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name + "\n" + addressString;
|
||||
}
|
||||
|
||||
public static Address fromString(String addr) {
|
||||
Address a = new Address();
|
||||
|
||||
String[] arr = addr.split("\n", 2);
|
||||
a.name = arr[0];
|
||||
if (arr.length > 1) {
|
||||
a.addressString = arr[1];
|
||||
}
|
||||
|
||||
return a;
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ public class Article {
|
||||
@Basic
|
||||
public long id;
|
||||
|
||||
@ManyToOne(optional = false)
|
||||
@OneToOne(optional = false)
|
||||
public ArticleOffer related;
|
||||
|
||||
public int shopPricePerUnitNetCent;
|
||||
@ -32,9 +32,18 @@ public class Article {
|
||||
public String description;
|
||||
|
||||
@OneToOne(optional = true)
|
||||
@Basic(fetch = FetchType.LAZY)
|
||||
public Image image;
|
||||
|
||||
@ManyToMany
|
||||
@ManyToMany(cascade = CascadeType.ALL)
|
||||
@JoinTable(name = "article_categories_bindings")
|
||||
public Set<Category> categories = new HashSet<>();
|
||||
}
|
||||
|
||||
public int getVat() {
|
||||
return (shopPricePerUnitNetCent * related.vatPercent) / 100;
|
||||
}
|
||||
|
||||
public int getPriceGross() {
|
||||
return shopPricePerUnitNetCent + getVat();
|
||||
}
|
||||
}
|
@ -1,7 +1,5 @@
|
||||
package org.hso.ecommerce.entities.shop;
|
||||
|
||||
import org.hso.ecommerce.entities.shop.Article;
|
||||
|
||||
import javax.persistence.*;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.HashSet;
|
||||
@ -22,4 +20,15 @@ public class Category {
|
||||
|
||||
@ManyToMany(mappedBy = "categories")
|
||||
public Set<Article> articles = new HashSet<>();
|
||||
|
||||
|
||||
public Category() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
public Category (String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import org.hso.ecommerce.entities.user.User;
|
||||
|
||||
import javax.persistence.*;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@ -24,14 +25,14 @@ public class CustomerOrder {
|
||||
|
||||
@OneToMany(
|
||||
targetEntity = CustomerOrderPosition.class,
|
||||
mappedBy = "order"
|
||||
mappedBy = "order", cascade = CascadeType.ALL
|
||||
)
|
||||
public List<CustomerOrderPosition> positions = new ArrayList<>();
|
||||
|
||||
@NotNull
|
||||
public java.sql.Timestamp created;
|
||||
|
||||
@NotNull
|
||||
@Column(nullable = true)
|
||||
public String trackingId;
|
||||
|
||||
@Column(nullable = true)
|
||||
@ -43,4 +44,21 @@ public class CustomerOrder {
|
||||
public int totalNetCent;
|
||||
public int totalGrossCent;
|
||||
public int totalVatCent;
|
||||
|
||||
public String formatInDeliverySince(){
|
||||
return new SimpleDateFormat("dd.MM.yyyy HH:mm").format(inDeliverySince);
|
||||
}
|
||||
|
||||
public String formatCreated(){
|
||||
return new SimpleDateFormat("dd.MM.yyyy HH:mm").format(created);
|
||||
}
|
||||
|
||||
public String formatDeliveredAt(){
|
||||
return new SimpleDateFormat("dd.MM.yyyy HH:mm").format(deliveredAt);
|
||||
}
|
||||
|
||||
public String getEstimatedArrival() {
|
||||
//TODO: get estimated arrival from api
|
||||
return "TODO TODO TODO";
|
||||
}
|
||||
}
|
||||
|
@ -19,4 +19,8 @@ public class CustomerOrderPosition {
|
||||
|
||||
public int pricePerUnit;
|
||||
public int quantity;
|
||||
|
||||
public int getSumPrice(){
|
||||
return article.getPriceGross() * quantity;
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,5 @@ public class Image {
|
||||
@Basic
|
||||
public long id;
|
||||
|
||||
@Lob
|
||||
@Column(name = "data", columnDefinition = "BLOB", nullable = false)
|
||||
private byte[] data;
|
||||
}
|
||||
public String path;
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
package org.hso.ecommerce.entities.shop;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
// Not a db entity. Just for session storage
|
||||
public class ShoppingCart {
|
||||
|
||||
private final static int MAX_ITEMS = 10;
|
||||
|
||||
private int revision;
|
||||
private ArrayList<ShoppingCartItem> items;
|
||||
|
||||
public ShoppingCart() {
|
||||
clear();
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
items = new ArrayList<>();
|
||||
revision = (int) Math.round(Math.random() * 0xFFFF);
|
||||
}
|
||||
|
||||
public List<ShoppingCartItem> getItems() {
|
||||
return items;
|
||||
}
|
||||
|
||||
public int getItemCount() {
|
||||
int count = 0;
|
||||
|
||||
for (ShoppingCartItem i : items) {
|
||||
count += i.getAmount();
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
public int getRevision() {
|
||||
return revision;
|
||||
}
|
||||
|
||||
public void addArticle(Article article, int quantity) {
|
||||
this.revision++;
|
||||
|
||||
for (ShoppingCartItem i : items) {
|
||||
if (i.getArticleId() == article.id) {
|
||||
i.addAmount(quantity);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
items.add(new ShoppingCartItem(quantity, article));
|
||||
}
|
||||
|
||||
public void setArticleCount(Article article, Integer quantity) {
|
||||
this.revision++;
|
||||
|
||||
boolean found = false;
|
||||
for (ShoppingCartItem i : items) {
|
||||
if (i.getArticleId() == article.id) {
|
||||
i.setAmount(quantity);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
items.add(new ShoppingCartItem(quantity, article));
|
||||
}
|
||||
|
||||
items.removeIf(i -> i.getAmount() <= 0);
|
||||
}
|
||||
|
||||
public static class ShoppingCartItem {
|
||||
private int amount;
|
||||
private final long articleId;
|
||||
|
||||
public ShoppingCartItem(int amount, Article article) {
|
||||
this.amount = amount;
|
||||
this.articleId = article.id;
|
||||
}
|
||||
|
||||
public int getAmount() {
|
||||
return amount;
|
||||
}
|
||||
|
||||
public void addAmount(int amount) {
|
||||
this.amount += amount;
|
||||
if (this.amount > MAX_ITEMS) {
|
||||
this.amount = MAX_ITEMS;
|
||||
}
|
||||
}
|
||||
|
||||
public long getArticleId() {
|
||||
return articleId;
|
||||
}
|
||||
|
||||
public void setAmount(Integer amount) {
|
||||
this.amount = amount;
|
||||
if (this.amount > MAX_ITEMS) {
|
||||
this.amount = MAX_ITEMS;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -14,9 +14,20 @@ public class ArticleOffer {
|
||||
|
||||
@NotNull
|
||||
public String manufacturer;
|
||||
|
||||
@NotNull
|
||||
public String title;
|
||||
|
||||
@NotNull
|
||||
public int pricePerUnitNet;
|
||||
|
||||
@NotNull
|
||||
public String articleNumber;
|
||||
|
||||
public int vatPercent;
|
||||
|
||||
public boolean shouldBeAdvertised;
|
||||
|
||||
@ManyToOne(optional = true)
|
||||
public Supplier cheapestSupplier;
|
||||
}
|
||||
|
@ -24,16 +24,21 @@ public class User {
|
||||
@Column(unique = true)
|
||||
public String email;
|
||||
|
||||
@Column(insertable = false, updatable = false)
|
||||
public String name;
|
||||
|
||||
public String salutation;
|
||||
|
||||
public String passwordHash;
|
||||
|
||||
public boolean isActive;
|
||||
public boolean isEmployee;
|
||||
|
||||
@Embedded
|
||||
private Address defaultDeliveryAddress;
|
||||
public Address defaultDeliveryAddress;
|
||||
|
||||
@Embedded
|
||||
private PaymentMethod defaultPayment;
|
||||
public PaymentMethod defaultPayment;
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
|
@ -0,0 +1,17 @@
|
||||
package org.hso.ecommerce.entities.warehouse;
|
||||
|
||||
import javax.persistence.*;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
@Entity
|
||||
@Table(name = "warehouse_slots")
|
||||
public class Slot {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Basic
|
||||
public long id;
|
||||
|
||||
@NotNull
|
||||
@Column(unique = true)
|
||||
public int slotNum;
|
||||
}
|
@ -1,5 +1,7 @@
|
||||
package org.hso.ecommerce.entities.warehouse;
|
||||
|
||||
import org.hso.ecommerce.entities.booking.BookingReason;
|
||||
|
||||
import javax.persistence.*;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.ArrayList;
|
||||
@ -21,7 +23,11 @@ public class WarehouseBooking {
|
||||
public boolean isDone;
|
||||
|
||||
@OneToMany(
|
||||
mappedBy = "booking"
|
||||
mappedBy = "booking", cascade = CascadeType.ALL
|
||||
)
|
||||
public List<WarehouseBookingPosition> positions = new ArrayList<>();
|
||||
|
||||
// TODO FIX ME
|
||||
@OneToOne(optional = false, cascade = CascadeType.ALL)
|
||||
public BookingReason reason;
|
||||
}
|
||||
|
@ -23,10 +23,6 @@ public class WarehouseBookingPosition {
|
||||
|
||||
public int amount; // positive or negative
|
||||
|
||||
@ManyToOne(optional = true)
|
||||
public WarehouseBookingPositionSlotEntry source;
|
||||
|
||||
@ManyToOne(optional = true)
|
||||
public WarehouseBookingPositionSlotEntry destination;
|
||||
|
||||
@ManyToOne(optional = true, cascade = CascadeType.ALL)
|
||||
public WarehouseBookingPositionSlotEntry slotEntry;
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package org.hso.ecommerce.entities.warehouse;
|
||||
import org.hso.ecommerce.entities.shop.Article;
|
||||
|
||||
import javax.persistence.*;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
@Entity
|
||||
@Table(name = "warehouse_booking_position_entries")
|
||||
@ -13,11 +14,43 @@ public class WarehouseBookingPositionSlotEntry {
|
||||
@Basic
|
||||
public long id;
|
||||
|
||||
@NotNull
|
||||
@ManyToOne
|
||||
public Article article;
|
||||
|
||||
public int newSumArticles;
|
||||
public int newSumWarehousePosition;
|
||||
@NotNull
|
||||
public int newSumSlot;
|
||||
|
||||
public int slot;
|
||||
@NotNull
|
||||
@ManyToOne
|
||||
public Slot slot;
|
||||
|
||||
public WarehouseBookingPositionSlotEntry copyAddAmount(int amount, Article article) {
|
||||
// Article can be changed if newSumSlot == 0.
|
||||
if (this.newSumSlot != 0 && this.article.id != article.id) {
|
||||
throw new IllegalArgumentException("Article does not match.");
|
||||
}
|
||||
|
||||
WarehouseBookingPositionSlotEntry e = new WarehouseBookingPositionSlotEntry();
|
||||
|
||||
e.article = article;
|
||||
e.slot = slot;
|
||||
|
||||
e.newSumSlot = newSumSlot + amount;
|
||||
|
||||
assert e.article.warehouseUnitsPerSlot >= e.newSumSlot;
|
||||
assert e.newSumSlot >= 0;
|
||||
|
||||
return e;
|
||||
}
|
||||
|
||||
public static WarehouseBookingPositionSlotEntry empty(Article article, Slot slot) {
|
||||
WarehouseBookingPositionSlotEntry e = new WarehouseBookingPositionSlotEntry();
|
||||
|
||||
e.article = article;
|
||||
e.slot = slot;
|
||||
e.newSumSlot = 0;
|
||||
|
||||
return e;
|
||||
}
|
||||
}
|
||||
|
@ -23,4 +23,12 @@ public class WarehouseBookingReason {
|
||||
public CustomerOrder customerOrder;
|
||||
|
||||
public boolean isManuel;
|
||||
|
||||
// Default Constructor is needed for construction by ORM
|
||||
public WarehouseBookingReason() {
|
||||
}
|
||||
|
||||
public WarehouseBookingReason(CustomerOrder order) {
|
||||
this.customerOrder = order;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,28 @@
|
||||
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;
|
||||
|
||||
@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();
|
||||
|
||||
@Query(value = "SELECT * FROM booking_account_entries as e WHERE e.isvataccount = 1 ORDER BY e.id DESC LIMIT 1", nativeQuery = true)
|
||||
Optional<BookingAccountEntry> getByVat();
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,28 @@
|
||||
package org.hso.ecommerce.repos.booking;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.hso.ecommerce.entities.booking.Booking;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface BookingRepository extends JpaRepository<Booking, Long> {
|
||||
|
||||
@Query("SELECT b FROM Booking b ORDER BY b.id DESC")
|
||||
List<Booking> allBookingsReverseChronologically();
|
||||
|
||||
@Query("SELECT b FROM Booking b LEFT JOIN b.source s LEFT JOIN b.destination d WHERE s.isMainAccount = 1 OR d.isMainAccount = 1 ORDER BY b.id DESC")
|
||||
List<Booking> mainBookingsReverseChronologically();
|
||||
|
||||
@Query("SELECT b FROM Booking b LEFT JOIN b.source s LEFT JOIN b.destination d WHERE s.isVATAccount = 1 OR d.isVATAccount = 1 ORDER BY b.id DESC")
|
||||
List<Booking> vatBookingsReverseChronologically();
|
||||
|
||||
@Query("SELECT b FROM Booking b LEFT JOIN b.source s LEFT JOIN b.destination d WHERE s.userAccount.id = :customerId OR d.userAccount.id = :customerId ORDER BY b.id DESC")
|
||||
List<Booking> customerBookingsReverseChronologically(long customerId);
|
||||
|
||||
@Query("SELECT b FROM Booking b LEFT JOIN b.source s LEFT JOIN b.destination d WHERE s.supplierAccount.id = :supplierId OR d.supplierAccount.id = :supplierId ORDER BY b.id DESC")
|
||||
List<Booking> supplierBookingsReverseChronologically(long supplierId);
|
||||
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package org.hso.ecommerce.repos.cronjob;
|
||||
|
||||
import org.hso.ecommerce.entities.cron.BackgroundJob;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface BackgroundJobRepository extends JpaRepository<BackgroundJob, Long> {
|
||||
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
package org.hso.ecommerce.repos.shop;
|
||||
|
||||
import org.hso.ecommerce.entities.shop.Article;
|
||||
import org.hso.ecommerce.entities.supplier.ArticleOffer;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Repository
|
||||
public interface ArticleRepository extends JpaRepository<Article, Long> {
|
||||
|
||||
/***
|
||||
* use findById instead.
|
||||
*/
|
||||
@Deprecated
|
||||
@Query("SELECT a FROM Article a WHERE a.id = :articleId")
|
||||
Article findArticleById(@Param("articleId") long articleId);
|
||||
|
||||
@Query("SELECT a FROM Article a")
|
||||
List<Article> findAll();
|
||||
|
||||
@Query(value = "Select a.* from articles as a, article_offers as ao, warehouse_booking_position_entries as wbpe where a.related_id = ao.id and wbpe.article_id = a.id and ao.should_be_advertised = true group by wbpe.slot_id having max(wbpe.id) and wbpe.new_sum_slot != 0", nativeQuery = true)
|
||||
List<Article> getAdvertisedArticles();
|
||||
|
||||
@Query("SELECT a FROM CustomerOrderPosition cop JOIN cop.order co JOIN co.customer c JOIN cop.article a ORDER BY co.id DESC")
|
||||
List<Article> getOrderedArticles();
|
||||
|
||||
@Query("SELECT a FROM CustomerOrderPosition cop JOIN cop.order co JOIN co.customer c JOIN cop.article a WHERE c.id = :customerId ORDER BY co.id DESC")
|
||||
List<Article> getOrderedArticles(long customerId);
|
||||
|
||||
/***
|
||||
* use type safe findArticleByArticleOffer instead.
|
||||
*/
|
||||
@Deprecated
|
||||
@Query(value = "SELECT a.id FROM articles a WHERE a.related_id = :relatedId", nativeQuery = true)
|
||||
Optional<Integer> findArticleIDByRelatedID(@Param("relatedId") long relatedId);
|
||||
|
||||
@Query(value = "SELECT a FROM Article a Where a.related = :related")
|
||||
Optional<Article> findArticleByArticleOffer(ArticleOffer related);
|
||||
|
||||
@Query(value = "Select a.* from articles as a, warehouse_booking_position_entries as wbpe where wbpe.article_id = a.id and a.title LIKE %:term% group by wbpe.slot_id having max(wbpe.id) and wbpe.new_sum_slot != 0", nativeQuery = true)
|
||||
List<Article> getArticlesByTermInTitle(String term);
|
||||
|
||||
@Query(value = "Select a.* from articles as a, warehouse_booking_position_entries as wbpe where wbpe.article_id = a.id and a.description LIKE %:term% group by wbpe.slot_id having max(wbpe.id) and wbpe.new_sum_slot != 0", nativeQuery = true)
|
||||
List<Article> getArticlesByTermInDescription(String term);
|
||||
|
||||
@Query(value = "Select a.* from articles as a, categories as c, article_categories_bindings as acb, warehouse_booking_position_entries as wbpe where wbpe.article_id = a.id and acb.articles_id = a.id and acb.categories_id = c.id and c.name = :category group by wbpe.slot_id having max(wbpe.id) and wbpe.new_sum_slot != 0", nativeQuery = true)
|
||||
List<Article> getArticlesByCategory(String category);
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package org.hso.ecommerce.repos.shop;
|
||||
|
||||
import org.hso.ecommerce.entities.shop.Category;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Repository
|
||||
public interface CategoryRepository extends JpaRepository<Category, Long> {
|
||||
|
||||
@Query("SELECT a FROM Category a WHERE a.name = :name")
|
||||
Optional<Category> findCategoryByName(@Param("name") String name);
|
||||
|
||||
@Query("SELECT c FROM Category c")
|
||||
List<Category> getCategories();
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
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;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Repository
|
||||
public interface CustomerOrderRepository 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
|
||||
);
|
||||
|
||||
@Query("SELECT co FROM CustomerOrder co WHERE co.customer.id = :userId ORDER BY co.id DESC")
|
||||
List<CustomerOrder> getOrdersByUserId(long userId);
|
||||
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package org.hso.ecommerce.repos.shop;
|
||||
|
||||
import org.hso.ecommerce.entities.shop.Image;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@Repository
|
||||
public interface ImageRepository extends JpaRepository<Image, Long> {
|
||||
|
||||
@Query("SELECT i.id FROM Image i WHERE i.path = :path")
|
||||
Optional<Integer> findImageIDByPath(@Param("path") String path);
|
||||
|
||||
@Query("SELECT i FROM Image i WHERE i.id = :imageId")
|
||||
Image findImageById(@Param("imageId") long imageId);
|
||||
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package org.hso.ecommerce.repos.shop;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.hso.ecommerce.entities.supplier.ArticleOffer;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface OffersRepository extends JpaRepository<ArticleOffer, Long> {
|
||||
|
||||
@Query("SELECT a FROM ArticleOffer a")
|
||||
List<ArticleOffer> findAll();
|
||||
|
||||
@Query("SELECT a FROM ArticleOffer a WHERE a.id = :offeredarticleId")
|
||||
ArticleOffer findOfferedArticleById(@Param("offeredarticleId") long offeredarticleId);
|
||||
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package org.hso.ecommerce.repos.supplier;
|
||||
|
||||
import org.hso.ecommerce.entities.supplier.ArticleOffer;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface ArticleOfferRepository extends JpaRepository<ArticleOffer, Long> {
|
||||
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package org.hso.ecommerce.repos.supplier;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.hso.ecommerce.entities.supplier.SupplierOrder;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
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);
|
||||
|
||||
@Query(value = "SELECT * FROM supplier_orders as a WHERE a.supplier_id = :supplierId", nativeQuery = true)
|
||||
List<SupplierOrder> findOrderBySupplierID(@Param("supplierId") long supplierId);
|
||||
|
||||
@Query("SELECT a FROM SupplierOrder a")
|
||||
List<SupplierOrder> findAll();
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package org.hso.ecommerce.repos.supplier;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.hso.ecommerce.entities.supplier.Supplier;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface SupplierRepository extends JpaRepository<Supplier, Long> {
|
||||
|
||||
@Query("SELECT a FROM Supplier a")
|
||||
List<Supplier> findAll();
|
||||
|
||||
@Query("SELECT a FROM Supplier a WHERE a.id = :supplierId")
|
||||
Supplier findSupplierById(@Param("supplierId") long supplierId);
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package org.hso.ecommerce.repos.warehouse;
|
||||
|
||||
import org.hso.ecommerce.entities.warehouse.Slot;
|
||||
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 SlotRepository extends JpaRepository<Slot, Long> {
|
||||
|
||||
@Query("SELECT s FROM Slot s WHERE s.slotNum = :slotNum")
|
||||
Optional<Slot> findBySlotNum(int slotNum);
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package org.hso.ecommerce.repos.warehouse;
|
||||
|
||||
import org.hso.ecommerce.entities.warehouse.WarehouseBookingPositionSlotEntry;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Repository
|
||||
public interface WarehouseBookingPositionSlotEntryRepository extends JpaRepository<WarehouseBookingPositionSlotEntry, Long> {
|
||||
|
||||
@Query(value = "Select e.id, e.article_id, e.new_sum_slot, e.slot_id from warehouse_booking_position_entries as e, warehouse_slots as s where e.slot_id = s.id AND e.article_id = :article GROUP BY s.slot_num HAVING max(e.id)", nativeQuery = true)
|
||||
List<WarehouseBookingPositionSlotEntry> getByArticle(long article);
|
||||
|
||||
@Query(value = "SELECT SUM(w.new_sum_slot) FROM warehouse_booking_position_entries as w WHERE w.article_id = :articleid", nativeQuery = true)
|
||||
Optional<Integer> getArticleStock(long articleid);
|
||||
|
||||
@Query(value = "Select e.id, e.article_id, e.new_sum_slot, e.slot_id from warehouse_booking_position_entries as e, warehouse_slots as s where e.slot_id = s.id AND s.slot_num = :slotnum GROUP BY s.slot_num HAVING max(e.id)", nativeQuery = true)
|
||||
Optional<WarehouseBookingPositionSlotEntry> getBySlotNum(long slotnum);
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,17 @@
|
||||
package org.hso.ecommerce.repos.warehouse;
|
||||
|
||||
import org.hso.ecommerce.entities.warehouse.WarehouseBooking;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Repository
|
||||
public interface WarehouseBookingRepository extends JpaRepository<WarehouseBooking, Long> {
|
||||
|
||||
@Query("Select b FROM WarehouseBooking b WHERE b.isDone = 0")
|
||||
List<WarehouseBooking> findNotDone();
|
||||
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ spring.resources.cache.cachecontrol.maxAge=P0D
|
||||
# LOGGING
|
||||
logging.level.org.springframework.web=WARN
|
||||
# DATABASE
|
||||
spring.datasource.url=jdbc:sqlite:./test.db
|
||||
spring.datasource.url=jdbc:sqlite:./e-commerce.db
|
||||
spring.datasource.driverClassName=org.sqlite.JDBC
|
||||
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.SQLiteDialect
|
||||
spring.jpa.hibernate.ddl-auto=update
|
||||
@ -16,6 +16,8 @@ spring.jpa.show-sql=true
|
||||
#server.servlet.session.persistent=true
|
||||
# ----------------------------------------
|
||||
# WEB PROPERTIES
|
||||
spring.servlet.multipart.max-file-size=10MB
|
||||
spring.servlet.multipart.max-request-size=10MB
|
||||
# ----------------------------------------
|
||||
# EMBEDDED SERVER CONFIGURATION (ServerProperties)
|
||||
server.address=::1
|
||||
|
@ -1,7 +0,0 @@
|
||||
CREATE TABLE "customers" (
|
||||
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
"lastname" TEXT,
|
||||
"firstname" TEXT,
|
||||
"username" TEXT,
|
||||
"password" TEXT
|
||||
);
|
@ -278,7 +278,7 @@ img.s {
|
||||
}
|
||||
|
||||
img.m {
|
||||
width: var(--u8);
|
||||
width: 20rem;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -1,277 +1,100 @@
|
||||
<svg id="mainImage_create" data-name="mainImage" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 171.2 81.5">
|
||||
<style id="svgStyle">
|
||||
@import url('https://fonts.googleapis.com/css?family=Merriweather');
|
||||
.changeColor {
|
||||
fill: #4f86ed;
|
||||
}
|
||||
|
||||
#title {
|
||||
font-size: 50%;
|
||||
font-family: 'Merriweather', serif;
|
||||
}
|
||||
|
||||
.cls-1 {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.cls-7 {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.cls-2 {
|
||||
fill: #fff;
|
||||
}
|
||||
|
||||
.cls-10,
|
||||
.cls-11,
|
||||
.cls-12,
|
||||
.cls-14,
|
||||
.cls-16,
|
||||
.cls-3 {
|
||||
fill: none;
|
||||
}
|
||||
|
||||
.cls-3 {
|
||||
stroke: #5c7690;
|
||||
}
|
||||
|
||||
.cls-10,
|
||||
.cls-11,
|
||||
.cls-12,
|
||||
.cls-3 {
|
||||
stroke-miterlimit: 10;
|
||||
}
|
||||
|
||||
.cls-14,
|
||||
.cls-15,
|
||||
.cls-16,
|
||||
.cls-3 {
|
||||
stroke-width: 0.5px;
|
||||
}
|
||||
|
||||
.cls-4 {
|
||||
fill: #ffe1d9;
|
||||
}
|
||||
|
||||
.cls-5 {
|
||||
fill: #ffcfbf;
|
||||
}
|
||||
|
||||
.cls-6 {
|
||||
fill: #fecbb6;
|
||||
}
|
||||
|
||||
.cls-9 {
|
||||
fill: #fecb02;
|
||||
}
|
||||
|
||||
.cls-10,
|
||||
.cls-12 {
|
||||
stroke: #d26f51;
|
||||
}
|
||||
|
||||
.cls-10,
|
||||
.cls-11 {
|
||||
stroke-width: 0.38px;
|
||||
}
|
||||
|
||||
.cls-11 {
|
||||
stroke: #000;
|
||||
}
|
||||
|
||||
.cls-12 {
|
||||
stroke-width: 0.19px;
|
||||
}
|
||||
|
||||
.cls-13 {
|
||||
opacity: 0.45;
|
||||
}
|
||||
|
||||
.cls-14,
|
||||
.cls-15,
|
||||
.cls-16 {
|
||||
stroke: #b0bec5;
|
||||
stroke-linejoin: round;
|
||||
}
|
||||
|
||||
.cls-15 {
|
||||
fill: #edf0f2;
|
||||
}
|
||||
|
||||
.cls-16 {
|
||||
stroke-linecap: round;
|
||||
}
|
||||
|
||||
.cls-17 {
|
||||
font-family: 'PT Sans', sans-serif;
|
||||
font-size: 49.87px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.cls-18 {
|
||||
fill: #fffdbb;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
|
||||
/*---------------------------
|
||||
SVG Animate
|
||||
---------------------------*/
|
||||
.earMove {
|
||||
transition: all ease-in-out 2s;
|
||||
transform-origin: 50% 50%;
|
||||
animation: earmove 1.5s linear infinite alternate;
|
||||
}
|
||||
|
||||
.faceMove {
|
||||
transition: all ease-in-out 2s;
|
||||
transform-origin: 50% 50%;
|
||||
animation: move 1.5s linear infinite alternate;
|
||||
}
|
||||
|
||||
.neckMove {
|
||||
transition: all ease-in-out 2s;
|
||||
transform-origin: 50% 50%;
|
||||
animation: neck 1.5s linear infinite alternate;
|
||||
}
|
||||
|
||||
|
||||
@keyframes earmove {
|
||||
0% {
|
||||
transform: translateX(-0.3px) translateY(0.6px);
|
||||
}
|
||||
30% {
|
||||
transform: translateX(-0.3px) translateY(0.6px);
|
||||
}
|
||||
|
||||
60% {
|
||||
transform: translateX(-0.7px) translateY(0px);
|
||||
}
|
||||
|
||||
70% {
|
||||
transform: translateX(-0.7px) translateY(-0.3px);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(-0.7px) translateY(-0.3px);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes move {
|
||||
0% {
|
||||
transform: translateX(-0.3px) translateY(0.6px);
|
||||
}
|
||||
30% {
|
||||
transform: translateX(-0.3px) translateY(0.6px);
|
||||
}
|
||||
|
||||
60% {
|
||||
transform: translateX(2px) translateY(0px);
|
||||
}
|
||||
|
||||
70% {
|
||||
transform: translateX(2px) translateY(-0.3px);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(2px) translateY(-0.3px);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes neck {
|
||||
0% {
|
||||
transform: translateY(0.7px);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(0.7px);
|
||||
}
|
||||
100% {
|
||||
transform: translateY(0px);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<path id="c-1" class="changeColor cls-1"
|
||||
d="M46.62,52.5c5.78,4.9,21.14,8.4,39.19,8.4s33.41-3.5,39.19-8.4c-5.78-4.9-21.14-8.4-39.19-8.4S52.41,47.6,46.62,52.5Z"
|
||||
style="fill: rgb(0, 21, 255);"></path>
|
||||
<path class="cls-2"
|
||||
d="M99.73,47.71H68.65a7.13,7.13,0,0,0-7.13,7.13V60a152.58,152.58,0,0,0,24.3,1.83,157.87,157.87,0,0,0,21.05-1.35V54.84A7.13,7.13,0,0,0,99.73,47.71Z"></path>
|
||||
<path class="cls-3"
|
||||
d="M123.56,55.81C115,58.94,101.27,61,85.81,61c-26,0-47-5.71-47-12.76,0-3.45,5.05-6.58,13.25-8.88"></path>
|
||||
<path class="cls-3" d="M55.37,38.47a140,140,0,0,1,30.44-3c26,0,47,5.71,47,12.76,0,2.4-2.44,4.65-6.69,6.57"></path>
|
||||
<path class="cls-3" d="M53.41,38.95l.94-.24"></path>
|
||||
<path class="cls-4"
|
||||
d="M91.68,47.71l-.75-11.2L79.15,43.84l-1.69,3.87H75.79c0,3.36,3.76,6.08,8.4,6.08s8.4-2.72,8.4-6.08Z"></path>
|
||||
<path class="cls-5 neckMove"
|
||||
d="M78,46.53a27.19,27.19,0,0,0,6.41.82c3.1,0,7.11-2.19,7.11-2.19l-.42-6.2L79.15,43.84Z"></path>
|
||||
<polygon class="earMove" points="92.59 32.22 92.59 28.5 76.77 27.71 76.77 32.22 92.59 32.22"></polygon>
|
||||
<circle class="cls-6 earMove" cx="78.06" cy="34.04" r="2.47"></circle>
|
||||
<path class="cls-4"
|
||||
d="M81.74,57.06,60.63,49.72h0A6.72,6.72,0,1,0,57.7,62.49H93.25C93.25,56.78,81.74,57.06,81.74,57.06Z"></path>
|
||||
<path class="cls-4"
|
||||
d="M77.46,25H90.92a0,0,0,0,1,0,0V39.38a6.73,6.73,0,0,1-6.73,6.73h0a6.73,6.73,0,0,1-6.73-6.73V25A0,0,0,0,1,77.46,25Z"></path>
|
||||
<rect id="c-2" class="changeColor cls-7" x="74.82" y="26.48" width="19.14" height="2.45"
|
||||
transform="translate(1.29 -3.65) rotate(2.49)" style="fill: rgb(0, 21, 255);"></rect>
|
||||
<path id="c-3" class="changeColor cls-7"
|
||||
d="M84.36,18.69h.5a7.8,7.8,0,0,1,7.8,7.8v0a0,0,0,0,1,0,0H76.56a0,0,0,0,1,0,0v0A7.8,7.8,0,0,1,84.36,18.69Z"
|
||||
transform="translate(1.06 -3.66) rotate(2.49)" style="fill: rgb(0, 21, 255);"></path>
|
||||
<polygon id="c-4" class="changeColor cls-8"
|
||||
points="82.44 23.89 92.18 24.32 92.59 24.34 92.48 26.84 80.96 26.33 82.44 23.89"
|
||||
style="fill: rgb(0, 21, 255);"></polygon>
|
||||
<circle class="cls-9 faceMove" cx="78.72" cy="23.73" r="3.73"
|
||||
transform="translate(51.58 101.34) rotate(-87.51)"></circle>
|
||||
<circle class="cls-2 faceMove" cx="78.72" cy="23.73" r="2.36"
|
||||
transform="translate(51.58 101.34) rotate(-87.51)"></circle>
|
||||
<circle class="cls-4 earMove" cx="90.92" cy="34.04" r="2.47"></circle>
|
||||
<path class="cls-4"
|
||||
d="M112.2,53l-9.87-21.92-3-5.48-11.86-.22,7.42,3.35H91.55l5.82,4.58,2,22.26h0A6.72,6.72,0,1,0,112.2,53Z"></path>
|
||||
<ellipse class="faceMove" cx="80.09" cy="33.12" rx="0.53" ry="0.59"></ellipse>
|
||||
<ellipse class="faceMove" cx="86.34" cy="33.12" rx="0.53" ry="0.59"></ellipse>
|
||||
<polyline class="cls-10 faceMove" points="84.19 31.08 81.74 37.01 84.39 37.01"></polyline>
|
||||
<path class="cls-10 faceMove" d="M83.06,40.36a4,4,0,0,1,2.75-1"></path>
|
||||
<line class="cls-11 faceMove" x1="81.07" y1="30.33" x2="78.47" y2="30.58"></line>
|
||||
<line class="cls-11 faceMove" x1="86.34" y1="30.45" x2="88.15" y2="31.08"></line>
|
||||
<line class="cls-12" x1="106.86" y1="47.82" x2="110.99" y2="46.11"></line>
|
||||
<line class="cls-12" x1="107.43" y1="49.9" x2="111.55" y2="48.19"></line>
|
||||
<line class="cls-12" x1="107.99" y1="51.98" x2="112.11" y2="50.27"></line>
|
||||
<g class="cls-13">
|
||||
<rect class="cls-14" x="85.81" y="2.46" width="10.77" height="3.5"></rect>
|
||||
<rect class="cls-15" x="96.58" y="2.46" width="10.77" height="3.5"></rect>
|
||||
<rect class="cls-14" x="92.19" y="5.95" width="10.77" height="3.5"></rect>
|
||||
<line class="cls-16" x1="107.36" y1="5.95" x2="109.63" y2="5.95"></line>
|
||||
<line class="cls-16" x1="110.68" y1="5.95" x2="111.57" y2="5.95"></line>
|
||||
</g>
|
||||
<g class="cls-13">
|
||||
<rect class="cls-16" x="125" y="23.12" width="10.77" height="3.5"></rect>
|
||||
<rect class="cls-15" x="130.39" y="26.62" width="10.77" height="3.5"></rect>
|
||||
<rect class="cls-16" x="119.62" y="26.62" width="10.77" height="3.5"></rect>
|
||||
<line class="cls-16" x1="141.16" y1="26.62" x2="145.73" y2="26.62"></line>
|
||||
<line class="cls-16" x1="125" y1="23.12" x2="115.4" y2="23.12"></line>
|
||||
<line class="cls-16" x1="117.95" y1="26.62" x2="115.4" y2="26.62"></line>
|
||||
</g>
|
||||
<g class="cls-13">
|
||||
<rect class="cls-16" x="39.34" y="16.12" width="10.77" height="3.5"></rect>
|
||||
<rect class="cls-16" x="39.34" y="23.11" width="10.77" height="3.5"></rect>
|
||||
<rect class="cls-16" x="50.11" y="23.11" width="10.77" height="3.5"></rect>
|
||||
<rect class="cls-16" x="50.11" y="16.12" width="10.77" height="3.5"></rect>
|
||||
<rect class="cls-15" x="44" y="19.61" width="10.77" height="3.5"></rect>
|
||||
<rect class="cls-16" x="33.23" y="19.61" width="10.77" height="3.5"></rect>
|
||||
<line class="cls-16" x1="60.89" y1="19.61" x2="65.51" y2="19.61"></line>
|
||||
<line class="cls-16" x1="39.34" y1="16.12" x2="35.46" y2="16.12"></line>
|
||||
<line class="cls-16" x1="36.45" y1="26.61" x2="33.23" y2="26.61"></line>
|
||||
<line class="cls-16" x1="63.2" y1="23.11" x2="65.51" y2="23.11"></line>
|
||||
</g>
|
||||
<polyline class="cls-3" points="115.4 58.12 115.4 38.27 120.2 37.01"></polyline>
|
||||
<polyline class="cls-3" points="129.01 53.21 129.01 43.14 131.74 42.13"></polyline>
|
||||
<path class="cls-3" d="M115.4,42.13a53.27,53.27,0,0,1,8,2A42,42,0,0,1,129,47"></path>
|
||||
<path class="cls-3" d="M115.4,47.34a53.27,53.27,0,0,1,8,2A42,42,0,0,1,129,52.22"></path>
|
||||
<path class="cls-3" d="M115.4,52.56a53.27,53.27,0,0,1,8,2l1,.42"></path>
|
||||
<path class="cls-18 faceMove"
|
||||
d="M78.84,26.09l0-4.71L68.05,18.32a.91.91,0,0,0-.45-.13c-1.17,0-2.11,2.46-2.11,5.5s.95,5.5,2.11,5.5a.9.9,0,0,0,.44-.12Z"></path>
|
||||
<path class="cls-5" d="M57.7,62.49H93.25A3.67,3.67,0,0,0,92.92,61H53.43A6.69,6.69,0,0,0,57.7,62.49Z"></path>
|
||||
<path class="cls-12" d="M88.15,60.27s1.7.95,1.7,2.22"></path>
|
||||
<path class="cls-5" d="M101.81,61a6.68,6.68,0,0,0,8.51,0Z"></path>
|
||||
<polygon class="cls-5" points="90.92 30.25 77.46 29.69 77.46 28.64 90.92 29.22 90.92 30.25"></polygon>
|
||||
<text id="title" transform="matrix(1 0 0 1 44.7249 78)">Oops, Page not found</text>
|
||||
</svg>
|
||||
|
||||
<!-- <h1 id="title">404 Page not found</h1> -->
|
||||
<!-- <button id="newBtn" class="btn" disabled>→ Back to Home</button> -->
|
||||
|
||||
<svg id="fd59ce54-f850-4dfc-bc34-dd7d379d600e" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" width="1074.392"
|
||||
height="584.231" viewBox="0 0 1074.392 584.231">
|
||||
<title>page not found</title>
|
||||
<ellipse cx="540.64346" cy="549.3094" rx="527.5" ry="34.9216" fill="#f2f2f2"/>
|
||||
<path d="M583.47969,324.89424c-85.94407,0-147.651,55.13938-147.651,183.79791,0,145.813,61.70691,184.41057,147.651,184.41057s151.327-42.27352,151.327-184.41057C734.80664,356.75255,669.42376,324.89424,583.47969,324.89424Zm.56495,319.80837c-59.52686,0-90.62592-34.92288-90.62592-135.9163,0-89.11185,32.37209-136.10461,91.899-136.10461s91.899,30.86774,91.899,136.10461C677.21663,607.23367,643.5715,644.70261,584.04464,644.70261Z"
|
||||
transform="translate(-63.054 -157.8845)" fill="#2f2e41"/>
|
||||
<path d="M384.36531,591.40121H348.831V486.76183A20.95585,20.95585,0,0,0,327.87517,465.806h-8.32638a20.95585,20.95585,0,0,0-20.95586,20.95585V591.40121H198.36285a11.96327,11.96327,0,0,1-10.57763-17.552l106.0824-200.78034A20.95585,20.95585,0,0,0,284.28724,344.33l-6.26231-2.9572a20.95585,20.95585,0,0,0-27.4293,9.07005L121.21416,592.4754a28.41578,28.41578,0,0,0-3.35584,13.39612v0a28.41583,28.41583,0,0,0,28.41584,28.41583H298.59293v66.16727a25.119,25.119,0,0,0,25.119,25.119h.00005a25.119,25.119,0,0,0,25.119-25.119V634.28739h35.53428a21.44307,21.44307,0,0,0,21.44307-21.44307v0A21.44307,21.44307,0,0,0,384.36531,591.40121Z"
|
||||
transform="translate(-63.054 -157.8845)" fill="#16a085"/>
|
||||
<path d="M1042.36183,591.40121h-35.53428V486.76183A20.95585,20.95585,0,0,0,985.87169,465.806h-8.32638a20.95585,20.95585,0,0,0-20.95586,20.95585V591.40121H856.35937a11.96326,11.96326,0,0,1-10.57763-17.552L951.86413,373.06891A20.95586,20.95586,0,0,0,942.28376,344.33l-6.26231-2.9572a20.95586,20.95586,0,0,0-27.42931,9.07005L779.21068,592.4754a28.41578,28.41578,0,0,0-3.35584,13.39612v0a28.41583,28.41583,0,0,0,28.41583,28.41583H956.58945v66.16727a25.119,25.119,0,0,0,25.119,25.119h0a25.119,25.119,0,0,0,25.119-25.119V634.28739h35.53428a21.44307,21.44307,0,0,0,21.44307-21.44307v0A21.44307,21.44307,0,0,0,1042.36183,591.40121Z"
|
||||
transform="translate(-63.054 -157.8845)" fill="#16a085"/>
|
||||
<path d="M394.16787,579.148H358.63358V474.50864a20.95585,20.95585,0,0,0-20.95585-20.95586h-8.32638a20.95586,20.95586,0,0,0-20.95586,20.95586V579.148H208.16541a11.96327,11.96327,0,0,1-10.57763-17.552L303.67017,360.81572a20.95586,20.95586,0,0,0-9.58037-28.73893l-6.26231-2.9572a20.95586,20.95586,0,0,0-27.42931,9.07L131.01672,580.2222a28.41582,28.41582,0,0,0-3.35584,13.39613v0a28.41583,28.41583,0,0,0,28.41583,28.41583H308.39549v66.16727a25.119,25.119,0,0,0,25.119,25.119h.00005a25.119,25.119,0,0,0,25.119-25.119V622.0342h35.53429a21.44307,21.44307,0,0,0,21.44307-21.44307v0A21.44307,21.44307,0,0,0,394.16787,579.148Z"
|
||||
transform="translate(-63.054 -157.8845)" fill="none" stroke="#3f3d56" stroke-miterlimit="10"/>
|
||||
<path d="M1060.74162,579.148h-35.53428V474.50864a20.95586,20.95586,0,0,0-20.95586-20.95586H995.9251a20.95586,20.95586,0,0,0-20.95586,20.95586V579.148H874.73916a11.96327,11.96327,0,0,1-10.57763-17.552L970.24392,360.81572a20.95586,20.95586,0,0,0-9.58037-28.73893l-6.26231-2.9572a20.95586,20.95586,0,0,0-27.42931,9.07L797.59047,580.2222a28.41582,28.41582,0,0,0-3.35584,13.39613v0a28.41583,28.41583,0,0,0,28.41583,28.41583H974.96924v66.16727a25.119,25.119,0,0,0,25.119,25.119h0a25.119,25.119,0,0,0,25.119-25.119V622.0342h35.53428a21.44307,21.44307,0,0,0,21.44307-21.44307v0A21.44307,21.44307,0,0,0,1060.74162,579.148Z"
|
||||
transform="translate(-63.054 -157.8845)" fill="none" stroke="#3f3d56" stroke-miterlimit="10"/>
|
||||
<path d="M603.0848,313.86637c-85.94407,0-147.651,55.13937-147.651,183.79791,0,145.813,61.70691,184.41057,147.651,184.41057s151.327-42.27352,151.327-184.41057C754.41175,345.72467,689.02887,313.86637,603.0848,313.86637Zm.565,319.80836c-59.52686,0-90.62592-34.92287-90.62592-135.91629,0-89.11185,32.37209-136.10461,91.899-136.10461s91.899,30.86774,91.899,136.10461C696.82174,596.20579,663.17661,633.67473,603.64975,633.67473Z"
|
||||
transform="translate(-63.054 -157.8845)" fill="none" stroke="#3f3d56" stroke-miterlimit="10"/>
|
||||
<circle cx="471.14108" cy="18.25044" r="12.90118" fill="#2f2e41"/>
|
||||
<ellipse cx="502.60736" cy="46.88476" rx="36.18622" ry="46.88476" fill="#2f2e41"/>
|
||||
<path d="M565.66136,237.49419c-18.1276,0-33.1413-17.27052-35.77576-39.80484a60.9759,60.9759,0,0,0-.41046,7.07991c0,25.89373,16.20114,46.88476,36.18622,46.88476s36.18623-20.991,36.18623-46.88476a60.9759,60.9759,0,0,0-.41046-7.07991C598.80267,220.22367,583.789,237.49419,565.66136,237.49419Z"
|
||||
transform="translate(-63.054 -157.8845)" opacity="0.1"/>
|
||||
<path d="M639.29619,342.07326c-.77711,3.19345-4.12792,5.751-7.83881,7.53791-7.80188,3.75682-17.4253,4.87788-26.7597,5.25418a45.17622,45.17622,0,0,1-7.1445-.132,20.5371,20.5371,0,0,1-12.25052-5.63141,1.68086,1.68086,0,0,1,.04371-2.84388c4.9694-5.45888,13.2622-8.80605,21.61613-11.21609,6.3344-1.82743,17.3813-6.56089,24.29013-5.9221C637.94444,329.73864,640.2774,338.04112,639.29619,342.07326Z"
|
||||
transform="translate(-63.054 -157.8845)" fill="#3f3d56"/>
|
||||
<path d="M639.29619,342.07326c-.77711,3.19345-4.12792,5.751-7.83881,7.53791-7.80188,3.75682-17.4253,4.87788-26.7597,5.25418a45.17622,45.17622,0,0,1-7.1445-.132,20.5371,20.5371,0,0,1-12.25052-5.63141,1.68086,1.68086,0,0,1,.04371-2.84388c4.9694-5.45888,13.2622-8.80605,21.61613-11.21609,6.3344-1.82743,17.3813-6.56089,24.29013-5.9221C637.94444,329.73864,640.2774,338.04112,639.29619,342.07326Z"
|
||||
transform="translate(-63.054 -157.8845)" opacity="0.1"/>
|
||||
<path d="M540.09786,318.2059a19.76967,19.76967,0,0,0-1.1987,15.07476,26.33914,26.33914,0,0,0,8.82921,12.49683c10.09467,8.09163,23.98784,9.20512,36.92477,9.09278a284.6495,284.6495,0,0,0,33.90525-2.32384,40.53788,40.53788,0,0,0,11.00143-2.55442c4.22242-1.82679,7.93282-5.17756,9.436-9.5257s.43625-9.67246-3.13383-12.57428c-3.13686-2.54969-7.46265-2.9004-11.49775-3.14289l-23.08764-1.38745c2.281-2.30839,5.31816-3.614,8.09586-5.29216,3.68523-2.22642,6.13358-5.96455,8.81312-9.33471a129.00143,129.00143,0,0,1,13.4386-13.817c.75138,4.31038,3.4782,7.8499,6.68733,10.824s6.90841,5.36845,10.2439,8.20013c8.0786,6.85838,13.89583,16.1669,22.39215,22.50043a43.82885,43.82885,0,0,0,16.04862-8.0122l-3.30209-5.98141a3.94,3.94,0,0,0-1.24459-1.55282c-.93465-.575-2.13975-.27872-3.225-.44144-2.90082-.435-4.16771-3.784-5.306-6.48737-3.12491-7.42173-9.108-13.17993-14.21783-19.40381a98.00854,98.00854,0,0,1-9.99577-14.72284c-1.71652-3.10162-3.288-6.33107-5.61746-9.00321s-5.59358-4.773-9.1385-4.78051c-3.13222-.00662-6.02122,1.58355-8.71422,3.18308a230.47679,230.47679,0,0,0-23.63018,16.09894c-3.94376,3.0617-7.86306,6.29645-12.48933,8.17393-1.94748.79035-4.00044,1.33052-5.86924,2.29223-3.27313,1.6844-5.75721,4.53435-8.43128,7.06415C566.27712,311.89225,553.219,317.73841,540.09786,318.2059Z"
|
||||
transform="translate(-63.054 -157.8845)" fill="#3f3d56"/>
|
||||
<path d="M588.3737,253.98251a23.77444,23.77444,0,0,1-1.73379,8.03335,10.04492,10.04492,0,0,1-5.76772,5.57269,12.37513,12.37513,0,0,1-5.62306.18249,10.88232,10.88232,0,0,1-4.58151-1.56071c-2.16484-1.48837-3.24415-4.14413-3.63748-6.74325-.39333-2.596-.21714-5.24857-.46885-7.86342a42.94439,42.94439,0,0,0-1.202-6.25549c-.16993-.68282-.343-1.36248-.51294-2.04216-.16674-.67967-.33037-1.35935-.48141-2.039-.13847-.63878-.26745-1.28068-.37761-1.92574-.09123-.54436-.173-1.09189-.23285-1.64255a18.42329,18.42329,0,0,0-.80867-4.81118,14.60727,14.60727,0,0,0-1.68659-2.854c-.28635-.40906-.56326-.81811-.81815-1.24292a5.88984,5.88984,0,0,1-.97226-3.74763,3.286,3.286,0,0,1,.14788-.601c.02516-.07552.05347-.151.085-.2234A1.80187,1.80187,0,0,0,560.932,223.07a3.43341,3.43341,0,0,0-.14788-1.77783,11.31808,11.31808,0,0,0-.95974-2.28761c-.2643-.47829-1.16108-1.34046-1.16738-1.888-.0126-1.10132,2.13972-1.98867,3.01134-2.42291a16.79623,16.79623,0,0,1,8.59657-1.74323c1.90369.129,3.9679.71428,5.0189,2.30962.944,1.438.81807,3.30081,1.22085,4.97169a1.47068,1.47068,0,0,0,.29892.66393,1.34135,1.34135,0,0,0,.73948.33982,4.54948,4.54948,0,0,0,1.416.05666h.00315a2.93138,2.93138,0,0,0,.37128-.05351,4.957,4.957,0,0,0,2.03271-.8779q.58531-.15576,1.18-.25488a.25112.25112,0,0,0,.04725-.00945c1.57646,4.97482,1.781,10.30836,3.07111,15.37444.63874,2.52044,1.55442,5.00943,1.6834,7.60225.00945.11327.0126.2297.01575.34612.0189.83386-.04717,1.674-.0126,2.50472a6.981,6.981,0,0,0,.12591,1.1139,15.61121,15.61121,0,0,0,.52546,1.74325l.00945.02831c.05977.18251.11643.36817.16363.55381.03457.1353.06607.26747.09127.40277l.00311.00943A14.93754,14.93754,0,0,1,588.3737,253.98251Z"
|
||||
transform="translate(-63.054 -157.8845)" fill="#fbbebe"/>
|
||||
<circle cx="503.23669" cy="44.99678" r="18.56511" fill="#fbbebe"/>
|
||||
<path d="M684.15711,304.03278a30.445,30.445,0,0,0-5.236-14.10317q.72216,4.29513,1.44748,8.58714a3.214,3.214,0,0,1-3.36688-1.03523,10.33663,10.33663,0,0,1-1.76529-3.27565,67.46571,67.46571,0,0,0-8.2095-14.73567c-11.81876-.98489-23.50223-5.88418-33.89555-11.59532-10.39643-5.708-20.12582-12.5519-30.38382-18.50217a43.57346,43.57346,0,0,0-5.54436-2.832c-3.20954-1.287-6.81242-1.95406-9.85526-3.46759-.2045-.1007-.409-.20767-.61043-.31781a12.57834,12.57834,0,0,1-1.94459-1.30584,10.34363,10.34363,0,0,1-.93139-.8559,20.35115,20.35115,0,0,1-3.55886-5.95341c-1.63308-3.61232-2.21524-7.97041-3.84517-11.58274a11.20292,11.20292,0,0,1,2.50156-1.76525h.00315c.13213-.06924.2643-.13532.39962-.19824a11.9404,11.9404,0,0,1,2.00437-.73317q.58531-.15576,1.18-.25488a.25112.25112,0,0,0,.04725-.00945,11.56564,11.56564,0,0,1,5.49085.43424c2.58652.87477,4.76711,2.62115,6.94148,4.27313a114.02006,114.02006,0,0,1,10.14787,8.04908c1.79357,1.718,3.4298,3.606,5.35868,5.16676a42.14393,42.14393,0,0,0,5.05662,3.35116q15.65613,9.32658,31.31525,18.65005c3.53365,2.1051,7.07046,4.21019,10.52553,6.438,5.24855,3.38578,10.30828,7.05474,15.36493,10.72057q4.46978,3.23787,8.93647,6.47889a9.72771,9.72771,0,0,1,2.533,2.3411,8.4724,8.4724,0,0,1,1.12337,3.433A31.3874,31.3874,0,0,1,684.15711,304.03278Z"
|
||||
transform="translate(-63.054 -157.8845)" fill="#fbbebe"/>
|
||||
<path d="M592.97726,267.9441c-1.25235,5.61674-6.92888,9.012-9.89617,13.94586-3.68784,6.12335-2.18378,13.241-.79922,20.25484q-3.79485,3.27095-7.59285,6.54186c-1.39708,1.19886-2.79417,2.404-4.29827,3.46444a57.35064,57.35064,0,0,1-6.85966,3.93956q-3.3606,1.72752-6.72119,3.45814a32.1282,32.1282,0,0,1-6.57961,2.78793c-4.41473,1.13278-9.10318.33982-13.4707-.97232a6.08761,6.08761,0,0,1-1.47264-.601,2.39351,2.39351,0,0,1-.69854-.63248,3.91067,3.91067,0,0,1-.44365-2.53933c.44365-7.35052,2.24036-14.54686,4.03081-21.68971a85.2598,85.2598,0,0,1,3.84832-12.57708,85.0766,85.0766,0,0,1,5.41538-10.151,68.36751,68.36751,0,0,1,7.92948-11.51353,18.47881,18.47881,0,0,0,3.67525-4.73882c1.11706-2.54876.686-5.472.91252-8.24732a17.14844,17.14844,0,0,1,1.63312-6.0069v-.00315a17.09326,17.09326,0,0,1,1.74321-2.88232q.45788,1.06671.91568,2.13027.30209.69855.59783,1.394.38706.89679.7678,1.78728,1.09973,2.55823,2.19637,5.11327a21.58968,21.58968,0,0,0,3.33538,5.944,6.49923,6.49923,0,0,0,11.12337-.85275,21.26125,21.26125,0,0,0,2.27185-6.0132,19.21547,19.21547,0,0,0,.25175-7.83509c-.75835-5.00945-2.88862-10.12585-4.43678-14.77972a14.94511,14.94511,0,0,1-1.07927-4.871,3.35144,3.35144,0,0,1,.05662-.56011c.00945-.04719.0189-.09754.02834-.14473a11.9404,11.9404,0,0,1,2.00437-.73317q.58531-.15576,1.18-.25488,2.04378,11.06355,4.09377,22.12709c.0315.17307.0661.34613.09756.52234.19509,1.05726.39333,2.11454.61358,3.16865.19828.95657.41223,1.91.65137,2.85715l.00945.02831c.08182.321.16678.63877.2549.95658l.00311.00943c.2423.86848.5129,1.73065.81811,2.58024C590.93825,257.47528,594.16355,262.62946,592.97726,267.9441Z"
|
||||
transform="translate(-63.054 -157.8845)" fill="#16a085"/>
|
||||
<path d="M668.32144,346.87707a6.58269,6.58269,0,0,0,.61,3.14328c1.16192,2.12353,3.94981,2.60625,6.36228,2.80484a188.37688,188.37688,0,0,0,42.2657-1.28774,4.88565,4.88565,0,0,0,2.15136-.66766c1.98985-1.39509.76329-4.7951-1.40951-5.88355s-4.75126-.82614-7.1353-1.29748a22.47912,22.47912,0,0,1-6.67794-2.89617q-7.25234-4.16669-14.293-8.68808c-2.79453-1.79464-6.09272-3.70993-9.23987-2.64587C672.43,332.34264,668.26533,337.68065,668.32144,346.87707Z"
|
||||
transform="translate(-63.054 -157.8845)" fill="#3f3d56"/>
|
||||
<path d="M564.43732,240.87367v.00315c-.022.13215-.04406.26116-.07237.39018-.0346.214-.07551.43108-.11642.645-.39018,1.99812-.86847,3.98678-1.41913,5.96287-1.5104,5.45939-3.53366,10.83069-5.54121,16.12332q-8.08055,21.28692-16.16423,42.577c-1.35936,3.57457-2.71554,7.15228-4.26054,10.65448-.516,1.16741-1.04782,2.34424-1.57647,3.53368-1.89427,4.25737-3.713,8.65322-4.31716,13.18436a27.44976,27.44976,0,0,0-.19194,9.04027c.60416,2.97042,2.40718,5.8716,5.22969,6.96977,1.37823.53808,3.35113,1.25865,2.97355,2.69037-.2045.78665-1.09817,1.17055-1.90057,1.3027a7.31234,7.31234,0,0,1-5.966-1.718c-1.50725-1.33732-2.66518-3.41725-4.66959-3.64065-1.38767-.151-2.66518.67966-3.93643,1.26178-5.18564,2.36942-11.22719.71114-16.674-.9723.42794-2.20579,2.64318-3.65953,4.84267-4.10006,2.19949-.44367,4.47449-.129,6.718-.18879a3.50958,3.50958,0,0,0,2.04216-.52549,3.70545,3.70545,0,0,0,1.10132-1.88169,78.96356,78.96356,0,0,0,3.21273-13.14661c.7237-4.66645,1.02581-9.40527,2.05787-14.01507.80241-3.59661,2.0422-7.07991,3.10572-10.61044a224.68238,224.68238,0,0,0,5.0598-22.07674,78.02019,78.02019,0,0,0,1.42543-9.36751c.17935-2.6117.09438-5.236.34609-7.83826a60.8877,60.8877,0,0,1,2.11141-9.99683q1.44427-5.34769,2.88547-10.68911c1.42544-5.2706,2.95465-10.74572,6.567-14.84264a13.96159,13.96159,0,0,1,10.02834-4.78915,9.8819,9.8819,0,0,1,2.13027.22969c.11639.02831.23285.05664.34923.0881a8.63447,8.63447,0,0,1,2.17437.89995c1.11388-.708,1.68025-.45942,2.41974.63246a6.97319,6.97319,0,0,1,.88107,3.79485A52.42378,52.42378,0,0,1,564.43732,240.87367Z"
|
||||
transform="translate(-63.054 -157.8845)" fill="#fbbebe"/>
|
||||
<path d="M565.66136,245.0461l-.0472.04719-.25486.25488-2.5299,2.52675-1.23976-5.20767-4.25109-17.854a9.8819,9.8819,0,0,1,2.13027.22969,3.286,3.286,0,0,1,.14788-.601l.20135.68911,1.44118,4.90245,2.72811,9.30773.45,1.53241v.00315Z"
|
||||
transform="translate(-63.054 -157.8845)" fill="#16a085"/>
|
||||
<path d="M581.71523,188.0873a12.58165,12.58165,0,0,1-3.70049,8.89583,12.31392,12.31392,0,0,1-1.36008,1.17634,12.52812,12.52812,0,0,1-7.53567,2.52415H554.023a12.5902,12.5902,0,0,1,0-25.18037h15.096A12.62919,12.62919,0,0,1,581.71523,188.0873Z"
|
||||
transform="translate(-63.054 -157.8845)" fill="#2f2e41"/>
|
||||
<circle cx="532.81499" cy="18.25044" r="12.90118" fill="#2f2e41"/>
|
||||
<path d="M595.55433,163.23377c-.15825,0-.31505.00628-.472.01193a12.89776,12.89776,0,0,1,0,25.77849c.15694.00565.31374.01193.472.01193a12.90117,12.90117,0,1,0,0-25.80235Z"
|
||||
transform="translate(-63.054 -157.8845)" opacity="0.1"/>
|
||||
<path d="M534.19508,163.23377c.15825,0,.31505.00628.472.01193a12.89776,12.89776,0,0,0,0,25.77849c-.157.00565-.31375.01193-.472.01193a12.90118,12.90118,0,0,1,0-25.80235Z"
|
||||
transform="translate(-63.054 -157.8845)" opacity="0.1"/>
|
||||
<path d="M576.65466,198.15947a12.52812,12.52812,0,0,1-7.53567,2.52415H554.023a12.52833,12.52833,0,0,1-7.53574-2.52415Z"
|
||||
transform="translate(-63.054 -157.8845)" opacity="0.1"/>
|
||||
<path d="M674.13958,291.64042s3.25228,9.37161,6.229,6.87633L677.996,286.26693Z"
|
||||
transform="translate(-63.054 -157.8845)" fill="#fbbebe"/>
|
||||
<path d="M1069.91781,577.43414a20.81252,20.81252,0,1,0,2.7716-39.91524l.52093,10.7122-5.06814-9.18045a20.734,20.734,0,0,0-10.68367,11.72261,20.40847,20.40847,0,0,0-1.19713,5.62986A20.80856,20.80856,0,0,0,1069.91781,577.43414Z"
|
||||
transform="translate(-63.054 -157.8845)" fill="#57b894"/>
|
||||
<path d="M1094.99516,701.67756c-1.78906-9.11027,5.9633-17.1868,13.62086-22.43651s16.605-10.40779,19.21775-19.31684c3.755-12.80387-7.43-24.52981-16.13564-34.64176a125.30044,125.30044,0,0,1-16.52359-24.55738c-1.81107-3.5325-3.47558-7.22528-3.95221-11.16626-.68641-5.67546,1.13693-11.32309,2.9739-16.73673q9.17925-27.05169,19.62843-53.65005"
|
||||
transform="translate(-63.054 -157.8845)" fill="none" stroke="#3f3d56" stroke-miterlimit="10"/>
|
||||
<path d="M1070.77493,574.6762a20.81252,20.81252,0,1,0,2.7716-39.91524l.52093,10.7122-5.06815-9.18045a20.734,20.734,0,0,0-10.68366,11.72261,20.40847,20.40847,0,0,0-1.19713,5.62986A20.80855,20.80855,0,0,0,1070.77493,574.6762Z"
|
||||
transform="translate(-63.054 -157.8845)" fill="none" stroke="#3f3d56" stroke-miterlimit="10"/>
|
||||
<path d="M1092.45136,515.47266a20.78819,20.78819,0,0,1,14.97993-13.19764l1.71361,10.18378,3.177-10.69566a20.81,20.81,0,1,1-19.87057,13.70952Z"
|
||||
transform="translate(-63.054 -157.8845)" fill="#57b894"/>
|
||||
<path d="M1093.59418,511.7954a20.7882,20.7882,0,0,1,14.97993-13.19763l1.71361,10.18378,3.177-10.69567a20.81,20.81,0,1,1-19.87057,13.70952Z"
|
||||
transform="translate(-63.054 -157.8845)" fill="none" stroke="#3f3d56" stroke-miterlimit="10"/>
|
||||
<path d="M1108.04474,625.48885a20.81,20.81,0,0,0,18.419-37.02267l-2.44121,8.21926-1.73105-10.30382a.36183.36183,0,0,0-.053-.0201,20.81113,20.81113,0,1,0-14.1938,39.12733Z"
|
||||
transform="translate(-63.054 -157.8845)" fill="#57b894"/>
|
||||
<path d="M1109.035,621.76417a20.81,20.81,0,0,0,18.419-37.02267l-2.44121,8.21926-1.73105-10.30382a.3621.3621,0,0,0-.053-.0201,20.81113,20.81113,0,1,0-14.1938,39.12733Z"
|
||||
transform="translate(-63.054 -157.8845)" fill="none" stroke="#3f3d56" stroke-miterlimit="10"/>
|
||||
<path d="M1086.37782,660.05148a20.80131,20.80131,0,1,0,4.01058-16.29737l9.27267,13.95654-12.66994-7.40768A20.61638,20.61638,0,0,0,1086.37782,660.05148Z"
|
||||
transform="translate(-63.054 -157.8845)" fill="#57b894"/>
|
||||
<path d="M1087.23494,657.29354a20.80131,20.80131,0,1,0,4.01058-16.29737l9.27267,13.95655-12.66994-7.40769A20.61626,20.61626,0,0,0,1087.23494,657.29354Z"
|
||||
transform="translate(-63.054 -157.8845)" fill="none" stroke="#3f3d56" stroke-miterlimit="10"/>
|
||||
<path d="M72.06146,628.13325a13.67421,13.67421,0,1,0,1.821-26.225l.34227,7.03811-3.32987-6.03172a13.62263,13.62263,0,0,0-7.01936,7.702,13.40883,13.40883,0,0,0-.78654,3.69893A13.6716,13.6716,0,0,0,72.06146,628.13325Z"
|
||||
transform="translate(-63.054 -157.8845)" fill="#57b894"/>
|
||||
<path d="M88.53774,709.76344c-1.17545-5.98561,3.918-11.292,8.94915-14.7412s10.90978-6.8381,12.62642-12.69151c2.46711-8.41238-4.88167-16.11653-10.60142-22.76027A82.32442,82.32442,0,0,1,88.6556,643.43581a22.20962,22.20962,0,0,1-2.59668-7.33643c-.451-3.72888.747-7.43947,1.95391-10.99634q6.03093-17.77346,12.89623-35.24906"
|
||||
transform="translate(-63.054 -157.8845)" fill="none" stroke="#3f3d56" stroke-miterlimit="10"/>
|
||||
<path d="M72.62461,626.32123a13.6742,13.6742,0,1,0,1.821-26.225l.34227,7.03812L71.458,601.10258a13.62262,13.62262,0,0,0-7.01936,7.702,13.40912,13.40912,0,0,0-.78654,3.69892A13.67158,13.67158,0,0,0,72.62461,626.32123Z"
|
||||
transform="translate(-63.054 -157.8845)" fill="none" stroke="#3f3d56" stroke-miterlimit="10"/>
|
||||
<path d="M86.86641,587.42343a13.65822,13.65822,0,0,1,9.84209-8.67109l1.12587,6.69093,2.08737-7.02725a13.67252,13.67252,0,1,1-13.05533,9.00741Z"
|
||||
transform="translate(-63.054 -157.8845)" fill="#57b894"/>
|
||||
<path d="M87.61727,585.0074a13.65822,13.65822,0,0,1,9.84209-8.67108l1.12587,6.69093L100.6726,576a13.67252,13.67252,0,1,1-13.05533,9.0074Z"
|
||||
transform="translate(-63.054 -157.8845)" fill="none" stroke="#3f3d56" stroke-miterlimit="10"/>
|
||||
<path d="M97.11155,659.70607a13.67255,13.67255,0,0,0,12.10164-24.32457l-1.60392,5.4002-1.13733-6.76979a.238.238,0,0,0-.0348-.0132,13.67329,13.67329,0,1,0-9.32559,25.70736Z"
|
||||
transform="translate(-63.054 -157.8845)" fill="#57b894"/>
|
||||
<path d="M97.76214,657.25889a13.67255,13.67255,0,0,0,12.10164-24.32457l-1.60392,5.4002-1.13733-6.7698a.238.238,0,0,0-.0348-.0132,13.67329,13.67329,0,1,0-9.32559,25.70737Z"
|
||||
transform="translate(-63.054 -157.8845)" fill="none" stroke="#3f3d56" stroke-miterlimit="10"/>
|
||||
<path d="M82.876,682.41435a13.66684,13.66684,0,1,0,2.635-10.70767l6.09231,9.16971-8.32438-4.867A13.54535,13.54535,0,0,0,82.876,682.41435Z"
|
||||
transform="translate(-63.054 -157.8845)" fill="#57b894"/>
|
||||
<path d="M83.43913,680.60233a13.66684,13.66684,0,1,0,2.635-10.70767l6.09231,9.16971-8.32439-4.867A13.54535,13.54535,0,0,0,83.43913,680.60233Z"
|
||||
transform="translate(-63.054 -157.8845)" fill="none" stroke="#3f3d56" stroke-miterlimit="10"/>
|
||||
<ellipse cx="480.946" cy="319.1155" rx="17" ry="22" fill="#2f2e41"/>
|
||||
<ellipse cx="573.446" cy="319.6155" rx="17" ry="22" fill="#2f2e41"/>
|
||||
<path d="M623.5,542.5c0,9.94-13.88,18-31,18s-31-8.06-31-18c0-8.61,10.41-15.81,24.32-17.57a50.10353,50.10353,0,0,1,6.68-.43,50.69869,50.69869,0,0,1,11.13,1.2C615.25,528.29,623.5,534.84,623.5,542.5Z"
|
||||
transform="translate(-63.054 -157.8845)" fill="#2f2e41"/>
|
||||
<ellipse cx="484.946" cy="314.1155" rx="17" ry="22" fill="none" stroke="#3f3d56" stroke-miterlimit="10"/>
|
||||
<ellipse cx="577.446" cy="314.6155" rx="17" ry="22" fill="none" stroke="#3f3d56" stroke-miterlimit="10"/>
|
||||
<ellipse cx="533.446" cy="379.6155" rx="31" ry="18" fill="none" stroke="#3f3d56" stroke-miterlimit="10"/>
|
||||
<path d="M604,527.2a4.93658,4.93658,0,0,1-1.32,3.392A4.33873,4.33873,0,0,1,599.5,532h-10a4.66433,4.66433,0,0,1-4.5-4.8,4.90458,4.90458,0,0,1,.82-2.74134A47.02,47.02,0,0,1,592.5,524a47.66454,47.66454,0,0,1,11.13,1.28A5.06656,5.06656,0,0,1,604,527.2Z"
|
||||
transform="translate(-63.054 -157.8845)" fill="#fff"/>
|
||||
<circle cx="484.946" cy="308.1155" r="5" fill="#fff"/>
|
||||
<circle cx="577.946" cy="308.1155" r="5" fill="#fff"/>
|
||||
<circle cx="582.946" cy="355.1155" r="5" fill="#16a085" opacity="0.3"/>
|
||||
<circle cx="460.946" cy="355.1155" r="5" fill="#16a085" opacity="0.3"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 9.8 KiB After Width: | Height: | Size: 21 KiB |
@ -1,277 +1,75 @@
|
||||
<svg id="mainImage_create" data-name="mainImage" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 171.2 81.5">
|
||||
<style id="svgStyle">
|
||||
@import url('https://fonts.googleapis.com/css?family=Merriweather');
|
||||
.changeColor {
|
||||
fill: #4f86ed;
|
||||
}
|
||||
|
||||
#title {
|
||||
font-size: 50%;
|
||||
font-family: 'Merriweather', serif;
|
||||
}
|
||||
|
||||
.cls-1 {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.cls-7 {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.cls-2 {
|
||||
fill: #fff;
|
||||
}
|
||||
|
||||
.cls-10,
|
||||
.cls-11,
|
||||
.cls-12,
|
||||
.cls-14,
|
||||
.cls-16,
|
||||
.cls-3 {
|
||||
fill: none;
|
||||
}
|
||||
|
||||
.cls-3 {
|
||||
stroke: #5c7690;
|
||||
}
|
||||
|
||||
.cls-10,
|
||||
.cls-11,
|
||||
.cls-12,
|
||||
.cls-3 {
|
||||
stroke-miterlimit: 10;
|
||||
}
|
||||
|
||||
.cls-14,
|
||||
.cls-15,
|
||||
.cls-16,
|
||||
.cls-3 {
|
||||
stroke-width: 0.5px;
|
||||
}
|
||||
|
||||
.cls-4 {
|
||||
fill: #ffe1d9;
|
||||
}
|
||||
|
||||
.cls-5 {
|
||||
fill: #ffcfbf;
|
||||
}
|
||||
|
||||
.cls-6 {
|
||||
fill: #fecbb6;
|
||||
}
|
||||
|
||||
.cls-9 {
|
||||
fill: #fecb02;
|
||||
}
|
||||
|
||||
.cls-10,
|
||||
.cls-12 {
|
||||
stroke: #d26f51;
|
||||
}
|
||||
|
||||
.cls-10,
|
||||
.cls-11 {
|
||||
stroke-width: 0.38px;
|
||||
}
|
||||
|
||||
.cls-11 {
|
||||
stroke: #000;
|
||||
}
|
||||
|
||||
.cls-12 {
|
||||
stroke-width: 0.19px;
|
||||
}
|
||||
|
||||
.cls-13 {
|
||||
opacity: 0.45;
|
||||
}
|
||||
|
||||
.cls-14,
|
||||
.cls-15,
|
||||
.cls-16 {
|
||||
stroke: #b0bec5;
|
||||
stroke-linejoin: round;
|
||||
}
|
||||
|
||||
.cls-15 {
|
||||
fill: #edf0f2;
|
||||
}
|
||||
|
||||
.cls-16 {
|
||||
stroke-linecap: round;
|
||||
}
|
||||
|
||||
.cls-17 {
|
||||
font-family: 'PT Sans', sans-serif;
|
||||
font-size: 49.87px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.cls-18 {
|
||||
fill: #fffdbb;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
|
||||
/*---------------------------
|
||||
SVG Animate
|
||||
---------------------------*/
|
||||
.earMove {
|
||||
transition: all ease-in-out 2s;
|
||||
transform-origin: 50% 50%;
|
||||
animation: earmove 1.5s linear infinite alternate;
|
||||
}
|
||||
|
||||
.faceMove {
|
||||
transition: all ease-in-out 2s;
|
||||
transform-origin: 50% 50%;
|
||||
animation: move 1.5s linear infinite alternate;
|
||||
}
|
||||
|
||||
.neckMove {
|
||||
transition: all ease-in-out 2s;
|
||||
transform-origin: 50% 50%;
|
||||
animation: neck 1.5s linear infinite alternate;
|
||||
}
|
||||
|
||||
|
||||
@keyframes earmove {
|
||||
0% {
|
||||
transform: translateX(-0.3px) translateY(0.6px);
|
||||
}
|
||||
30% {
|
||||
transform: translateX(-0.3px) translateY(0.6px);
|
||||
}
|
||||
|
||||
60% {
|
||||
transform: translateX(-0.7px) translateY(0px);
|
||||
}
|
||||
|
||||
70% {
|
||||
transform: translateX(-0.7px) translateY(-0.3px);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(-0.7px) translateY(-0.3px);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes move {
|
||||
0% {
|
||||
transform: translateX(-0.3px) translateY(0.6px);
|
||||
}
|
||||
30% {
|
||||
transform: translateX(-0.3px) translateY(0.6px);
|
||||
}
|
||||
|
||||
60% {
|
||||
transform: translateX(2px) translateY(0px);
|
||||
}
|
||||
|
||||
70% {
|
||||
transform: translateX(2px) translateY(-0.3px);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(2px) translateY(-0.3px);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes neck {
|
||||
0% {
|
||||
transform: translateY(0.7px);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(0.7px);
|
||||
}
|
||||
100% {
|
||||
transform: translateY(0px);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<path id="c-1" class="changeColor cls-1"
|
||||
d="M46.62,52.5c5.78,4.9,21.14,8.4,39.19,8.4s33.41-3.5,39.19-8.4c-5.78-4.9-21.14-8.4-39.19-8.4S52.41,47.6,46.62,52.5Z"
|
||||
style="fill: rgb(255, 5, 18);"></path>
|
||||
<path class="cls-2"
|
||||
d="M99.73,47.71H68.65a7.13,7.13,0,0,0-7.13,7.13V60a152.58,152.58,0,0,0,24.3,1.83,157.87,157.87,0,0,0,21.05-1.35V54.84A7.13,7.13,0,0,0,99.73,47.71Z"></path>
|
||||
<path class="cls-3"
|
||||
d="M123.56,55.81C115,58.94,101.27,61,85.81,61c-26,0-47-5.71-47-12.76,0-3.45,5.05-6.58,13.25-8.88"></path>
|
||||
<path class="cls-3" d="M55.37,38.47a140,140,0,0,1,30.44-3c26,0,47,5.71,47,12.76,0,2.4-2.44,4.65-6.69,6.57"></path>
|
||||
<path class="cls-3" d="M53.41,38.95l.94-.24"></path>
|
||||
<path class="cls-4"
|
||||
d="M91.68,47.71l-.75-11.2L79.15,43.84l-1.69,3.87H75.79c0,3.36,3.76,6.08,8.4,6.08s8.4-2.72,8.4-6.08Z"></path>
|
||||
<path class="cls-5 neckMove"
|
||||
d="M78,46.53a27.19,27.19,0,0,0,6.41.82c3.1,0,7.11-2.19,7.11-2.19l-.42-6.2L79.15,43.84Z"></path>
|
||||
<polygon class="earMove" points="92.59 32.22 92.59 28.5 76.77 27.71 76.77 32.22 92.59 32.22"></polygon>
|
||||
<circle class="cls-6 earMove" cx="78.06" cy="34.04" r="2.47"></circle>
|
||||
<path class="cls-4"
|
||||
d="M81.74,57.06,60.63,49.72h0A6.72,6.72,0,1,0,57.7,62.49H93.25C93.25,56.78,81.74,57.06,81.74,57.06Z"></path>
|
||||
<path class="cls-4"
|
||||
d="M77.46,25H90.92a0,0,0,0,1,0,0V39.38a6.73,6.73,0,0,1-6.73,6.73h0a6.73,6.73,0,0,1-6.73-6.73V25A0,0,0,0,1,77.46,25Z"></path>
|
||||
<rect id="c-2" class="changeColor cls-7" x="74.82" y="26.48" width="19.14" height="2.45"
|
||||
transform="translate(1.29 -3.65) rotate(2.49)" style="fill: rgb(255, 5, 18);"></rect>
|
||||
<path id="c-3" class="changeColor cls-7"
|
||||
d="M84.36,18.69h.5a7.8,7.8,0,0,1,7.8,7.8v0a0,0,0,0,1,0,0H76.56a0,0,0,0,1,0,0v0A7.8,7.8,0,0,1,84.36,18.69Z"
|
||||
transform="translate(1.06 -3.66) rotate(2.49)" style="fill: rgb(255, 5, 18);"></path>
|
||||
<polygon id="c-4" class="changeColor cls-8"
|
||||
points="82.44 23.89 92.18 24.32 92.59 24.34 92.48 26.84 80.96 26.33 82.44 23.89"
|
||||
style="fill: rgb(255, 5, 18);"></polygon>
|
||||
<circle class="cls-9 faceMove" cx="78.72" cy="23.73" r="3.73"
|
||||
transform="translate(51.58 101.34) rotate(-87.51)"></circle>
|
||||
<circle class="cls-2 faceMove" cx="78.72" cy="23.73" r="2.36"
|
||||
transform="translate(51.58 101.34) rotate(-87.51)"></circle>
|
||||
<circle class="cls-4 earMove" cx="90.92" cy="34.04" r="2.47"></circle>
|
||||
<path class="cls-4"
|
||||
d="M112.2,53l-9.87-21.92-3-5.48-11.86-.22,7.42,3.35H91.55l5.82,4.58,2,22.26h0A6.72,6.72,0,1,0,112.2,53Z"></path>
|
||||
<ellipse class="faceMove" cx="80.09" cy="33.12" rx="0.53" ry="0.59"></ellipse>
|
||||
<ellipse class="faceMove" cx="86.34" cy="33.12" rx="0.53" ry="0.59"></ellipse>
|
||||
<polyline class="cls-10 faceMove" points="84.19 31.08 81.74 37.01 84.39 37.01"></polyline>
|
||||
<path class="cls-10 faceMove" d="M83.06,40.36a4,4,0,0,1,2.75-1"></path>
|
||||
<line class="cls-11 faceMove" x1="81.07" y1="30.33" x2="78.47" y2="30.58"></line>
|
||||
<line class="cls-11 faceMove" x1="86.34" y1="30.45" x2="88.15" y2="31.08"></line>
|
||||
<line class="cls-12" x1="106.86" y1="47.82" x2="110.99" y2="46.11"></line>
|
||||
<line class="cls-12" x1="107.43" y1="49.9" x2="111.55" y2="48.19"></line>
|
||||
<line class="cls-12" x1="107.99" y1="51.98" x2="112.11" y2="50.27"></line>
|
||||
<g class="cls-13">
|
||||
<rect class="cls-14" x="85.81" y="2.46" width="10.77" height="3.5"></rect>
|
||||
<rect class="cls-15" x="96.58" y="2.46" width="10.77" height="3.5"></rect>
|
||||
<rect class="cls-14" x="92.19" y="5.95" width="10.77" height="3.5"></rect>
|
||||
<line class="cls-16" x1="107.36" y1="5.95" x2="109.63" y2="5.95"></line>
|
||||
<line class="cls-16" x1="110.68" y1="5.95" x2="111.57" y2="5.95"></line>
|
||||
</g>
|
||||
<g class="cls-13">
|
||||
<rect class="cls-16" x="125" y="23.12" width="10.77" height="3.5"></rect>
|
||||
<rect class="cls-15" x="130.39" y="26.62" width="10.77" height="3.5"></rect>
|
||||
<rect class="cls-16" x="119.62" y="26.62" width="10.77" height="3.5"></rect>
|
||||
<line class="cls-16" x1="141.16" y1="26.62" x2="145.73" y2="26.62"></line>
|
||||
<line class="cls-16" x1="125" y1="23.12" x2="115.4" y2="23.12"></line>
|
||||
<line class="cls-16" x1="117.95" y1="26.62" x2="115.4" y2="26.62"></line>
|
||||
</g>
|
||||
<g class="cls-13">
|
||||
<rect class="cls-16" x="39.34" y="16.12" width="10.77" height="3.5"></rect>
|
||||
<rect class="cls-16" x="39.34" y="23.11" width="10.77" height="3.5"></rect>
|
||||
<rect class="cls-16" x="50.11" y="23.11" width="10.77" height="3.5"></rect>
|
||||
<rect class="cls-16" x="50.11" y="16.12" width="10.77" height="3.5"></rect>
|
||||
<rect class="cls-15" x="44" y="19.61" width="10.77" height="3.5"></rect>
|
||||
<rect class="cls-16" x="33.23" y="19.61" width="10.77" height="3.5"></rect>
|
||||
<line class="cls-16" x1="60.89" y1="19.61" x2="65.51" y2="19.61"></line>
|
||||
<line class="cls-16" x1="39.34" y1="16.12" x2="35.46" y2="16.12"></line>
|
||||
<line class="cls-16" x1="36.45" y1="26.61" x2="33.23" y2="26.61"></line>
|
||||
<line class="cls-16" x1="63.2" y1="23.11" x2="65.51" y2="23.11"></line>
|
||||
</g>
|
||||
<polyline class="cls-3" points="115.4 58.12 115.4 38.27 120.2 37.01"></polyline>
|
||||
<polyline class="cls-3" points="129.01 53.21 129.01 43.14 131.74 42.13"></polyline>
|
||||
<path class="cls-3" d="M115.4,42.13a53.27,53.27,0,0,1,8,2A42,42,0,0,1,129,47"></path>
|
||||
<path class="cls-3" d="M115.4,47.34a53.27,53.27,0,0,1,8,2A42,42,0,0,1,129,52.22"></path>
|
||||
<path class="cls-3" d="M115.4,52.56a53.27,53.27,0,0,1,8,2l1,.42"></path>
|
||||
<path class="cls-18 faceMove"
|
||||
d="M78.84,26.09l0-4.71L68.05,18.32a.91.91,0,0,0-.45-.13c-1.17,0-2.11,2.46-2.11,5.5s.95,5.5,2.11,5.5a.9.9,0,0,0,.44-.12Z"></path>
|
||||
<path class="cls-5" d="M57.7,62.49H93.25A3.67,3.67,0,0,0,92.92,61H53.43A6.69,6.69,0,0,0,57.7,62.49Z"></path>
|
||||
<path class="cls-12" d="M88.15,60.27s1.7.95,1.7,2.22"></path>
|
||||
<path class="cls-5" d="M101.81,61a6.68,6.68,0,0,0,8.51,0Z"></path>
|
||||
<polygon class="cls-5" points="90.92 30.25 77.46 29.69 77.46 28.64 90.92 29.22 90.92 30.25"></polygon>
|
||||
<text id="title" transform="matrix(1 0 0 1 44.7249 78)">Oops, something went wrong</text>
|
||||
</svg>
|
||||
|
||||
<!-- <h1 id="title">404 Page not found</h1> -->
|
||||
<!-- <button id="newBtn" class="btn" disabled>→ Back to Home</button> -->
|
||||
|
||||
<svg id="aa03ddf9-f8f2-4819-a4ce-be9b0a220741" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" width="1119.60911"
|
||||
height="699" viewBox="0 0 1119.60911 699">
|
||||
<title>server down</title>
|
||||
<circle cx="292.60911" cy="213" r="213" fill="#f2f2f2"/>
|
||||
<path d="M31.39089,151.64237c0,77.49789,48.6181,140.20819,108.70073,140.20819"
|
||||
transform="translate(-31.39089 -100.5)" fill="#2f2e41"/>
|
||||
<path d="M140.09162,291.85056c0-78.36865,54.255-141.78356,121.30372-141.78356"
|
||||
transform="translate(-31.39089 -100.5)" fill="#16a085"/>
|
||||
<path d="M70.77521,158.66768c0,73.61476,31.00285,133.18288,69.31641,133.18288"
|
||||
transform="translate(-31.39089 -100.5)" fill="#16a085"/>
|
||||
<path d="M140.09162,291.85056c0-100.13772,62.7103-181.16788,140.20819-181.16788"
|
||||
transform="translate(-31.39089 -100.5)" fill="#2f2e41"/>
|
||||
<path d="M117.22379,292.83905s15.41555-.47479,20.06141-3.783,23.713-7.2585,24.86553-1.95278,23.16671,26.38821,5.76263,26.5286-40.43935-2.711-45.07627-5.53549S117.22379,292.83905,117.22379,292.83905Z"
|
||||
transform="translate(-31.39089 -100.5)" fill="#a8a8a8"/>
|
||||
<path d="M168.224,311.78489c-17.40408.14042-40.43933-2.71094-45.07626-5.53548-3.53126-2.151-4.93843-9.86945-5.40926-13.43043-.32607.014-.51463.02-.51463.02s.97638,12.43276,5.61331,15.2573,27.67217,5.67589,45.07626,5.53547c5.02386-.04052,6.7592-1.82793,6.66391-4.47526C173.87935,310.756,171.96329,311.75474,168.224,311.78489Z"
|
||||
transform="translate(-31.39089 -100.5)" opacity="0.2"/>
|
||||
<ellipse cx="198.60911" cy="424.5" rx="187" ry="25.43993" fill="#3f3d56"/>
|
||||
<ellipse cx="198.60911" cy="424.5" rx="157" ry="21.35866" opacity="0.1"/>
|
||||
<ellipse cx="836.60911" cy="660.5" rx="283" ry="38.5" fill="#3f3d56"/>
|
||||
<ellipse cx="310.60911" cy="645.5" rx="170" ry="23.12721" fill="#3f3d56"/>
|
||||
<path d="M494,726.5c90,23,263-30,282-90" transform="translate(-31.39089 -100.5)" fill="none" stroke="#2f2e41"
|
||||
stroke-miterlimit="10" stroke-width="2"/>
|
||||
<path d="M341,359.5s130-36,138,80-107,149-17,172" transform="translate(-31.39089 -100.5)" fill="none"
|
||||
stroke="#2f2e41" stroke-miterlimit="10" stroke-width="2"/>
|
||||
<path d="M215.40233,637.78332s39.0723-10.82,41.47675,24.04449-32.15951,44.78287-5.10946,51.69566"
|
||||
transform="translate(-31.39089 -100.5)" fill="none" stroke="#2f2e41" stroke-miterlimit="10" stroke-width="2"/>
|
||||
<path d="M810.09554,663.73988,802.218,714.03505s-38.78182,20.60284-11.51335,21.20881,155.73324,0,155.73324,0,24.84461,0-14.54318-21.81478l-7.87756-52.719Z"
|
||||
transform="translate(-31.39089 -100.5)" fill="#2f2e41"/>
|
||||
<path d="M785.21906,734.69812c6.193-5.51039,16.9989-11.252,16.9989-11.252l7.87756-50.2952,113.9216.10717,7.87756,49.582c9.185,5.08711,14.8749,8.987,18.20362,11.97818,5.05882-1.15422,10.58716-5.44353-18.20362-21.38921l-7.87756-52.719-113.9216,3.02983L802.218,714.03506S769.62985,731.34968,785.21906,734.69812Z"
|
||||
transform="translate(-31.39089 -100.5)" opacity="0.1"/>
|
||||
<rect x="578.43291" y="212.68859" width="513.25314" height="357.51989" rx="18.04568" fill="#2f2e41"/>
|
||||
<rect x="595.70294" y="231.77652" width="478.71308" height="267.83694" fill="#3f3d56"/>
|
||||
<circle cx="835.05948" cy="223.29299" r="3.02983" fill="#f2f2f2"/>
|
||||
<path d="M1123.07694,621.32226V652.6628a18.04341,18.04341,0,0,1-18.04568,18.04568H627.86949A18.04341,18.04341,0,0,1,609.8238,652.6628V621.32226Z"
|
||||
transform="translate(-31.39089 -100.5)" fill="#2f2e41"/>
|
||||
<polygon
|
||||
points="968.978 667.466 968.978 673.526 642.968 673.526 642.968 668.678 643.417 667.466 651.452 645.651 962.312 645.651 968.978 667.466"
|
||||
fill="#2f2e41"/>
|
||||
<path d="M1125.828,762.03359c-.59383,2.539-2.83591,5.21743-7.90178,7.75032-18.179,9.08949-55.1429-2.42386-55.1429-2.42386s-28.4804-4.84773-28.4804-17.573a22.72457,22.72457,0,0,1,2.49658-1.48459c7.64294-4.04351,32.98449-14.02122,77.9177.42248a18.73921,18.73921,0,0,1,8.54106,5.59715C1125.07908,756.45353,1126.50669,759.15715,1125.828,762.03359Z"
|
||||
transform="translate(-31.39089 -100.5)" fill="#2f2e41"/>
|
||||
<path d="M1125.828,762.03359c-22.251,8.526-42.0843,9.1622-62.43871-4.975-10.26507-7.12617-19.59089-8.88955-26.58979-8.75618,7.64294-4.04351,32.98449-14.02122,77.9177.42248a18.73921,18.73921,0,0,1,8.54106,5.59715C1125.07908,756.45353,1126.50669,759.15715,1125.828,762.03359Z"
|
||||
transform="translate(-31.39089 -100.5)" opacity="0.1"/>
|
||||
<ellipse cx="1066.53846" cy="654.13477" rx="7.87756" ry="2.42386" fill="#f2f2f2"/>
|
||||
<circle cx="835.05948" cy="545.66686" r="11.51335" fill="#f2f2f2"/>
|
||||
<polygon points="968.978 667.466 968.978 673.526 642.968 673.526 642.968 668.678 643.417 667.466 968.978 667.466"
|
||||
opacity="0.1"/>
|
||||
<rect x="108.60911" y="159" width="208" height="242" fill="#2f2e41"/>
|
||||
<rect x="87.60911" y="135" width="250" height="86" fill="#3f3d56"/>
|
||||
<rect x="87.60911" y="237" width="250" height="86" fill="#3f3d56"/>
|
||||
<rect x="87.60911" y="339" width="250" height="86" fill="#3f3d56"/>
|
||||
<rect x="271.60911" y="150" width="16" height="16" fill="#16a085" opacity="0.4"/>
|
||||
<rect x="294.60911" y="150" width="16" height="16" fill="#16a085" opacity="0.8"/>
|
||||
<rect x="317.60911" y="150" width="16" height="16" fill="#16a085"/>
|
||||
<rect x="271.60911" y="251" width="16" height="16" fill="#16a085" opacity="0.4"/>
|
||||
<rect x="294.60911" y="251" width="16" height="16" fill="#16a085" opacity="0.8"/>
|
||||
<rect x="317.60911" y="251" width="16" height="16" fill="#16a085"/>
|
||||
<rect x="271.60911" y="352" width="16" height="16" fill="#16a085" opacity="0.4"/>
|
||||
<rect x="294.60911" y="352" width="16" height="16" fill="#16a085" opacity="0.8"/>
|
||||
<rect x="317.60911" y="352" width="16" height="16" fill="#16a085"/>
|
||||
<circle cx="316.60911" cy="538" r="79" fill="#2f2e41"/>
|
||||
<rect x="280.60911" y="600" width="24" height="43" fill="#2f2e41"/>
|
||||
<rect x="328.60911" y="600" width="24" height="43" fill="#2f2e41"/>
|
||||
<ellipse cx="300.60911" cy="643.5" rx="20" ry="7.5" fill="#2f2e41"/>
|
||||
<ellipse cx="348.60911" cy="642.5" rx="20" ry="7.5" fill="#2f2e41"/>
|
||||
<circle cx="318.60911" cy="518" r="27" fill="#fff"/>
|
||||
<circle cx="318.60911" cy="518" r="9" fill="#3f3d56"/>
|
||||
<path d="M271.36733,565.03228c-6.37889-28.56758,14.01185-57.43392,45.544-64.47477s62.2651,10.41,68.644,38.9776-14.51861,39.10379-46.05075,46.14464S277.74622,593.59986,271.36733,565.03228Z"
|
||||
transform="translate(-31.39089 -100.5)" fill="#16a085"/>
|
||||
<ellipse cx="417.21511" cy="611.34365" rx="39.5" ry="12.40027"
|
||||
transform="translate(-238.28665 112.98044) rotate(-23.17116)" fill="#2f2e41"/>
|
||||
<ellipse cx="269.21511" cy="664.34365" rx="39.5" ry="12.40027"
|
||||
transform="translate(-271.07969 59.02084) rotate(-23.17116)" fill="#2f2e41"/>
|
||||
<path d="M394,661.5c0,7.732-19.90861,23-42,23s-43-14.268-43-22,20.90861-6,43-6S394,653.768,394,661.5Z"
|
||||
transform="translate(-31.39089 -100.5)" fill="#fff"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 9.8 KiB After Width: | Height: | Size: 6.8 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 5.5 KiB |
Before Width: | Height: | Size: 9.1 KiB |
Before Width: | Height: | Size: 14 KiB |