Dashboard working
This commit is contained in:
parent
2ba4968556
commit
6284a4d7bf
|
@ -59,7 +59,7 @@ public class CalculateWarehouseStatsAction {
|
||||||
return articleIds.size();
|
return articleIds.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class WarehouseStats {
|
public static class WarehouseStats {
|
||||||
public int numArticles;
|
public int numArticles;
|
||||||
public double efficiency;
|
public double efficiency;
|
||||||
public double ratioUsedSlots;
|
public double ratioUsedSlots;
|
||||||
|
|
|
@ -1,16 +1,5 @@
|
||||||
package org.hso.ecommerce.controller.cronjob;
|
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.booking.CreateBookingAction;
|
||||||
import org.hso.ecommerce.action.cronjob.ReadSupplierDataAction;
|
import org.hso.ecommerce.action.cronjob.ReadSupplierDataAction;
|
||||||
import org.hso.ecommerce.action.cronjob.ReadSupplierDataAction.ArticleIdentifier;
|
import org.hso.ecommerce.action.cronjob.ReadSupplierDataAction.ArticleIdentifier;
|
||||||
|
@ -27,17 +16,25 @@ import org.hso.ecommerce.entities.supplier.SupplierOrder;
|
||||||
import org.hso.ecommerce.repos.booking.BookingAccountEntryRepository;
|
import org.hso.ecommerce.repos.booking.BookingAccountEntryRepository;
|
||||||
import org.hso.ecommerce.repos.booking.BookingRepository;
|
import org.hso.ecommerce.repos.booking.BookingRepository;
|
||||||
import org.hso.ecommerce.repos.cronjob.BackgroundJobRepository;
|
import org.hso.ecommerce.repos.cronjob.BackgroundJobRepository;
|
||||||
|
import org.hso.ecommerce.repos.dashboard.DashboardSummaryRepository;
|
||||||
import org.hso.ecommerce.repos.shop.ArticleRepository;
|
import org.hso.ecommerce.repos.shop.ArticleRepository;
|
||||||
import org.hso.ecommerce.repos.shop.CustomerOrderRepository;
|
import org.hso.ecommerce.repos.shop.CustomerOrderRepository;
|
||||||
import org.hso.ecommerce.repos.supplier.ArticleOfferRepository;
|
import org.hso.ecommerce.repos.supplier.ArticleOfferRepository;
|
||||||
import org.hso.ecommerce.repos.supplier.SupplierOrderRepository;
|
import org.hso.ecommerce.repos.supplier.SupplierOrderRepository;
|
||||||
import org.hso.ecommerce.repos.supplier.SupplierRepository;
|
import org.hso.ecommerce.repos.supplier.SupplierRepository;
|
||||||
|
import org.hso.ecommerce.repos.user.UserRepository;
|
||||||
|
import org.hso.ecommerce.repos.warehouse.SlotRepository;
|
||||||
import org.hso.ecommerce.repos.warehouse.WarehouseBookingPositionSlotEntryRepository;
|
import org.hso.ecommerce.repos.warehouse.WarehouseBookingPositionSlotEntryRepository;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.annotation.PostConstruct;
|
||||||
|
import java.sql.Timestamp;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
interface ICronjob {
|
interface ICronjob {
|
||||||
/**
|
/**
|
||||||
* Calculate the earliest cronjob execution time that happens after the given reference time.
|
* Calculate the earliest cronjob execution time that happens after the given reference time.
|
||||||
|
@ -223,6 +220,9 @@ class CronjobController {
|
||||||
@Autowired
|
@Autowired
|
||||||
private final BackgroundJobRepository cronjobRepository = null;
|
private final BackgroundJobRepository cronjobRepository = null;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
final SlotRepository slotRepository = null;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
final ArticleRepository articleRepository = null;
|
final ArticleRepository articleRepository = null;
|
||||||
|
|
||||||
|
@ -241,17 +241,24 @@ class CronjobController {
|
||||||
@Autowired
|
@Autowired
|
||||||
final WarehouseBookingPositionSlotEntryRepository warehouseBookingPositionSlotEntryRepository = null;
|
final WarehouseBookingPositionSlotEntryRepository warehouseBookingPositionSlotEntryRepository = null;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
final DashboardSummaryRepository dashboardSummaryRepository = null;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
final SupplierRepository supplierRepository = null;
|
final SupplierRepository supplierRepository = null;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
final SupplierOrderRepository supplierOrderRepository = null;
|
final SupplierOrderRepository supplierOrderRepository = null;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
final UserRepository userRepository = null;
|
||||||
|
|
||||||
private static Map<String, ICronjob> getCronjobs() {
|
private static Map<String, ICronjob> getCronjobs() {
|
||||||
HashMap<String, ICronjob> map = new HashMap<>();
|
HashMap<String, ICronjob> map = new HashMap<>();
|
||||||
|
|
||||||
// Register all existing cronjobs
|
// Register all existing cronjobs
|
||||||
map.put(BackgroundJob.JOB_REORDER, new Reorder());
|
map.put(BackgroundJob.JOB_REORDER, new Reorder());
|
||||||
|
map.put(BackgroundJob.JOB_DASHBOARD, new DashboardCronjob());
|
||||||
|
|
||||||
return Collections.unmodifiableMap(map);
|
return Collections.unmodifiableMap(map);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,79 @@
|
||||||
|
package org.hso.ecommerce.controller.cronjob;
|
||||||
|
|
||||||
|
|
||||||
|
import org.hso.ecommerce.action.warehouse.CalculateWarehouseStatsAction;
|
||||||
|
import org.hso.ecommerce.entities.dashboard.DashboardSummary;
|
||||||
|
import org.hso.ecommerce.entities.warehouse.WarehouseBookingPositionSlotEntry;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.sql.Timestamp;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class DashboardCronjob implements ICronjob {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Calendar nextExecution(Calendar reference) {
|
||||||
|
reference.add(Calendar.DAY_OF_MONTH, 1);
|
||||||
|
reference.set(Calendar.HOUR_OF_DAY, 0);
|
||||||
|
reference.set(Calendar.MINUTE, 0);
|
||||||
|
reference.set(Calendar.SECOND, 0);
|
||||||
|
reference.set(Calendar.MILLISECOND, 0);
|
||||||
|
return reference;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Calendar previousExecution(Calendar reference) {
|
||||||
|
reference.set(Calendar.HOUR_OF_DAY, 0);
|
||||||
|
reference.set(Calendar.MINUTE, 0);
|
||||||
|
reference.set(Calendar.SECOND, 0);
|
||||||
|
reference.set(Calendar.MILLISECOND, 0);
|
||||||
|
return reference;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void executeAt(Calendar time, CronjobController controller) {
|
||||||
|
|
||||||
|
Calendar oneDayBefore = (Calendar) time.clone();
|
||||||
|
oneDayBefore.add(Calendar.DAY_OF_MONTH, -1);
|
||||||
|
|
||||||
|
DashboardSummary dashboardSummary = new DashboardSummary();
|
||||||
|
|
||||||
|
List<WarehouseBookingPositionSlotEntry> entries = controller.slotRepository.findAll().stream().map(
|
||||||
|
s -> controller.warehouseBookingPositionSlotEntryRepository
|
||||||
|
.getBySlotNum(s.slotNum)
|
||||||
|
.orElseGet(() -> WarehouseBookingPositionSlotEntry.empty(null, s))
|
||||||
|
).collect(Collectors.toList());
|
||||||
|
CalculateWarehouseStatsAction.WarehouseStats warehouseStats = new CalculateWarehouseStatsAction(entries).finish();
|
||||||
|
|
||||||
|
dashboardSummary.created = new java.sql.Date(time.getTimeInMillis());
|
||||||
|
dashboardSummary.todaysCustomersOrders = getSales(oneDayBefore, time, controller);
|
||||||
|
|
||||||
|
dashboardSummary.todaysNewCustomers = getNewUsers(oneDayBefore, time, controller);
|
||||||
|
dashboardSummary.todaysWarehouseCapacity = warehouseStats.efficiency;
|
||||||
|
dashboardSummary.currentWarehouseCapacity = warehouseStats.ratioUsedSlots;
|
||||||
|
dashboardSummary.todaysSalesCent = getTurnover(oneDayBefore, time, controller);
|
||||||
|
|
||||||
|
controller.dashboardSummaryRepository.save(dashboardSummary);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Integer getSales (Calendar begin, Calendar end, CronjobController controller)
|
||||||
|
{
|
||||||
|
return controller.customerOrderRepository.countOrdersInTimespan(
|
||||||
|
new Timestamp(begin.getTimeInMillis()), new Timestamp(end.getTimeInMillis()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Integer getTurnover (Calendar begin, Calendar end, CronjobController controller)
|
||||||
|
{
|
||||||
|
return controller.customerOrderRepository.countTurnoverInTimespan(
|
||||||
|
new Timestamp(begin.getTimeInMillis()), new Timestamp(end.getTimeInMillis()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Integer getNewUsers (Calendar begin, Calendar end, CronjobController controller)
|
||||||
|
{
|
||||||
|
return controller.userRepository.countUsersInTimespan(
|
||||||
|
new Timestamp(begin.getTimeInMillis()), new Timestamp(end.getTimeInMillis()));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
package org.hso.ecommerce.controller.intern;
|
||||||
|
|
||||||
|
import org.hso.ecommerce.entities.dashboard.DashboardSummary;
|
||||||
|
import org.hso.ecommerce.repos.dashboard.DashboardSummaryRepository;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.data.domain.PageRequest;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
public class DashboardController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private DashboardSummaryRepository dashboardSummaryRepository;
|
||||||
|
|
||||||
|
@GetMapping("/intern/dashboardsummary")
|
||||||
|
public List<DashboardSummary> getDashboardEntries()
|
||||||
|
{
|
||||||
|
List<DashboardSummary> inTimespan = dashboardSummaryRepository.findInTimespan(PageRequest.of(0, 7) );
|
||||||
|
return inTimespan;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -15,11 +15,12 @@ public class DashboardSummary {
|
||||||
@NotNull
|
@NotNull
|
||||||
public java.sql.Date created;
|
public java.sql.Date created;
|
||||||
|
|
||||||
public int todaysCustomers;
|
public int todaysNewCustomers;
|
||||||
public int todaysCustomersOrders;
|
public int todaysCustomersOrders;
|
||||||
public int todaysSuppliersOrders;
|
public int todaysSuppliersOrders;
|
||||||
public int todaysItemsSold;
|
public int todaysItemsSold;
|
||||||
public int todaysSalesCent;
|
public int todaysSalesCent;
|
||||||
public int totalWarehouseCapacity;
|
public int totalWarehouseCapacity;
|
||||||
public int currentWarehouseCapacity;
|
public double currentWarehouseCapacity;
|
||||||
|
public double todaysWarehouseCapacity;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
package org.hso.ecommerce.repos.dashboard;
|
||||||
|
|
||||||
|
import org.hso.ecommerce.entities.dashboard.DashboardSummary;
|
||||||
|
import org.springframework.data.domain.Pageable;
|
||||||
|
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 DashboardSummaryRepository extends JpaRepository<DashboardSummary, Long> {
|
||||||
|
|
||||||
|
@Query("SELECT ds FROM DashboardSummary ds ORDER BY ds.id DESC")
|
||||||
|
List<DashboardSummary> findInTimespan(
|
||||||
|
Pageable pageable
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
|
@ -18,4 +18,14 @@ public interface CustomerOrderRepository extends JpaRepository<CustomerOrder, Lo
|
||||||
@Query("SELECT co FROM CustomerOrder co WHERE co.customer.id = :userId ORDER BY co.id DESC")
|
@Query("SELECT co FROM CustomerOrder co WHERE co.customer.id = :userId ORDER BY co.id DESC")
|
||||||
List<CustomerOrder> getOrdersByUserId(long userId);
|
List<CustomerOrder> getOrdersByUserId(long userId);
|
||||||
|
|
||||||
|
@Query("SELECT COUNT(co.id) FROM CustomerOrder co WHERE co.created >= :begin AND co.created < :end")
|
||||||
|
Integer countOrdersInTimespan(
|
||||||
|
java.sql.Timestamp begin, java.sql.Timestamp end
|
||||||
|
);
|
||||||
|
|
||||||
|
@Query("SELECT SUM(co.totalGrossCent) FROM CustomerOrder co WHERE co.created >= :begin AND co.created < :end")
|
||||||
|
Integer countTurnoverInTimespan(
|
||||||
|
java.sql.Timestamp begin, java.sql.Timestamp end
|
||||||
|
);
|
||||||
|
|
||||||
}
|
}
|
|
@ -13,6 +13,11 @@ public interface UserRepository extends JpaRepository<User, Long> {
|
||||||
@Query("SELECT c FROM User c WHERE c.email = :email")
|
@Query("SELECT c FROM User c WHERE c.email = :email")
|
||||||
Optional<User> findByEmail(String email);
|
Optional<User> findByEmail(String email);
|
||||||
|
|
||||||
|
@Query("SELECT COUNT(c.id) FROM User c WHERE c.created >= :begin AND c.created < :end AND c.isEmployee = false")
|
||||||
|
Integer countUsersInTimespan(
|
||||||
|
java.sql.Timestamp begin, java.sql.Timestamp end
|
||||||
|
);
|
||||||
|
|
||||||
@Query("SELECT count(*) FROM User WHERE isEmployee = true")
|
@Query("SELECT count(*) FROM User WHERE isEmployee = true")
|
||||||
Optional<Integer> numberOfEmployees();
|
Optional<Integer> numberOfEmployees();
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,3 +22,8 @@ spring.servlet.multipart.max-request-size=10MB
|
||||||
# EMBEDDED SERVER CONFIGURATION (ServerProperties)
|
# EMBEDDED SERVER CONFIGURATION (ServerProperties)
|
||||||
server.address=::1
|
server.address=::1
|
||||||
server.port=8080
|
server.port=8080
|
||||||
|
|
||||||
|
spring.thymeleaf.prefix=file:src/main/resources/templates/
|
||||||
|
spring.thymeleaf.cache=false
|
||||||
|
spring.resources.static-locations=file:src/main/resources/static/
|
||||||
|
spring.resources.cache=false
|
|
@ -6,8 +6,8 @@
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=0.75, user-scalable=no">
|
<meta name="viewport" content="width=device-width, initial-scale=0.75, user-scalable=no">
|
||||||
<script src="/js/chart.js"></script>
|
<script src="/js/chart.js"></script>
|
||||||
<script>
|
<script>
|
||||||
function chart(id, name, data, unit) {
|
function chart(id, name, data, unit, dates) {
|
||||||
document.addEventListener("DOMContentLoaded", function() {
|
|
||||||
let elm = document.getElementById(id);
|
let elm = document.getElementById(id);
|
||||||
let cs = getComputedStyle(elm)
|
let cs = getComputedStyle(elm)
|
||||||
let ctx = elm.getContext('2d');
|
let ctx = elm.getContext('2d');
|
||||||
|
@ -20,7 +20,7 @@
|
||||||
|
|
||||||
// The data for our dataset
|
// The data for our dataset
|
||||||
data: {
|
data: {
|
||||||
labels: ['Freitag', 'Donnerstag', 'Mittwoch', 'Dienstag', 'Montag'],
|
labels: dates,
|
||||||
datasets: [{
|
datasets: [{
|
||||||
label: name,
|
label: name,
|
||||||
backgroundColor: cs.getPropertyValue('--c-primary'),
|
backgroundColor: cs.getPropertyValue('--c-primary'),
|
||||||
|
@ -61,7 +61,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
@ -107,7 +106,12 @@
|
||||||
<h3>Verkäufe (Anzahl)</h3>
|
<h3>Verkäufe (Anzahl)</h3>
|
||||||
<canvas id="sales"></canvas>
|
<canvas id="sales"></canvas>
|
||||||
<script>
|
<script>
|
||||||
chart('sales', 'Verkäufe/Tag', [15, 12, 14, 8, 7])
|
fetch('./dashboardsummary')
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
chart('sales', 'Verkäufe/Tag', data.map(d => d.todaysCustomersOrders),
|
||||||
|
"", data.map(g => g.created));
|
||||||
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
</section>
|
</section>
|
||||||
|
@ -116,7 +120,12 @@
|
||||||
<h3>Verkäufe (Umsatz)</h3>
|
<h3>Verkäufe (Umsatz)</h3>
|
||||||
<canvas id="umsatz"></canvas>
|
<canvas id="umsatz"></canvas>
|
||||||
<script>
|
<script>
|
||||||
chart('umsatz', 'Umsatz/Tag', [1512.45, 1225.15, 1452.54, 814.54, 746.00], "EUR")
|
fetch('./dashboardsummary')
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
chart('umsatz', 'Umsatz/Tag', data.map(d => d.todaysSalesCent / 100),
|
||||||
|
"€", data.map(g => g.created));
|
||||||
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
</section>
|
</section>
|
||||||
|
@ -125,7 +134,12 @@
|
||||||
<h3>Neukunden</h3>
|
<h3>Neukunden</h3>
|
||||||
<canvas id="Neukunden"></canvas>
|
<canvas id="Neukunden"></canvas>
|
||||||
<script>
|
<script>
|
||||||
chart('Neukunden', 'Neukunden', [1, 1, 0, 5, 0], "")
|
fetch('./dashboardsummary')
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
chart('Neukunden', 'Neukunden', data.map(d => d.todaysNewCustomers),
|
||||||
|
"", data.map(g => g.created));
|
||||||
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
</section>
|
</section>
|
||||||
|
@ -139,7 +153,12 @@
|
||||||
<h2>Lagerauslastung</h2>
|
<h2>Lagerauslastung</h2>
|
||||||
<canvas id="warehouse"></canvas>
|
<canvas id="warehouse"></canvas>
|
||||||
<script>
|
<script>
|
||||||
chart('warehouse', 'Lagerauslastung', [50, 30, 20, 80, 60], "%")
|
fetch('./dashboardsummary')
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
chart('warehouse', 'Lagerauslastung', data.map(d => d.todaysWarehouseCapacity),
|
||||||
|
"%", data.map(g => g.created));
|
||||||
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
</section>
|
</section>
|
||||||
|
@ -148,7 +167,12 @@
|
||||||
<h2>Lagereffizienz</h2>
|
<h2>Lagereffizienz</h2>
|
||||||
<canvas id="warehouse-ef"></canvas>
|
<canvas id="warehouse-ef"></canvas>
|
||||||
<script>
|
<script>
|
||||||
chart('warehouse-ef', 'Lagereffizienz', [80, 70, 60, 75, 54], "%")
|
fetch('./dashboardsummary')
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
chart('warehouse-ef', 'Lagereffizienz', data.map(d => d.todaysWarehouseCapacity),
|
||||||
|
"%", data.map(g => g.created));
|
||||||
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
</section>
|
</section>
|
||||||
|
|
Reference in New Issue