Merge branch 'master' into feature/config

This commit is contained in:
Jannik 2020-06-16 20:44:10 +02:00
commit 5a0b303601
Signed by: Seil0
GPG Key ID: E8459F3723C52C24
15 changed files with 473 additions and 25 deletions

View File

@ -0,0 +1,37 @@
package org.hso.ecommerce.components;
import org.hso.ecommerce.entities.booking.PaymentMethod;
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.Component;
import java.sql.Timestamp;
import java.util.Optional;
import javax.annotation.PostConstruct;
@Component
public class AdminInitializer {
@Autowired
private final UserRepository userRepository = null;
@PostConstruct
public void init() {
Optional<Integer> numberOfEmployees = userRepository.numberOfEmployees();
if (numberOfEmployees.orElse(0) == 0) {
// create first admin user
User firstAdmin = new User();
firstAdmin.created = new Timestamp(System.currentTimeMillis());
firstAdmin.name = "admin";
firstAdmin.defaultPayment = new PaymentMethod();
firstAdmin.defaultPayment.creditCardNumber = ""; //set empty number
firstAdmin.email = "admin";
firstAdmin.isActive = true;
firstAdmin.isEmployee = true;
firstAdmin.setPassword("admin");
userRepository.save(firstAdmin); //save to DB
}
}
}

View File

@ -11,8 +11,8 @@ import javax.annotation.PostConstruct;
@Component
public class SlotInitializer {
@Autowired
private final SlotRepository slotRepository = null;
@Autowired
private final SlotRepository slotRepository = null;
@Autowired
private final AppSettings appSettings = null;

View File

@ -26,8 +26,6 @@ public class RegisterController {
@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()) {

View File

@ -1,6 +1,7 @@
package org.hso.ecommerce.controller.cronjob;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.GregorianCalendar;
@ -37,6 +38,11 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
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;
interface ICronjob {
/**
@ -63,10 +69,22 @@ interface ICronjob {
* @param controller Back-reference that allows to use repositories.
*/
void executeAt(Calendar time, CronjobController controller);
/**
* Get a name for this cronjob, that can be presented to the user in the frontend.
*
* @return A german name of this cronjob.
*/
String getDisplayName();
}
@Component
class Reorder implements ICronjob {
@Override
public String getDisplayName() {
return "Nachbestellung";
}
@Override
public Calendar nextExecution(Calendar reference) {
if (reference.get(Calendar.HOUR_OF_DAY) >= 8) {
@ -215,6 +233,7 @@ class ScheduledCronjob {
}
@Component
@RequestMapping("intern/cronjobs")
class CronjobController {
private static final Logger log = LoggerFactory.getLogger(CronjobController.class);
@ -328,4 +347,42 @@ class CronjobController {
public void onPostConstruct() {
new Thread(this::runCronjobExecutionLoop).start();
}
class ManualCronjob {
public final String identifier;
public final String visibleName;
public ManualCronjob(String identifier, String visibleName) {
this.identifier = identifier;
this.visibleName = visibleName;
}
}
private List<ManualCronjob> listAllCronjobs() {
ArrayList<ManualCronjob> entries = new ArrayList<>();
for (Entry<String, ICronjob> job : cronjobs.entrySet()) {
entries.add(new ManualCronjob(job.getKey(), job.getValue().getDisplayName()));
}
return entries;
}
@GetMapping("/")
public String cronjobOverview(Model model) {
model.addAttribute("jobs", listAllCronjobs());
return "intern/cronjobs/index";
}
@PostMapping("/run/{identifier}")
public String runCronjob(Model model, @PathVariable("identifier") String identifier) {
ICronjob jobToExecute = cronjobs.get(identifier);
if (jobToExecute != null) {
jobToExecute.executeAt(new GregorianCalendar(), this);
model.addAttribute("info",
String.format("Der Cronjob \"%s\" wurde ausgeführt.", jobToExecute.getDisplayName()));
} else {
model.addAttribute("error", "Der Cronjob konnte nicht gefunden werden.");
}
model.addAttribute("jobs", listAllCronjobs());
return "intern/cronjobs/index";
}
}

View File

@ -10,9 +10,9 @@ import java.util.Optional;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
@Query("SELECT c FROM User c WHERE c.email = :email")
Optional<User> findByEmail(String email);
@Query("SELECT c FROM User c WHERE c.email = :email")
Optional<User> findByEmail(String email);
@Query("SELECT count(*) FROM User WHERE isEmployee = true")
Optional<Integer> numberOfEmployees();
}

View File

@ -0,0 +1,35 @@
.alert {
display: block;
text-transform: uppercase;
background: #c0392b;
color: white;
animation: blinking 1s linear infinite;
overflow: hidden;
}
.alert > span {
display: inline-block;
padding-left: 100%;
animation: marquee 10s linear infinite;
white-space: nowrap;
}
@keyframes marquee {
0% { transform: translateX(0); }
100% { transform: translateX(-100%); }
}
@keyframes blinking {
60% {
color: white;
}
75% {
color: #c0392b;
}
85% {
color: #c0392b;
}
100% {
color: white;
}
}

View File

@ -47,6 +47,8 @@
</ul>
</li>
<li><a th:href="@{/intern/cronjobs/}">Cronjobs</a></li>
</ul>
</ul>
</nav>

View File

@ -0,0 +1,60 @@
<!DOCTYPE html>
<html lang="de" dir="ltr" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=0.75, user-scalable=no">
<title>Cronjobs</title>
<script th:src="@{/js/filterTable.js}"></script>
<link rel="stylesheet" th:href="@{/css/ecom.css}"/>
<link rel="stylesheet" th:href="@{/css/manual-cronjobs.css}"/>
</head>
<body>
<nav th:replace="fragments/header :: header">Header</nav>
<div class="sidebar-layout content-width">
<nav></nav>
<div>
<h1>Cronjobs</h1>
</div>
</div>
<main class="sidebar-layout content-width">
<nav th:replace="fragments/intern :: sidebar"></nav>
<div class="content-width">
<h3>Manuelle Ausführung von Cronjobs</h3>
<div class="alert"><span>
WARNUNG<br>
Manuelles Triggern von Cronjobs kann zu unerwartetem Verhalten führen!<br>
Diese Seite ist nur für Debugging-Zwecke gedacht.
</span></div>
<p>
<table id="main-table">
<tr>
<th colspan="7">
<input type="text" placeholder="Filtern" class="smaller jsFilterTable full-width"
data-target-id="main-table"></input>
</th>
</tr>
<tr>
<th>Cronjob</th>
<th></th>
</tr>
<tr th:each="job: ${jobs}">
<td th:text="${job.visibleName}"></td>
<td>
<form th:action="@{/intern/cronjobs/run/{identifier}(identifier = ${job.identifier})}" method="post">
<input class="button smaller" type="submit" value="Ausführen" />
</form>
</td>
</tr>
</table>
</p>
</div>
</main>
<footer th:replace="fragments/footer :: footer"></footer>
</body>
</html>

View File

@ -54,23 +54,6 @@
<textarea rows="5" class="full-width" type="text" name="address" id="address"
placeholder="Optional: Zusatz&#10;Optional: Unternehmen&#10;Straße Hausnummer&#10;Postleitzeit Ort&#10;Land"></textarea>
</div>
<fieldset>
<input type="radio" id="type-priv" name="type" value="priv">
<label for="type-priv">Ich bin Privatkunde</label><br>
<input type="radio" id="type-bus" name="type" value="bus">
<label for="type-bus">Ich bin Geschäftskunde</label><br>
</fieldset>
<div>
<h2> Werbung </h2>
</div>
<div>
<fieldset>
<input type="radio" id="ad-y" name="ad" value="y">
<label for="ad-y">Ich möchte Werbung erhalten.</label><br>
<input type="radio" id="ad-n" name="ad" value="n">
<label for="ad-n">Ich möchte keine Werbung erhalten.</label><br>
</fieldset>
</div>
<div>
<button class="full-width" type="submit" name="action" value="login">Registeren</button>
<a th:href="@{/terms}">

50
supplier/config/mda.json Normal file
View File

@ -0,0 +1,50 @@
{
"id" : "mda",
"name" : "MDA",
"discount" : {
"minimumDailySalesVolumeNetCent": 20,
"percentDiscount": 3
},
"articles": [
{
"title": "MDA Nezyr 7",
"manufacturer": "MDA",
"articleNumber": "n7",
"vatPercent": 19,
"pricePerUnitNet": 29990,
"shouldBeAdvertised": true
},
{
"title": "MDA Nezyr 5",
"manufacturer": "MDA",
"articleNumber": "n5",
"vatPercent": 19,
"pricePerUnitNet": 19700,
"shouldBeAdvertised": true
},
{
"title": "MDA Nezyr 3",
"manufacturer": "MDA",
"articleNumber": "n3",
"vatPercent": 19,
"pricePerUnitNet": 8990,
"shouldBeAdvertised": false
},
{
"title": "MDA Nezyr Threadcracker 3990",
"manufacturer": "MDA",
"articleNumber": "ntc3990",
"vatPercent": 19,
"pricePerUnitNet": 404900,
"shouldBeAdvertised": true
},
{
"title": "MDA Radon RX 5700",
"manufacturer": "MDA",
"articleNumber": "rrx5700",
"vatPercent": 19,
"pricePerUnitNet": 43624,
"shouldBeAdvertised": true
}
]
}

View File

@ -0,0 +1,34 @@
{
"id" : "nanosoft",
"name" : "Nanosoft",
"discount" : {
"minimumDailySalesVolumeNetCent": 50,
"percentDiscount": 0.5
},
"articles": [
{
"title": "Nanosoft Doors 10",
"manufacturer": "Nanosoft",
"articleNumber": "d10",
"vatPercent": 7,
"pricePerUnitNet": 2099,
"shouldBeAdvertised": true
},
{
"title": "Nanosoft Interior Pro 7",
"manufacturer": "Nanosoft",
"articleNumber": "ip7",
"vatPercent": 19,
"pricePerUnitNet": 90780,
"shouldBeAdvertised": false
},
{
"title": "Nanosoft Ybox Two",
"manufacturer": "Nanosoft",
"articleNumber": "ybox2",
"vatPercent": 19,
"pricePerUnitNet": 23500,
"shouldBeAdvertised": true
}
]
}

View File

@ -0,0 +1,42 @@
{
"id" : "outtel",
"name" : "Outtel",
"discount" : {
"minimumDailySalesVolumeNetCent": 20,
"percentDiscount": 1
},
"articles": [
{
"title": "Outtel Core o7",
"manufacturer": "Outtel",
"articleNumber": "o7",
"vatPercent": 19,
"pricePerUnitNet": 40000,
"shouldBeAdvertised": true
},
{
"title": "Outtel Core o5",
"manufacturer": "Outtel",
"articleNumber": "o5",
"vatPercent": 19,
"pricePerUnitNet": 25000,
"shouldBeAdvertised": true
},
{
"title": "Outtel Core o3",
"manufacturer": "Outtel",
"articleNumber": "o3",
"vatPercent": 7,
"pricePerUnitNet": 8000,
"shouldBeAdvertised": false
},
{
"title": "Outtel Core o9",
"manufacturer": "Outtel",
"articleNumber": "o9",
"vatPercent": 19,
"pricePerUnitNet": 55000,
"shouldBeAdvertised": true
}
]
}

50
supplier/config/pear.json Normal file
View File

@ -0,0 +1,50 @@
{
"id" : "pear",
"name" : "Pear",
"discount" : {
"minimumDailySalesVolumeNetCent": 100,
"percentDiscount": 2
},
"articles": [
{
"title": "Pear iMobile 10",
"manufacturer": "Pear",
"articleNumber": "iM10",
"vatPercent": 19,
"pricePerUnitNet": 31500,
"shouldBeAdvertised": true
},
{
"title": "Pear iPlate Plus",
"manufacturer": "Pear",
"articleNumber": "ipp",
"vatPercent": 19,
"pricePerUnitNet": 44900,
"shouldBeAdvertised": true
},
{
"title": "Pear iDonalds 9",
"manufacturer": "Pear",
"articleNumber": "id9",
"vatPercent": 19,
"pricePerUnitNet": 234800,
"shouldBeAdvertised": true
},
{
"title": "Pear GroundPods",
"manufacturer": "Pear",
"articleNumber": "gp",
"vatPercent": 7,
"pricePerUnitNet": 13599,
"shouldBeAdvertised": true
},
{
"title": "Pear Donalsbook Pro",
"manufacturer": "Pear",
"articleNumber": "dbp",
"vatPercent": 7,
"pricePerUnitNet": 145900,
"shouldBeAdvertised": true
}
]
}

View File

@ -0,0 +1,42 @@
{
"id" : "sumsang",
"name" : "Sumsang",
"discount" : {
"minimumDailySalesVolumeNetCent": 300,
"percentDiscount": 2
},
"articles": [
{
"title": "Sumsang Universe S10",
"manufacturer": "Sumsang",
"articleNumber": "us10",
"vatPercent": 19,
"pricePerUnitNet": 59000,
"shouldBeAdvertised": true
},
{
"title": "Sumsang Universe S10e",
"manufacturer": "Sumsang",
"articleNumber": "us10e",
"vatPercent": 19,
"pricePerUnitNet": 70231,
"shouldBeAdvertised": false
},
{
"title": "Sumsang DumbTV",
"manufacturer": "Sumsang",
"articleNumber": "dtv",
"vatPercent": 19,
"pricePerUnitNet": 38395,
"shouldBeAdvertised": true
},
{
"title": "Sumsang UniverseWatch",
"manufacturer": "Sumsang",
"articleNumber": "uw",
"vatPercent": 19,
"pricePerUnitNet": 20494,
"shouldBeAdvertised": true
}
]
}

View File

@ -0,0 +1,58 @@
{
"id" : "techdealer",
"name" : "Tech Dealer",
"discount" : {
"minimumDailySalesVolumeNetCent": 100,
"percentDiscount": 2
},
"articles": [
{
"title": "TROPIC Gehäuselüfter",
"manufacturer": "TROPIC",
"articleNumber": "tgl",
"vatPercent": 19,
"pricePerUnitNet": 459,
"shouldBeAdvertised": true
},
{
"title": "Pirate PC-Netzteil",
"manufacturer": "Pirate",
"articleNumber": "ppcn",
"vatPercent": 19,
"pricePerUnitNet": 9355,
"shouldBeAdvertised": true
},
{
"title": "Pirate Void Elite RGB Wireless Gaming Headset",
"manufacturer": "Pirate",
"articleNumber": "pvergbwgh",
"vatPercent": 7,
"pricePerUnitNet": 10999,
"shouldBeAdvertised": false
},
{
"title": "Aeroheat CYLON PC-Gehäuse",
"manufacturer": "Aeroheat",
"articleNumber": "acpcg",
"vatPercent": 19,
"pricePerUnitNet": 3999,
"shouldBeAdvertised": true
},
{
"title": "Illogitech C270 Webcam",
"manufacturer": "Illogitech",
"articleNumber": "ic270w",
"vatPercent": 19,
"pricePerUnitNet": 3499,
"shouldBeAdvertised": true
},
{
"title": "Illogitech Z607 Surround Sound Lautsprecher",
"manufacturer": "Illogitech",
"articleNumber": "iz607ssl",
"vatPercent": 19,
"pricePerUnitNet": 9495,
"shouldBeAdvertised": false
}
]
}