add pizza bot
parent
8bc0f8cce0
commit
143347b2d9
@ -0,0 +1,21 @@
|
|||||||
|
package eu.jonahbauer.chat.bot.test;
|
||||||
|
|
||||||
|
import eu.jonahbauer.chat.bot.api.ChatBot;
|
||||||
|
import eu.jonahbauer.chat.bot.config.BotConfig;
|
||||||
|
import eu.jonahbauer.chat.bot.impl.ChatBotFactory;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
public final class ChatBotFactoryAccess {
|
||||||
|
|
||||||
|
private ChatBotFactoryAccess() {}
|
||||||
|
|
||||||
|
public static <T extends ChatBot> T create(@NotNull BotConfig config, @NotNull Supplier<T> supplier) {
|
||||||
|
return ScopedValue.where(ChatBotFactory.BOT_CONFIG, config).get(supplier);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T extends ChatBot> T create(@NotNull Supplier<T> supplier) {
|
||||||
|
return create(BotConfig.EMPTY, supplier);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
package eu.jonahbauer.chat.bot.test;
|
||||||
|
|
||||||
|
import eu.jonahbauer.chat.bot.api.Chat;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class MockChat implements Chat {
|
||||||
|
private final @NotNull List<@NotNull Message> messages = new ArrayList<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean send(@NotNull String channel, @NotNull String name, @NotNull String message, boolean bottag, boolean publicId) {
|
||||||
|
messages.add(new Message(channel, name, message, bottag, publicId));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NotNull List<@NotNull Message> getMessages() {
|
||||||
|
return messages;
|
||||||
|
}
|
||||||
|
|
||||||
|
public record Message(@NotNull String channel, @NotNull String name, @NotNull String message, boolean bottag, boolean publicId) {
|
||||||
|
public Message(@NotNull String channel, @NotNull String name, @NotNull String message) {
|
||||||
|
this(channel, name, message, true, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
module eu.jonahbauer.chat.bot.api.fixtures {
|
||||||
|
exports eu.jonahbauer.chat.bot.test;
|
||||||
|
|
||||||
|
requires eu.jonahbauer.chat.bot.api;
|
||||||
|
requires eu.jonahbauer.chat.bot.config;
|
||||||
|
requires static transitive org.jetbrains.annotations;
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
plugins {
|
||||||
|
id("chat-bot.bot-conventions")
|
||||||
|
}
|
||||||
|
|
||||||
|
group = "eu.jonahbauer.chat.bots"
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation(project(":bot-database"))
|
||||||
|
testImplementation(testFixtures(project(":bot-api")))
|
||||||
|
}
|
@ -0,0 +1,678 @@
|
|||||||
|
package eu.jonahbauer.chat.bot.pizza;
|
||||||
|
|
||||||
|
import eu.jonahbauer.chat.bot.api.ChatBot;
|
||||||
|
import eu.jonahbauer.chat.bot.api.Message.Post;
|
||||||
|
import eu.jonahbauer.chat.bot.config.BotConfig;
|
||||||
|
import eu.jonahbauer.chat.bot.database.Database;
|
||||||
|
import eu.jonahbauer.chat.bot.database.NonUniqueResultException;
|
||||||
|
import eu.jonahbauer.chat.bot.pizza.model.*;
|
||||||
|
import eu.jonahbauer.chat.bot.pizza.model.table.Order;
|
||||||
|
import eu.jonahbauer.chat.bot.pizza.model.table.OrderItem;
|
||||||
|
import eu.jonahbauer.chat.bot.pizza.model.table.Pizza;
|
||||||
|
import eu.jonahbauer.chat.bot.pizza.model.table.User;
|
||||||
|
import eu.jonahbauer.chat.bot.pizza.model.view.OrderDetail;
|
||||||
|
import eu.jonahbauer.chat.bot.pizza.model.view.OrderItemDetail;
|
||||||
|
import eu.jonahbauer.chat.bot.pizza.util.ArgumentParser;
|
||||||
|
import eu.jonahbauer.chat.bot.pizza.util.Pair;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.jetbrains.annotations.VisibleForTesting;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.function.BinaryOperator;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static eu.jonahbauer.chat.bot.pizza.util.ArgumentParser.*;
|
||||||
|
import static eu.jonahbauer.chat.bot.pizza.util.FormatUtils.*;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class PizzaBot extends ChatBot {
|
||||||
|
private static final @NotNull ScopedValue<Double> ANNOYANCE = ScopedValue.newInstance();
|
||||||
|
private static final @NotNull ScopedValue<User> USER = ScopedValue.newInstance();
|
||||||
|
|
||||||
|
private static final @NotNull BotConfig.Key.OfStringArray PRIMARY_CHANNELS = BotConfig.Key.ofStringArray("primary_channels");
|
||||||
|
private static final @NotNull BotConfig.Key.OfString DATABASE = BotConfig.Key.ofString("database");
|
||||||
|
|
||||||
|
private static final @NotNull DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("HH:mm")
|
||||||
|
.withZone(ZoneId.of("Europe/Berlin"))
|
||||||
|
.withLocale(Locale.GERMANY);
|
||||||
|
private static final @NotNull List<@NotNull String> ANNOYED_RESPONSES = List.of(
|
||||||
|
"Es ist so dunkel, ich kann dich nicht hören.",
|
||||||
|
"Jetzt schrei doch nicht so!",
|
||||||
|
"Fresse!"
|
||||||
|
);
|
||||||
|
|
||||||
|
private final @Nullable Set<@NotNull String> primaryChannels;
|
||||||
|
private final @NotNull PizzaService service;
|
||||||
|
|
||||||
|
public PizzaBot() {
|
||||||
|
super("Marek");
|
||||||
|
this.primaryChannels = getConfig().get(PRIMARY_CHANNELS).map(Set::copyOf).orElse(null);
|
||||||
|
this.service = new PizzaService(new Database(getConfig().require(DATABASE)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
PizzaBot(@Nullable Set<@NotNull String> primaryChannels, @NotNull Database database) {
|
||||||
|
super("Marek");
|
||||||
|
this.primaryChannels = primaryChannels;
|
||||||
|
this.service = new PizzaService(database);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onMessage(@NotNull Post post) {
|
||||||
|
var message = post.message();
|
||||||
|
if (message.length() < 6) return;
|
||||||
|
|
||||||
|
var command = message.substring(0, 6);
|
||||||
|
|
||||||
|
var runnable = (Runnable) () -> {
|
||||||
|
try {
|
||||||
|
if ("!marek".equals(command) || "!Marek".equals(command)) {
|
||||||
|
handle(post);
|
||||||
|
} else if ("!MAREK".equals(command) && !ANNOYED_RESPONSES.isEmpty()) {
|
||||||
|
handleAnnoyed();
|
||||||
|
} else if ("!marek".equalsIgnoreCase(command)) {
|
||||||
|
var uppercase = command.chars().filter(chr -> 'A' <= chr && chr <= 'Z').count();
|
||||||
|
ScopedValue.runWhere(ANNOYANCE, uppercase / 5d, () -> handle(post));
|
||||||
|
}
|
||||||
|
} catch (PizzaBotException ex) {
|
||||||
|
post(ex.getMessage());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (post.userId() != null && post.userName() != null) {
|
||||||
|
ScopedValue.runWhere(USER, new User(post.userId(), post.userName()), runnable);
|
||||||
|
} else {
|
||||||
|
runnable.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handle(@NotNull Post post) {
|
||||||
|
if (!isPrimaryChannel(post.channel())) {
|
||||||
|
handleNonPrimaryChannel();
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
dispatch(ArgumentParser.parse(post.message().substring(6)));
|
||||||
|
} catch (SQLException ex) {
|
||||||
|
log.error("Fehler beim Verarbeiten von {}.", post, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleAnnoyed() {
|
||||||
|
assert !ANNOYED_RESPONSES.isEmpty();
|
||||||
|
|
||||||
|
var idx = (int) (Math.random() * ANNOYED_RESPONSES.size());
|
||||||
|
post(ANNOYED_RESPONSES.get(idx));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleNonPrimaryChannel() {
|
||||||
|
assert primaryChannels != null;
|
||||||
|
|
||||||
|
if (primaryChannels.size() == 1) {
|
||||||
|
post(STR."Bitte benutze den Channel \{primaryChannels.iterator().next()}.");
|
||||||
|
} else {
|
||||||
|
var channels = new StringBuilder();
|
||||||
|
var it = primaryChannels.iterator();
|
||||||
|
while (it.hasNext()) {
|
||||||
|
var channel = it.next();
|
||||||
|
if (!it.hasNext()) {
|
||||||
|
channels.append(" oder ");
|
||||||
|
} else if (!channels.isEmpty()) {
|
||||||
|
channels.append(", ");
|
||||||
|
}
|
||||||
|
channels.append(channel);
|
||||||
|
}
|
||||||
|
post(STR."Bitte benutze einen der Channel \{channels.toString()}.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void dispatch(@NotNull List<@NotNull String> args) throws SQLException {
|
||||||
|
if (args.isEmpty()) {
|
||||||
|
helpUser();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var argc = args.size();
|
||||||
|
var command = args.getFirst();
|
||||||
|
switch (command) {
|
||||||
|
case "--user", "-u" -> runAs(args);
|
||||||
|
case "help" -> {
|
||||||
|
if (argc > 1 && "--all".equals(args.get(1))) {
|
||||||
|
helpAdmin();
|
||||||
|
} else {
|
||||||
|
helpUser();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "info" -> info();
|
||||||
|
case "order" -> {
|
||||||
|
if (argc == 1) throw new PizzaBotException("too few arguments");
|
||||||
|
switch (args.get(1)) {
|
||||||
|
case "revoke" -> {
|
||||||
|
if (argc == 2) orderRevoke();
|
||||||
|
else if (argc != 3) throw new PizzaBotException("too many arguments");
|
||||||
|
else switch (args.get(2)) {
|
||||||
|
case "--all" -> orderRevokeAll();
|
||||||
|
case String s when isLong(s) -> orderRevoke(toLong(s));
|
||||||
|
case String name -> orderRevoke(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "list" -> {
|
||||||
|
if (argc == 2) orderList();
|
||||||
|
// else if (argc == 3 && "--all".equals(args.get(2))) orderListAll();
|
||||||
|
else throw new PizzaBotException("invalid command");
|
||||||
|
}
|
||||||
|
case "add" -> {
|
||||||
|
if (argc < 4) throw new PizzaBotException("too few arguments");
|
||||||
|
else if (argc > 4) throw new PizzaBotException("too many arguments");
|
||||||
|
orderAdd(args.get(2), args.get(3));
|
||||||
|
}
|
||||||
|
case String name when argc == 2 -> order(name, null);
|
||||||
|
case String name -> order(name, String.join(" ", args.subList(2, args.size())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "summary" -> {
|
||||||
|
if (argc == 1) summary();
|
||||||
|
else if (argc == 2 && "--all".equals(args.get(1))) summaryAll();
|
||||||
|
else if (argc == 2) summary(args.get(1));
|
||||||
|
else throw new PizzaBotException("invalid command");
|
||||||
|
}
|
||||||
|
case "check" -> {
|
||||||
|
if (argc == 1) check();
|
||||||
|
else if (argc == 2 && "--all".equals(args.get(1))) checkAll();
|
||||||
|
else if (argc == 2) check(args.get(1));
|
||||||
|
else throw new PizzaBotException("invalid command");
|
||||||
|
}
|
||||||
|
case "pay" -> {
|
||||||
|
if (argc == 1) throw new PizzaBotException("missing argument");
|
||||||
|
else if (argc == 2) pay(toDouble(args.get(1)));
|
||||||
|
else if (argc == 3) pay(args.get(1), toDouble(args.get(2)));
|
||||||
|
else throw new PizzaBotException("too many arguments");
|
||||||
|
}
|
||||||
|
case "payment" -> {
|
||||||
|
if (argc == 1) throw new PizzaBotException("too few arguments");
|
||||||
|
switch (args.get(1)) {
|
||||||
|
case "confirm" -> {
|
||||||
|
if (argc == 2) throw new PizzaBotException("too few arguments");
|
||||||
|
else if (argc == 3 && "--all".equals(args.get(2))) throw new PizzaBotException("missing arguments");
|
||||||
|
else if (argc == 3) paymentConfirm(toLong(args.get(2)), null);
|
||||||
|
else if (argc == 4 && "--all".equals(args.get(2))) paymentConfirmAll(args.get(3));
|
||||||
|
else if (argc == 4) paymentConfirm(toLong(args.get(2)), args.get(3));
|
||||||
|
else throw new PizzaBotException("invalid command");
|
||||||
|
}
|
||||||
|
case "void" -> {
|
||||||
|
if (argc == 2) throw new PizzaBotException("too few arguments");
|
||||||
|
else if (argc == 3) paymentVoid(toLong(args.get(2)), null);
|
||||||
|
else if (argc == 4) paymentVoid(toLong(args.get(2)), args.get(3));
|
||||||
|
else throw new PizzaBotException("invalid command");
|
||||||
|
}
|
||||||
|
default -> throw new PizzaBotException("invalid command");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default -> throw new PizzaBotException("Unbekannter Befehl.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private @NotNull User getUser() {
|
||||||
|
if (USER.isBound()) {
|
||||||
|
return USER.get();
|
||||||
|
} else {
|
||||||
|
throw new PizzaBotException("Diese Aktion kann nur mit öffentlicher ID ausgeführt werden.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//<editor-fold desc="Admin" defaultstate="collapsed">
|
||||||
|
private void runAs(@NotNull List<String> args) throws SQLException {
|
||||||
|
// TODO
|
||||||
|
if (true) throw new PizzaBotException("nicht implementiert");
|
||||||
|
|
||||||
|
if (args.size() < 2) throw new PizzaBotException("missing argument");
|
||||||
|
|
||||||
|
var username = args.get(1);
|
||||||
|
// var user = service.getUserByEventAndName(, username);
|
||||||
|
//
|
||||||
|
// ScopedValue.runWhere(USER, user, () -> {
|
||||||
|
// try {
|
||||||
|
// dispatch(args.subList(2, args.size()));
|
||||||
|
// } catch (SQLException ex) {
|
||||||
|
// throw Lombok.sneakyThrow(ex);
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// if (false) throw new SQLException();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void orderAdd(@NotNull String targetOrder, @NotNull String newUserName) throws SQLException {
|
||||||
|
var order = service.getCurrentOrder().checkAdminAccess(getUser());
|
||||||
|
var newUser = service.getUserByName(newUserName);
|
||||||
|
|
||||||
|
OrderItem item;
|
||||||
|
Pizza pizza;
|
||||||
|
List<UserWithPayment> users;
|
||||||
|
try {
|
||||||
|
var targetId = Long.parseLong(targetOrder);
|
||||||
|
var detail = service.getItemById(targetId)
|
||||||
|
.orElseThrow(() -> new PizzaBotException(STR."Die Bestellung mit Nummer \{targetId} existiert nicht."));
|
||||||
|
item = detail.item();
|
||||||
|
pizza = detail.pizza();
|
||||||
|
users = detail.users();
|
||||||
|
} catch (NumberFormatException _) {
|
||||||
|
var user = service.getUserByEventAndName(order.eventId(), targetOrder);
|
||||||
|
try {
|
||||||
|
item = service.getItemByUser(order.id(), user.id())
|
||||||
|
.orElseThrow(() -> new PizzaBotException(STR."\{user.username()} hat keine Bestellung aufgegeben."));
|
||||||
|
pizza = service.getPizzaById(item.pizzaId()).orElseThrow();
|
||||||
|
users = service.getUsersByItem(item);
|
||||||
|
} catch (NonUniqueResultException ex) {
|
||||||
|
throw new PizzaBotException(STR."\{user.username()} hat mehr als eine Bestellung aufgegeben. Bitte gibt die Positionsnummer an.", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var payed = users.stream().filter(UserWithPayment::payed).map(UserWithPayment::username).toList();
|
||||||
|
if (!payed.isEmpty()) {
|
||||||
|
throw new PizzaBotException(STR."\{join(payed)} \{payed.size() == 1 ? "hat" : "haben"} schon bezahlt.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (users.stream().anyMatch(u -> u.id() == newUser.id())) {
|
||||||
|
throw new PizzaBotException(STR."\{newUser.username()} nimmt bereits an dieser Bestellung teil.");
|
||||||
|
}
|
||||||
|
|
||||||
|
service.add(item, newUser, false);
|
||||||
|
post(STR."\{newUser.username()} wurde zur Bestellung \{item.id()} (\{pizza.name()}) von \{join(users, UserWithPayment::username)} hinzugefügt.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zeigt eine Übersicht alle Bestellungen während der Veranstaltung.
|
||||||
|
*/
|
||||||
|
private void summaryAll() throws SQLException {
|
||||||
|
var order = service.getCurrentOrder().checkAdminAccess(getUser());
|
||||||
|
summary0(order.eventName(), service.getItemDetailByEvent(order.eventId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zeigt eine Übersicht der Positionen der laufenden Bestellung.
|
||||||
|
*/
|
||||||
|
private void summary() throws SQLException {
|
||||||
|
var order = service.getCurrentOrder().checkAdminAccess(getUser());
|
||||||
|
summary0(order.name(), service.getItemDetailByOrder(order.id()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zeigt eine Übersicht der Positionen der Bestellung mit dem angegebenen Namen.
|
||||||
|
*/
|
||||||
|
private void summary(@NotNull String name) throws SQLException {
|
||||||
|
var order = service.getCurrentOrder().checkAdminAccess(getUser());
|
||||||
|
var other = service.getOrderByEventAndName(order.eventId(), name);
|
||||||
|
summary0(other.name(), service.getItemDetailByOrder(other.id()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void summary0(@NotNull String name, @NotNull Collection<OrderItemDetail> details) {
|
||||||
|
var items = details.stream().collect(Collectors.groupingBy(
|
||||||
|
detail -> Pair.of(detail.pizza(), detail.notes()),
|
||||||
|
() -> new TreeMap<>(Comparator.comparing(Pair::first)),
|
||||||
|
Collectors.collectingAndThen(
|
||||||
|
Collectors.groupingBy(
|
||||||
|
OrderItemDetail::id,
|
||||||
|
Collectors.mapping(OrderItemDetail::userName, Collectors.joining("+"))
|
||||||
|
),
|
||||||
|
Map::values
|
||||||
|
)
|
||||||
|
));
|
||||||
|
var lines = new ArrayList<String>(items.size() + 2);
|
||||||
|
lines.add("Übersicht " + name);
|
||||||
|
lines.add("");
|
||||||
|
if (items.isEmpty()) lines.add("(keine Einträge)");
|
||||||
|
items.forEach((item, users) -> {
|
||||||
|
var counts = users.stream().collect(Collectors.groupingBy(
|
||||||
|
Function.identity(),
|
||||||
|
Collectors.counting()
|
||||||
|
));
|
||||||
|
var usersString = counts.entrySet().stream()
|
||||||
|
.sorted(Map.Entry.comparingByKey())
|
||||||
|
.map(entry -> entry.getValue() == 1 ? entry.getKey() : entry.getValue() + "x " + entry.getKey())
|
||||||
|
.collect(Collectors.joining(", "));
|
||||||
|
var pizza = item.first().name().toUpperCase(Locale.ROOT);
|
||||||
|
var notes = item.second() != null ? " " + item.second() : "";
|
||||||
|
lines.add(STR."\{tabular(users.size())}x \{pizza}\{notes} (\{usersString})");
|
||||||
|
});
|
||||||
|
post(String.join("\n", lines));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zeigt die zusammengefasste Rechnung für alle Bestellungen während der Veranstaltung.
|
||||||
|
*/
|
||||||
|
private void checkAll() throws SQLException {
|
||||||
|
var order = service.getCurrentOrder().checkAdminAccess(getUser());
|
||||||
|
var details = service.getItemDetailByEvent(order.eventId());
|
||||||
|
|
||||||
|
var tips = details.stream().collect(Collectors.groupingBy(
|
||||||
|
OrderItemDetail::order,
|
||||||
|
Collectors.collectingAndThen(Collectors.toList(), list -> calculateTip(list.getFirst().orderPayment(), list))
|
||||||
|
));
|
||||||
|
|
||||||
|
var items = details.stream().collect(Collectors.groupingBy(
|
||||||
|
OrderItemDetail::user,
|
||||||
|
Collectors.reducing(Receipt.empty(), item -> item.receipt(tips.get(item.order())), Receipt::sum)
|
||||||
|
));
|
||||||
|
check0(order.eventName(), tips.values().stream().anyMatch(tip -> tip > 0), items);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zeigt die Rechnung für der laufenden Bestellung.
|
||||||
|
*/
|
||||||
|
private void check() throws SQLException {
|
||||||
|
var order = service.getCurrentOrder().checkAdminAccess(getUser());
|
||||||
|
check0(order.order());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zeigt die Rechnung für die Bestellung mit dem angegebenen Namen.
|
||||||
|
*/
|
||||||
|
private void check(@NotNull String name) throws SQLException {
|
||||||
|
var order = service.getCurrentOrder().checkAdminAccess(getUser());
|
||||||
|
var other = service.getOrderByEventAndName(order.eventId(), name);
|
||||||
|
check0(other);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double calculateTip(@Nullable Double payment, List<@NotNull OrderItemDetail> details) {
|
||||||
|
var total = details.stream().mapToDouble(OrderItemDetail::userPrice).sum();
|
||||||
|
return payment == null ? 0 : payment / total;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void check0(@NotNull Order order) throws SQLException {
|
||||||
|
var details = service.getItemDetailByOrder(order.id());
|
||||||
|
|
||||||
|
var tip = calculateTip(order.payment(), details);
|
||||||
|
var items = details.stream().collect(Collectors.groupingBy(
|
||||||
|
OrderItemDetail::user,
|
||||||
|
Collectors.reducing(Receipt.empty(), item -> item.receipt(tip), Receipt::sum)
|
||||||
|
));
|
||||||
|
check0(order.name(), order.payment() != null, items);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void check0(@NotNull String name, boolean tipped, @NotNull Map<User, @NotNull Receipt> items) {
|
||||||
|
var lines = new ArrayList<String>();
|
||||||
|
lines.add("Rechnung für " + name + (tipped ? " (mit Trinkgeld)" : ""));
|
||||||
|
lines.add("alle Angaben ohne Gewähr");
|
||||||
|
lines.add("");
|
||||||
|
items.forEach((user, receipt) -> {
|
||||||
|
if (receipt.payed()) {
|
||||||
|
lines.add(strikethrough(user.username() + ": " + receipt.totalAsString()));
|
||||||
|
} else if (receipt.unpayed()) {
|
||||||
|
lines.add(user.username() + ": " + receipt.totalAsString());
|
||||||
|
} else {
|
||||||
|
lines.add(user.username() + ": " + strikethrough(receipt.totalAsString()) + " " + receipt.pendingAsString());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
post(String.join("\n", lines));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void pay(double amount) throws SQLException {
|
||||||
|
var order = service.getCurrentOrder().checkAdminAccess(getUser());
|
||||||
|
service.save(order.order().withPayment(amount));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void pay(@NotNull String name, double amount) throws SQLException {
|
||||||
|
var order = service.getCurrentOrder().checkAdminAccess(getUser());
|
||||||
|
var other = service.getOrderByEventAndName(order.eventId(), name);
|
||||||
|
service.save(other.withPayment(amount));
|
||||||
|
}
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * Beginnt eine neue Bestellung.
|
||||||
|
// * @param restaurant die Restaurant-ID
|
||||||
|
// */
|
||||||
|
// private void orderStart(@NotNull String restaurant, @NotNull String deadline) {
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// /**
|
||||||
|
// * Beginnt eine neue Bestellung.
|
||||||
|
// * @param restaurantId die Restaurant-ID
|
||||||
|
// */
|
||||||
|
// private void orderStart(long restaurantId, @NotNull String deadline) {
|
||||||
|
// var restaurant = restaurantRepository.getById(restaurantId)
|
||||||
|
// .orElseThrow(() -> new BreakException(STR."Das Restaurant mit der ID \{restaurantId} existiert nicht."));
|
||||||
|
// var order = new Order(-1, , restaurantId, , , null);
|
||||||
|
// orderRepository.save(order);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// /**
|
||||||
|
// * Beendet eine Bestellung.
|
||||||
|
// * @param payment der gezahlte Betrag inkl. Trinkgeld
|
||||||
|
// */
|
||||||
|
// private void orderFinish(double payment) {
|
||||||
|
// var order = service.getCurrentOrderAsAdmin();
|
||||||
|
// orderRepository.save(order.withPayment(payment));
|
||||||
|
// check(order.id());
|
||||||
|
// }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bestätigt den Zahlungseingang für alle ausstehenden Zahlungen der laufenden Veranstaltung eines Benutzers.
|
||||||
|
*/
|
||||||
|
private void paymentConfirmAll(@NotNull String name) throws SQLException {
|
||||||
|
var order = service.getCurrentOrder().checkAdminAccess(getUser());
|
||||||
|
var user = service.getUserByEventAndName(order.eventId(), name);
|
||||||
|
var count = service.paymentByEventAndUser(order.eventId(), user.id(), true);
|
||||||
|
post(STR."\{count} ausstehenden Zahlungen für \{user.username()} wurden bestätigt.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bestätigt den Zahlungseingang für eine Bestellung.
|
||||||
|
*/
|
||||||
|
private void paymentConfirm(long itemId, @Nullable String username) throws SQLException {
|
||||||
|
payment0(itemId, username, true, (pizza, user) -> STR."Zahlungseingang für \{pizza} (\{user}) wurde bestätigt.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Widerruft die Bestätigung eines Zahlungseingangs.
|
||||||
|
*/
|
||||||
|
private void paymentVoid(long itemId, @Nullable String username) throws SQLException {
|
||||||
|
payment0(itemId, username, false, (pizza, user) -> STR."Zahlungseingang für \{pizza} (\{user}) wurde bestätigt.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void payment0(long itemId, @Nullable String username, boolean payed, @NotNull BinaryOperator<String> message) throws SQLException {
|
||||||
|
var _ = service.getCurrentOrder().checkAdminAccess(getUser());
|
||||||
|
var summary = service.getItemById(itemId)
|
||||||
|
.orElseThrow(() -> new PizzaBotException(STR."Die Bestellung mit Nummer \{itemId} existiert nicht."));
|
||||||
|
if (username != null) {
|
||||||
|
var users = summary.users().stream().filter(u -> u.username().startsWith(username)).toList();
|
||||||
|
if (users.isEmpty()) {
|
||||||
|
throw new PizzaBotException("Der Benutzer existiert nicht oder hat nicht an der Bestellung teilgenommen.");
|
||||||
|
} else if (users.size() > 1) {
|
||||||
|
throw new PizzaBotException("Der Benutzer ist nicht eindeutig.");
|
||||||
|
} else {
|
||||||
|
var user = users.getFirst();
|
||||||
|
service.paymentByItemAndUser(itemId, user.id(), true);
|
||||||
|
post(message.apply(summary.pizza().name(), user.username()));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
service.paymentByItem(summary.item().id(), payed);
|
||||||
|
post(message.apply(summary.pizza().name(), join(" + ", summary.users(), UserWithPayment::username)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//</editor-fold>
|
||||||
|
|
||||||
|
//<editor-fold desc="User" defaultstate="collapsed">
|
||||||
|
/**
|
||||||
|
* Zeigt Informationen zur aktuell laufenden Bestellung.
|
||||||
|
*/
|
||||||
|
private void info() throws SQLException {
|
||||||
|
var order = service.getCurrentOrder();
|
||||||
|
var result = service.getRestaurantWithPizzas(order.restaurantId());
|
||||||
|
var restaurant = result.first();
|
||||||
|
var pizzas = result.second();
|
||||||
|
|
||||||
|
var lines = new ArrayList<String>();
|
||||||
|
lines.add(STR."Pizzabestellung bei \{restaurant.name()} in \{restaurant.city()}");
|
||||||
|
lines.add(STR."Bestellannahmeschluss: \{DATE_FORMATTER.format(order.deadline())}");
|
||||||
|
pizzas.stream().map(Pizza::toPrettyString).forEach(lines::add);
|
||||||
|
post(String.join("\n", lines));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listet die eigenen Bestellungen auf.
|
||||||
|
*/
|
||||||
|
private void orderList() throws SQLException {
|
||||||
|
var user = getUser();
|
||||||
|
var current = service.getCurrentOrder();
|
||||||
|
|
||||||
|
var itemsByOrder = service.getItemDetailByEventAndUser(current.eventId(), user.id()).stream().collect(Collectors.groupingBy(
|
||||||
|
OrderItemDetail::order,
|
||||||
|
TreeMap::new,
|
||||||
|
Collectors.toUnmodifiableList()
|
||||||
|
));
|
||||||
|
|
||||||
|
var lines = new ArrayList<String>();
|
||||||
|
lines.add("Bestellungen von " + user.username());
|
||||||
|
if (itemsByOrder.isEmpty()) lines.add("(keine Einträge)");
|
||||||
|
itemsByOrder.forEach((order, items) -> {
|
||||||
|
lines.add("");
|
||||||
|
lines.add(order.name());
|
||||||
|
items.stream().map(OrderItemDetail::toPrettyString).forEach(lines::add);
|
||||||
|
});
|
||||||
|
post(String.join("\n", lines));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Storniert die einzige eigene Bestellung.
|
||||||
|
*/
|
||||||
|
private void orderRevoke() throws SQLException {
|
||||||
|
var order = service.getCurrentOrder().checkDeadline();
|
||||||
|
var user = getUser();
|
||||||
|
|
||||||
|
try {
|
||||||
|
var item = service.getItemByUser(order.id(), user.id());
|
||||||
|
if (item.isEmpty()) {
|
||||||
|
post("Du hast keine Bestellung aufgegeben.");
|
||||||
|
} else {
|
||||||
|
orderRevoke0(item.get());
|
||||||
|
}
|
||||||
|
} catch (NonUniqueResultException ex) {
|
||||||
|
post("Du hast mehr als eine Bestellung aufgegeben. Bitte gib die Positionsnummer oder den Namen der Pizza an.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Storniert die angegebene, eigene Bestellung.
|
||||||
|
*/
|
||||||
|
private void orderRevoke(long itemId) throws SQLException {
|
||||||
|
var order = service.getCurrentOrder().checkDeadline();
|
||||||
|
var item = service.getItemById(itemId);
|
||||||
|
if (item.isEmpty() || item.get().item().orderId() != order.id()) {
|
||||||
|
post(STR."Die Position mit Nummer \{itemId} existiert nicht.");
|
||||||
|
} else {
|
||||||
|
orderRevoke0(item.get().checkUserAccess(getUser()).item());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Storniert die eigene Bestellung der angegebene Pizza.
|
||||||
|
*/
|
||||||
|
private void orderRevoke(@NotNull String name) throws SQLException {
|
||||||
|
var order = service.getCurrentOrder().checkDeadline();
|
||||||
|
var user = getUser();
|
||||||
|
try {
|
||||||
|
var item = service.getItemByPizzaName(order.id(), user.id(), name);
|
||||||
|
if (item.isEmpty()) {
|
||||||
|
post("Du hast keine solche Pizza bestellt.");
|
||||||
|
} else {
|
||||||
|
orderRevoke0(item.get());
|
||||||
|
}
|
||||||
|
} catch (NonUniqueResultException ex) {
|
||||||
|
post("Du hast mehr als eine solche Pizza bestellt. Bitte gib die Positionsnummer an.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void orderRevoke0(@NotNull OrderItem item) throws SQLException {
|
||||||
|
var pizza = service.getPizzaById(item.pizzaId()).orElseThrow();
|
||||||
|
service.delete(item);
|
||||||
|
post(STR."Die Position \{item.id()} (\{pizza.name()}) wurde storniert.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Storniert alle eigenen Bestellungen.
|
||||||
|
*/
|
||||||
|
private void orderRevokeAll() throws SQLException {
|
||||||
|
var order = service.getCurrentOrder().checkDeadline();
|
||||||
|
var user = getUser();
|
||||||
|
var items = service.getItemsByUser(order.id(), user.id());
|
||||||
|
if (items.isEmpty()) {
|
||||||
|
post("Du hast keine Bestellung aufgegeben.");
|
||||||
|
} else {
|
||||||
|
for (var item : items) {
|
||||||
|
service.delete(item);
|
||||||
|
}
|
||||||
|
post("Alle deine Bestellungen wurden storniert.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bestellt die Pizza mit dem angegebenen Namen.
|
||||||
|
*/
|
||||||
|
private void order(@NotNull String nameOrNummer, @Nullable String notes) throws SQLException {
|
||||||
|
var order = service.getCurrentOrder().checkDeadline();
|
||||||
|
var pizza = service.getPizza(order.restaurantId(), nameOrNummer);
|
||||||
|
order0(order, pizza, notes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void order0(@NotNull OrderDetail order, @NotNull Pizza pizza, @Nullable String notes) throws SQLException {
|
||||||
|
var id = service.order(order, getUser(), pizza, notes);
|
||||||
|
post(STR."Pizza \{pizza.name()} wurde zur Bestellung hinzugefügt (Positionsnummer \{id}).");
|
||||||
|
}
|
||||||
|
//</editor-fold>
|
||||||
|
|
||||||
|
private void helpUser() {
|
||||||
|
post("""
|
||||||
|
Marek is back!
|
||||||
|
!marek info - Menü anzeigen
|
||||||
|
|
||||||
|
Bestellung
|
||||||
|
!marek order (NUMMER | NAME) [NOTIZEN] - Pizza bestellen
|
||||||
|
!marek order revoke [NUMMER | NAME | --all] - Bestellung(en) stornieren
|
||||||
|
!marek order list - Bestellungen auflisten
|
||||||
|
""");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void helpAdmin() {
|
||||||
|
post("""
|
||||||
|
!marek --user USER order (NUMMER | NAME) [NOTIZEN] - Pizza bestellen
|
||||||
|
!marek --user USER order revoke [NUMMER | NAME | --all] - Bestellung(en) stornieren
|
||||||
|
!marek --user USER order list - Bestellungen auflisten
|
||||||
|
|
||||||
|
!marek summary [NAME | --all] - Bestellübersicht
|
||||||
|
!marek check [NAME | --all] - Rechnung anzeigen
|
||||||
|
!marek pay [NAME] AMOUNT - Rechnungsbetrag angeben
|
||||||
|
!marek order list --all - Alle Bestellungen auflisten
|
||||||
|
!marek order add (NUMMER | NAME) USER - Fügt USER zur Bestellung mit Nummer NUMMER / von NAME hinzu
|
||||||
|
|
||||||
|
!marek payment confirm NUMMER [USER] - Zahlungseingang bestätigen
|
||||||
|
!marek payment confirm --all USER - Zahlungseingang bestätigen
|
||||||
|
!marek payment void NUMMER [USER] - Widerruf der Bestätigung des Zahlungseingangs
|
||||||
|
""");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void post(@NotNull String message, @Nullable String name, @Nullable Boolean bottag, @Nullable Boolean publicId) {
|
||||||
|
var annoyance = ANNOYANCE.orElse(0d);
|
||||||
|
if (annoyance == 0) {
|
||||||
|
super.post(message, name, bottag, publicId);
|
||||||
|
} else if (annoyance == 1) {
|
||||||
|
super.post(message.toUpperCase(Locale.ROOT), name, bottag, publicId);
|
||||||
|
} else {
|
||||||
|
var infuriatedMessage = message.codePoints()
|
||||||
|
.map(chr -> Math.random() < annoyance ? Character.toUpperCase(chr) : chr)
|
||||||
|
.collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append)
|
||||||
|
.toString();
|
||||||
|
super.post(infuriatedMessage, name, bottag, publicId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isPrimaryChannel(@NotNull String channel) {
|
||||||
|
return primaryChannels == null || primaryChannels.contains(channel);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package eu.jonahbauer.chat.bot.pizza;
|
||||||
|
|
||||||
|
import lombok.experimental.StandardException;
|
||||||
|
|
||||||
|
@StandardException
|
||||||
|
public class PizzaBotException extends RuntimeException {
|
||||||
|
}
|
@ -0,0 +1,298 @@
|
|||||||
|
package eu.jonahbauer.chat.bot.pizza;
|
||||||
|
|
||||||
|
import eu.jonahbauer.chat.bot.database.Database;
|
||||||
|
import eu.jonahbauer.chat.bot.database.NonUniqueResultException;
|
||||||
|
import eu.jonahbauer.chat.bot.pizza.model.*;
|
||||||
|
import eu.jonahbauer.chat.bot.pizza.model.table.*;
|
||||||
|
import eu.jonahbauer.chat.bot.pizza.model.view.OrderDetail;
|
||||||
|
import eu.jonahbauer.chat.bot.pizza.model.view.OrderItemDetail;
|
||||||
|
import eu.jonahbauer.chat.bot.pizza.util.Pair;
|
||||||
|
import org.intellij.lang.annotations.Language;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public class PizzaService {
|
||||||
|
private final @NotNull Database database;
|
||||||
|
|
||||||
|
public PizzaService(@NotNull Database database) {
|
||||||
|
this.database = Objects.requireNonNull(database);
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NotNull OrderDetail getCurrentOrder() throws SQLException {
|
||||||
|
@Language("SQL")
|
||||||
|
var query = "SELECT * FROM order_detail WHERE active = TRUE ORDER BY deadline DESC LIMIT 1";
|
||||||
|
return database.executeUniqueQuery(OrderDetail.class, query)
|
||||||
|
.orElseThrow(() -> new PizzaBotException("Aktuell läuft keine Pizzabestellung."));
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NotNull Pair<@NotNull Restaurant, @NotNull List<@NotNull Pizza>> getRestaurantWithPizzas(long restaurantId) throws SQLException {
|
||||||
|
return database.transaction(() -> {
|
||||||
|
var restaurant = database.findById(Restaurant.class, restaurantId).orElseThrow();
|
||||||
|
var pizzas = database.findAllWhere(Pizza.class, Pizza.Fields.restaurantId, restaurantId);
|
||||||
|
return Pair.of(restaurant, pizzas);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Payment
|
||||||
|
|
||||||
|
public void save(@NotNull Order order) throws SQLException {
|
||||||
|
database.update(order);
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NotNull User getUserByName(@NotNull String name) throws SQLException {
|
||||||
|
try {
|
||||||
|
@Language("SQL")
|
||||||
|
var query = """
|
||||||
|
SELECT "user".* FROM "user"
|
||||||
|
WHERE "user"."username" LIKE ?
|
||||||
|
""";
|
||||||
|
return database.executeUniqueQuery(User.class, query, stmt -> stmt.setString(1, name + "%"))
|
||||||
|
.orElseThrow(() -> new PizzaBotException("Der Benutzer existiert nicht."));
|
||||||
|
} catch (NonUniqueResultException _) {
|
||||||
|
throw new PizzaBotException("Der Benutzer ist nicht eindeutig.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NotNull User getUserByEventAndName(long eventId, @NotNull String name) throws SQLException {
|
||||||
|
try {
|
||||||
|
@Language("SQL")
|
||||||
|
var query = """
|
||||||
|
SELECT "user".* FROM "user"
|
||||||
|
WHERE "user"."username" LIKE ?
|
||||||
|
AND EXISTS(
|
||||||
|
SELECT * FROM "order_item_to_user"
|
||||||
|
JOIN "order_item" ON "order_item_to_user"."order_item_id" = "order_item"."id"
|
||||||
|
JOIN "order" ON "order_item"."order_id" = "order"."id"
|
||||||
|
WHERE "order_item_to_user"."user_id" = "user"."id"
|
||||||
|
AND "order"."event_id" = ?
|
||||||
|
)
|
||||||
|
""";
|
||||||
|
return database.executeUniqueQuery(User.class, query, stmt -> {
|
||||||
|
stmt.setString(1, name + "%");
|
||||||
|
stmt.setLong(2, eventId);
|
||||||
|
}).orElseThrow(() -> new PizzaBotException("Der Benutzer existiert nicht oder hat an keiner Pizzabestellung teilgenommen."));
|
||||||
|
} catch (NonUniqueResultException _) {
|
||||||
|
throw new PizzaBotException("Der Benutzer ist nicht eindeutig.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void paymentByItem(long itemId, boolean payed) throws SQLException {
|
||||||
|
database.executeUpdate("UPDATE order_item_to_user SET payed = ? WHERE order_item_id = ?", stmt -> {
|
||||||
|
stmt.setBoolean(1, payed);
|
||||||
|
stmt.setLong(2, itemId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public int paymentByEventAndUser(long eventId, long userId, boolean payed) throws SQLException {
|
||||||
|
@Language("SQL")
|
||||||
|
var query = """
|
||||||
|
UPDATE "order_item_to_user"
|
||||||
|
SET "payed" = ?
|
||||||
|
WHERE "order_item_to_user"."user_id" = ?
|
||||||
|
AND EXISTS(
|
||||||
|
SELECT *
|
||||||
|
FROM "order_item"
|
||||||
|
JOIN "order" ON "order_item"."order_id" = "order"."id"
|
||||||
|
WHERE "order"."event_id" = ? AND "order_item"."id" = "order_item_to_user"."order_item_id"
|
||||||
|
)
|
||||||
|
""";
|
||||||
|
return database.executeUpdate(query, stmt -> {
|
||||||
|
stmt.setBoolean(1, payed);
|
||||||
|
stmt.setLong(2, userId);
|
||||||
|
stmt.setLong(3, eventId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void paymentByItemAndUser(long itemId, long userId, boolean payed) throws SQLException {
|
||||||
|
database.executeUpdate("UPDATE order_item_to_user SET payed = ? WHERE order_item_id = ? AND user_id = ?", stmt -> {
|
||||||
|
stmt.setBoolean(1, payed);
|
||||||
|
stmt.setLong(2, itemId);
|
||||||
|
stmt.setLong(3, userId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Order
|
||||||
|
|
||||||
|
public @NotNull Optional<Pizza> getPizzaById(long id) throws SQLException {
|
||||||
|
return database.findById(Pizza.class, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NotNull Pizza getPizza(long restaurantId, @NotNull String nameOrNummer) throws SQLException {
|
||||||
|
try {
|
||||||
|
var nummer = Integer.parseInt(nameOrNummer);
|
||||||
|
return getPizzaByNummer(restaurantId, nummer);
|
||||||
|
} catch (NumberFormatException _) {
|
||||||
|
return getPizzaByName(restaurantId, nameOrNummer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private @NotNull Pizza getPizzaByNummer(long restaurantId, int nummer) throws SQLException {
|
||||||
|
@Language("SQL")
|
||||||
|
var query = "SELECT * FROM pizza WHERE restaurant_id = ? AND number = ?";
|
||||||
|
return database.executeUniqueQuery(Pizza.class, query, stmt -> {
|
||||||
|
stmt.setLong(1, restaurantId);
|
||||||
|
stmt.setInt(2, nummer);
|
||||||
|
}).orElseThrow(() -> new PizzaBotException("Diese Pizza kenne ich nicht."));
|
||||||
|
}
|
||||||
|
|
||||||
|
private @NotNull Pizza getPizzaByName(long restaurantId, @NotNull String name) throws SQLException {
|
||||||
|
try {
|
||||||
|
@Language("SQL")
|
||||||
|
var query = "SELECT * FROM pizza WHERE restaurant_id = ? AND name LIKE ?";
|
||||||
|
return database.executeUniqueQuery(Pizza.class, query, stmt -> {
|
||||||
|
stmt.setLong(1, restaurantId);
|
||||||
|
stmt.setString(2, name + "%");
|
||||||
|
}).orElseThrow(() -> new PizzaBotException("Diese Pizza kenne ich nicht."));
|
||||||
|
} catch (NonUniqueResultException _) {
|
||||||
|
throw new PizzaBotException("Diese Pizza kenne ich nicht.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public long order(@NotNull OrderDetail order, @NotNull User user, @NotNull Pizza pizza, @Nullable String notes) throws SQLException {
|
||||||
|
return database.transaction(() -> {
|
||||||
|
try {
|
||||||
|
database.insert(user);
|
||||||
|
} catch (SQLException _) {}
|
||||||
|
|
||||||
|
var item = new OrderItem(-1, order.id(), pizza.id(), notes);
|
||||||
|
var itemId = database.insert(item);
|
||||||
|
|
||||||
|
var mapping = new OrderItemToUser(itemId, user.id(), false);
|
||||||
|
database.insert(mapping);
|
||||||
|
|
||||||
|
return itemId;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add(@NotNull OrderItem item, @NotNull User user, boolean payed) throws SQLException {
|
||||||
|
database.insert(new OrderItemToUser(item.id(), user.id(), payed));
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NotNull List<@NotNull UserWithPayment> getUsersByItem(@NotNull OrderItem item) throws SQLException {
|
||||||
|
@Language("SQL")
|
||||||
|
var query = """
|
||||||
|
SELECT
|
||||||
|
"user"."id" AS "id",
|
||||||
|
"user"."username" AS "username",
|
||||||
|
"order_item_to_user"."order_item_id" AS "order_item_id",
|
||||||
|
"order_item_to_user"."payed" AS "payed"
|
||||||
|
FROM "user"
|
||||||
|
JOIN "order_item_to_user" ON "user"."id" = "order_item_to_user"."user_id"
|
||||||
|
WHERE "order_item_to_user"."order_item_id" = ?
|
||||||
|
""";
|
||||||
|
return database.executeQuery(UserWithPayment.class, query, stmt -> stmt.setLong(1, item.id()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Revoke
|
||||||
|
|
||||||
|
public @NotNull Optional<OrderItemSummary> getItemById(long itemId) throws SQLException {
|
||||||
|
return database.transaction(() -> {
|
||||||
|
var item = database.findById(OrderItem.class, itemId);
|
||||||
|
if (item.isEmpty()) return Optional.empty();
|
||||||
|
|
||||||
|
var pizza = database.findById(Pizza.class, item.get().pizzaId()).orElseThrow();
|
||||||
|
var users = getUsersByItem(item.get());
|
||||||
|
|
||||||
|
return Optional.of(new OrderItemSummary(item.get(), pizza, users));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NotNull Optional<OrderItem> getItemByPizzaName(long orderId, long userId, @NotNull String name) throws SQLException {
|
||||||
|
@Language("SQL")
|
||||||
|
var query = """
|
||||||
|
SELECT "order_item".*
|
||||||
|
FROM "order_item"
|
||||||
|
JOIN "pizza" ON "order_item"."pizza_id" = "pizza"."id"
|
||||||
|
JOIN "order_item_to_user" ON "order_item"."id" = "order_item_to_user"."order_item_id"
|
||||||
|
WHERE "order_item"."order_id" = ? AND "order_item_to_user"."user_id" = ? AND "pizza"."name" LIKE ?
|
||||||
|
""";
|
||||||
|
return database.executeUniqueQuery(OrderItem.class, query, stmt -> {
|
||||||
|
stmt.setLong(1, orderId);
|
||||||
|
stmt.setLong(2, userId);
|
||||||
|
stmt.setString(3, name + "%");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NotNull Optional<OrderItem> getItemByUser(long orderId, long userId) throws SQLException {
|
||||||
|
@Language("SQL")
|
||||||
|
var query = """
|
||||||
|
SELECT "order_item".*
|
||||||
|
FROM "order_item"
|
||||||
|
JOIN "order_item_to_user" ON "order_item"."id" = "order_item_to_user"."order_item_id"
|
||||||
|
WHERE "order_item"."order_id" = ? AND "order_item_to_user"."user_id" = ?
|
||||||
|
""";
|
||||||
|
return database.executeUniqueQuery(OrderItem.class, query, stmt -> {
|
||||||
|
stmt.setLong(1, orderId);
|
||||||
|
stmt.setLong(2, userId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NotNull List<@NotNull OrderItem> getItemsByUser(long orderId, long userId) throws SQLException {
|
||||||
|
@Language("SQL")
|
||||||
|
var query = """
|
||||||
|
SELECT "order_item".*
|
||||||
|
FROM "order_item"
|
||||||
|
JOIN "order_item_to_user" ON "order_item"."id" = "order_item_to_user"."order_item_id"
|
||||||
|
WHERE "order_item"."order_id" = ? AND "order_item_to_user"."user_id" = ?
|
||||||
|
""";
|
||||||
|
return database.executeQuery(OrderItem.class, query, stmt -> {
|
||||||
|
stmt.setLong(1, orderId);
|
||||||
|
stmt.setLong(2, userId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean delete(@NotNull OrderItem item) throws SQLException {
|
||||||
|
return database.delete(OrderItem.class, item.id());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Summary
|
||||||
|
|
||||||
|
public @NotNull Order getOrderByEventAndName(long eventId, @NotNull String name) throws SQLException {
|
||||||
|
try {
|
||||||
|
@Language("SQL")
|
||||||
|
var query = """
|
||||||
|
SELECT "order".*
|
||||||
|
FROM "order"
|
||||||
|
WHERE "order"."event_id" = ? AND "order"."name" LIKE ?
|
||||||
|
""";
|
||||||
|
|
||||||
|
return database.executeUniqueQuery(Order.class, query, stmt -> {
|
||||||
|
stmt.setLong(1, eventId);
|
||||||
|
stmt.setString(2, name + "%");
|
||||||
|
}).orElseThrow(() -> new PizzaBotException("Diese Bestellung kenne ich nicht."));
|
||||||
|
} catch (NonUniqueResultException ex) {
|
||||||
|
throw new PizzaBotException("Die Bestellung ist nicht eindeutig.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Order List
|
||||||
|
|
||||||
|
public @NotNull List<@NotNull OrderItemDetail> getItemDetailByEvent(long eventId) throws SQLException {
|
||||||
|
return database.findAllWhere(OrderItemDetail.class, OrderItemDetail.Fields.eventId, eventId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NotNull List<@NotNull OrderItemDetail> getItemDetailByOrder(long orderId) throws SQLException {
|
||||||
|
return database.findAllWhere(OrderItemDetail.class, OrderItemDetail.Fields.orderId, orderId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NotNull List<@NotNull OrderItemDetail> getItemDetailByEventAndUser(long eventId, long userId) throws SQLException {
|
||||||
|
@Language("SQL")
|
||||||
|
var query = """
|
||||||
|
SELECT *
|
||||||
|
FROM order_item_detail
|
||||||
|
WHERE event_id = ? AND EXISTS(
|
||||||
|
SELECT * FROM "order_item_to_user"
|
||||||
|
WHERE "user_id" = ? AND "order_item_id" = "order_item_detail"."id"
|
||||||
|
)
|
||||||
|
""";
|
||||||
|
return database.executeQuery(OrderItemDetail.class, query, stmt -> {
|
||||||
|
stmt.setLong(1, eventId);
|
||||||
|
stmt.setLong(2, userId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
package eu.jonahbauer.chat.bot.pizza.model;
|
||||||
|
|
||||||
|
import eu.jonahbauer.chat.bot.pizza.PizzaBotException;
|
||||||
|
import eu.jonahbauer.chat.bot.pizza.model.table.OrderItem;
|
||||||
|
import eu.jonahbauer.chat.bot.pizza.model.table.Pizza;
|
||||||
|
import eu.jonahbauer.chat.bot.pizza.model.table.User;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public record OrderItemSummary(@NotNull OrderItem item, @NotNull Pizza pizza, @NotNull List<UserWithPayment> users) {
|
||||||
|
public @NotNull OrderItemSummary checkUserAccess(@NotNull User user) {
|
||||||
|
if (users.stream().noneMatch(u -> u.id() == user.id())) {
|
||||||
|
throw new PizzaBotException("Du bist nicht berechtigt, diese Bestellung zu bearbeiten.");
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
package eu.jonahbauer.chat.bot.pizza.model;
|
||||||
|
|
||||||
|
import lombok.experimental.FieldNameConstants;
|
||||||
|
|
||||||
|
@FieldNameConstants
|
||||||
|
public record OrderItemToUser(long orderItemId, long userId, long parts, boolean payed) {
|
||||||
|
public OrderItemToUser(long orderItemId, long userId, boolean payed) {
|
||||||
|
this(orderItemId, userId, 1, payed);
|
||||||
|
}
|
||||||
|
|
||||||
|
public OrderItemToUser withPayed(boolean payed) {
|
||||||
|
return new OrderItemToUser(orderItemId, userId, parts, payed);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
package eu.jonahbauer.chat.bot.pizza.model;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import static eu.jonahbauer.chat.bot.pizza.util.FormatUtils.tabular;
|
||||||
|
|
||||||
|
public record Receipt(double total, double tipped, double pending, double pendingTipped) {
|
||||||
|
public Receipt(double total, boolean payed, double tip) {
|
||||||
|
this(total, total * tip, payed ? 0 : total, payed ? 0 : total * tip);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean payed() {
|
||||||
|
return pending == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean unpayed() {
|
||||||
|
return total == pending;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NotNull String totalAsString() {
|
||||||
|
return tabular(total, 0) + " €" + (tipped != 0 ? " (" + tabular(tipped, 0) + " €)" : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NotNull String pendingAsString() {
|
||||||
|
return tabular(pending, 0) + " €" + (pendingTipped != 0 ? " (" + tabular(pendingTipped, 0) + " €)" : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static @NotNull Receipt empty() {
|
||||||
|
return new Receipt(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static @NotNull Receipt sum(@NotNull Receipt first, @NotNull Receipt second) {
|
||||||
|
return new Receipt(
|
||||||
|
first.total + second.total,
|
||||||
|
first.tipped + second.tipped,
|
||||||
|
first.pending + second.pending,
|
||||||
|
first.pendingTipped + second.pendingTipped
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package eu.jonahbauer.chat.bot.pizza.model;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
public record UserWithPayment(long id, @NotNull String username, long orderItemId, boolean payed) {
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
package eu.jonahbauer.chat.bot.pizza.model.table;
|
||||||
|
|
||||||
|
import eu.jonahbauer.chat.bot.database.Entity;
|
||||||
|
import lombok.experimental.FieldNameConstants;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
@FieldNameConstants
|
||||||
|
public record Event(long id, @NotNull String name, long adminId, boolean active) implements Entity<Event> {
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
package eu.jonahbauer.chat.bot.pizza.model.table;
|
||||||
|
|
||||||
|
import eu.jonahbauer.chat.bot.database.Entity;
|
||||||
|
import lombok.experimental.FieldNameConstants;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@FieldNameConstants
|
||||||
|
public record Order(
|
||||||
|
long id,
|
||||||
|
long eventId,
|
||||||
|
long restaurantId,
|
||||||
|
@NotNull String name,
|
||||||
|
@NotNull Instant deadline,
|
||||||
|
@Nullable Double payment
|
||||||
|
) implements Entity<Order>, Comparable<Order> {
|
||||||
|
|
||||||
|
public Order {
|
||||||
|
Objects.requireNonNull(deadline);
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NotNull Order withPayment(@Nullable Double payment) {
|
||||||
|
return new Order(id, eventId, restaurantId, name, deadline, payment);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(@NotNull Order o) {
|
||||||
|
return this.deadline.compareTo(o.deadline);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
package eu.jonahbauer.chat.bot.pizza.model.table;
|
||||||
|
|
||||||
|
import eu.jonahbauer.chat.bot.database.Entity;
|
||||||
|
import lombok.experimental.FieldNameConstants;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
@FieldNameConstants
|
||||||
|
public record OrderItem(
|
||||||
|
long id,
|
||||||
|
long orderId,
|
||||||
|
long pizzaId,
|
||||||
|
@Nullable String notes
|
||||||
|
) implements Entity<OrderItem> {
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
package eu.jonahbauer.chat.bot.pizza.model.table;
|
||||||
|
|
||||||
|
import eu.jonahbauer.chat.bot.database.Entity;
|
||||||
|
import lombok.experimental.FieldNameConstants;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import static eu.jonahbauer.chat.bot.pizza.util.FormatUtils.tabular;
|
||||||
|
|
||||||
|
@FieldNameConstants
|
||||||
|
public record Pizza(
|
||||||
|
long id,
|
||||||
|
long restaurantId,
|
||||||
|
int number,
|
||||||
|
@NotNull String name,
|
||||||
|
@NotNull String ingredients,
|
||||||
|
double price
|
||||||
|
) implements Entity<Pizza>, Comparable<Pizza> {
|
||||||
|
|
||||||
|
public @NotNull String toPrettyString() {
|
||||||
|
var number = tabular(number());
|
||||||
|
var price = tabular(price(), 5);
|
||||||
|
var name = name().toUpperCase(Locale.ROOT);
|
||||||
|
var ingredients = ingredients();
|
||||||
|
return STR."\{number} | \{price} € | \{name} \{ingredients}";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(@NotNull Pizza o) {
|
||||||
|
return Integer.compare(number, o.number);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
package eu.jonahbauer.chat.bot.pizza.model.table;
|
||||||
|
|
||||||
|
import eu.jonahbauer.chat.bot.database.Entity;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public record Restaurant(
|
||||||
|
long id,
|
||||||
|
@NotNull String name,
|
||||||
|
@NotNull String city,
|
||||||
|
@Nullable String phone,
|
||||||
|
@Nullable String notes
|
||||||
|
) implements Entity<Restaurant> {
|
||||||
|
public Restaurant {
|
||||||
|
Objects.requireNonNull(name);
|
||||||
|
Objects.requireNonNull(city);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
package eu.jonahbauer.chat.bot.pizza.model.table;
|
||||||
|
|
||||||
|
import lombok.experimental.FieldNameConstants;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@FieldNameConstants
|
||||||
|
public record User(long id, @NotNull String username) {
|
||||||
|
public User {
|
||||||
|
Objects.requireNonNull(username);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
package eu.jonahbauer.chat.bot.pizza.model.view;
|
||||||
|
|
||||||
|
import eu.jonahbauer.chat.bot.pizza.PizzaBotException;
|
||||||
|
import eu.jonahbauer.chat.bot.pizza.model.table.Order;
|
||||||
|
import eu.jonahbauer.chat.bot.pizza.model.table.User;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
public record OrderDetail(
|
||||||
|
// Order
|
||||||
|
long id,
|
||||||
|
@NotNull String name,
|
||||||
|
@NotNull Instant deadline,
|
||||||
|
@Nullable Double payment,
|
||||||
|
// Event
|
||||||
|
long eventId,
|
||||||
|
@NotNull String eventName,
|
||||||
|
long adminId,
|
||||||
|
// Restaurant
|
||||||
|
long restaurantId,
|
||||||
|
@NotNull String restaurantName
|
||||||
|
) {
|
||||||
|
public @NotNull Order order() {
|
||||||
|
return new Order(id, eventId, restaurantId, name, deadline, payment);
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NotNull OrderDetail checkDeadline() {
|
||||||
|
if (Instant.now().isAfter(deadline())) {
|
||||||
|
throw new PizzaBotException("Du kannst diese Bestellung nicht mehr bearbeiten, da der Bestellannahmeschluss schon vorbei ist.");
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NotNull OrderDetail checkAdminAccess(@NotNull User user) {
|
||||||
|
if (adminId() != user.id()) {
|
||||||
|
throw new PizzaBotException("Du bist nicht berechtigt, diese Aktion durchzuführen.");
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,79 @@
|
|||||||
|
package eu.jonahbauer.chat.bot.pizza.model.view;
|
||||||
|
|
||||||
|
import eu.jonahbauer.chat.bot.pizza.model.Receipt;
|
||||||
|
import eu.jonahbauer.chat.bot.pizza.model.table.Order;
|
||||||
|
import eu.jonahbauer.chat.bot.pizza.model.table.Pizza;
|
||||||
|
import eu.jonahbauer.chat.bot.pizza.model.table.User;
|
||||||
|
import lombok.experimental.FieldNameConstants;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import static eu.jonahbauer.chat.bot.pizza.util.FormatUtils.strikethrough;
|
||||||
|
import static eu.jonahbauer.chat.bot.pizza.util.FormatUtils.tabular;
|
||||||
|
|
||||||
|
@FieldNameConstants
|
||||||
|
public record OrderItemDetail(
|
||||||
|
// OrderItem
|
||||||
|
long id,
|
||||||
|
@Nullable String notes,
|
||||||
|
// Order
|
||||||
|
long orderId,
|
||||||
|
@NotNull String orderName,
|
||||||
|
@NotNull Instant orderDeadline,
|
||||||
|
@Nullable Double orderPayment,
|
||||||
|
// Event
|
||||||
|
long eventId,
|
||||||
|
// Restaurant
|
||||||
|
long restaurantId,
|
||||||
|
@NotNull String restaurantName,
|
||||||
|
// Pizza
|
||||||
|
long pizzaId,
|
||||||
|
int pizzaNumber,
|
||||||
|
@NotNull String pizzaName,
|
||||||
|
double pizzaPrice,
|
||||||
|
// User
|
||||||
|
long userId,
|
||||||
|
@NotNull String userName,
|
||||||
|
long userParts,
|
||||||
|
boolean userPayed,
|
||||||
|
long userCount
|
||||||
|
) {
|
||||||
|
public @NotNull Order order() {
|
||||||
|
return new Order(orderId, eventId, restaurantId, orderName, orderDeadline, orderPayment);
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NotNull Pizza pizza() {
|
||||||
|
return new Pizza(pizzaId, restaurantId, pizzaNumber, pizzaName, "N/A", pizzaPrice);
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NotNull User user() {
|
||||||
|
return new User(userId, userName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NotNull Receipt receipt(double tip) {
|
||||||
|
return new Receipt(userPrice(), userPayed(), tip);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double userPrice() {
|
||||||
|
return userParts() * pizzaPrice() / userCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NotNull String toPrettyString() {
|
||||||
|
String price;
|
||||||
|
if (userCount() == 1) {
|
||||||
|
price = tabular(pizzaPrice(), 0) + " €";
|
||||||
|
} else {
|
||||||
|
price = tabular(pizzaPrice(), 0) + " € / " + tabular(userCount(), 0) + " = " + tabular(userPrice(), 0) + " €";
|
||||||
|
}
|
||||||
|
if (userPayed()) price = strikethrough(price);
|
||||||
|
|
||||||
|
if (notes() != null) {
|
||||||
|
return STR."\{id()} | \{pizzaName().toUpperCase(Locale.ROOT)} \{notes()} | \{price}";
|
||||||
|
} else {
|
||||||
|
return STR."\{id()} | \{pizzaName().toUpperCase(Locale.ROOT)} | \{price}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,90 @@
|
|||||||
|
package eu.jonahbauer.chat.bot.pizza.util;
|
||||||
|
|
||||||
|
import eu.jonahbauer.chat.bot.pizza.PizzaBotException;
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@UtilityClass
|
||||||
|
public class ArgumentParser {
|
||||||
|
public static @NotNull List<@NotNull String> parse(@NotNull String string) {
|
||||||
|
var list = new ArrayList<String>();
|
||||||
|
var quote = (char) 0;
|
||||||
|
var escaped = false;
|
||||||
|
|
||||||
|
var current = new StringBuilder();
|
||||||
|
var it = string.chars().iterator();
|
||||||
|
while (it.hasNext()) {
|
||||||
|
var chr = (char) it.nextInt();
|
||||||
|
if (escaped) {
|
||||||
|
if (chr != '\n') current.append(chr);
|
||||||
|
} else if (chr == '\\' && quote != '\'') {
|
||||||
|
escaped = true;
|
||||||
|
} else if (quote == 0 && chr == '"') {
|
||||||
|
quote = '"';
|
||||||
|
} else if (quote == 0 && chr == '\'') {
|
||||||
|
quote = '\'';
|
||||||
|
} else if (quote == chr) {
|
||||||
|
quote = 0;
|
||||||
|
} else if (chr == ' ' || chr == '\t') {
|
||||||
|
if (!current.isEmpty()) {
|
||||||
|
list.add(current.toString());
|
||||||
|
current.setLength(0);
|
||||||
|
}
|
||||||
|
} else if (chr == '\n') {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
current.append(chr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (escaped) {
|
||||||
|
throw new IllegalArgumentException("incomplete escape sequence");
|
||||||
|
} else if (quote != 0) {
|
||||||
|
throw new IllegalArgumentException("incomplete quote");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!current.isEmpty()) {
|
||||||
|
list.add(current.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return Collections.unmodifiableList(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isLong(@NotNull String string) {
|
||||||
|
try {
|
||||||
|
Long.parseLong(string);
|
||||||
|
return true;
|
||||||
|
} catch (NumberFormatException _) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long toLong(@NotNull String string) {
|
||||||
|
try {
|
||||||
|
return Long.parseLong(string);
|
||||||
|
} catch (NumberFormatException ex) {
|
||||||
|
throw new PizzaBotException(string + " ist keine gültige Zahl.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isDouble(@NotNull String string) {
|
||||||
|
try {
|
||||||
|
Double.parseDouble(string);
|
||||||
|
return true;
|
||||||
|
} catch (NumberFormatException _) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double toDouble(@NotNull String string) {
|
||||||
|
try {
|
||||||
|
return Double.parseDouble(string);
|
||||||
|
} catch (NumberFormatException ex) {
|
||||||
|
throw new PizzaBotException(string + " ist keine gültige Zahl.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,82 @@
|
|||||||
|
package eu.jonahbauer.chat.bot.pizza.util;
|
||||||
|
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.TestOnly;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@UtilityClass
|
||||||
|
public class FormatUtils {
|
||||||
|
public static @NotNull String tabular(long number) {
|
||||||
|
return tabular(number, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static @NotNull String tabular(long number, int width) {
|
||||||
|
return tabular(String.format(Locale.ROOT, "%" + (width == 0 ? "" : width) + "d", number));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static @NotNull String tabular(double number) {
|
||||||
|
return tabular(number, 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static @NotNull String tabular(double number, int width) {
|
||||||
|
return tabular(String.format(Locale.ROOT, "%" + (width == 0 ? "" : width) + ".2f", number));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static @NotNull String tabular(@NotNull String string) {
|
||||||
|
var out = new StringBuilder();
|
||||||
|
for (int i = 0, length = string.length(); i < length; i++) {
|
||||||
|
var chr = string.charAt(i);
|
||||||
|
if (chr == ' ') {
|
||||||
|
out.append('\u2007'); // figure space
|
||||||
|
} else {
|
||||||
|
out.append('\u2060'); // word-joiner to prevent kerning
|
||||||
|
out.append(chr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("UnnecessaryUnicodeEscape")
|
||||||
|
public static @NotNull String strikethrough(@NotNull String string) {
|
||||||
|
var out = new StringBuilder();
|
||||||
|
for (int i = 0, length = string.length(); i < length; i++) {
|
||||||
|
out.append('\u0336').append(string.charAt(i));
|
||||||
|
}
|
||||||
|
out.append('\u0335');
|
||||||
|
return out.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestOnly
|
||||||
|
@SuppressWarnings("UnnecessaryUnicodeEscape")
|
||||||
|
public static @NotNull String strip(@NotNull String string) {
|
||||||
|
var out = new StringBuilder();
|
||||||
|
for (int i = 0, length = string.length(); i < length; i++) {
|
||||||
|
var chr = string.charAt(i);
|
||||||
|
if (chr != '\u0336' && chr != '\u0335' && chr != '\u2007' && chr != '\u2060') {
|
||||||
|
out.append(chr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static @NotNull String join(@NotNull List<@NotNull String> strings) {
|
||||||
|
return switch (strings.size()) {
|
||||||
|
case 0 -> "";
|
||||||
|
case 1 -> strings.getFirst();
|
||||||
|
default -> String.join(", ", strings.subList(0, strings.size() - 1)) + " und " + strings.getLast();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> @NotNull String join(@NotNull List<@NotNull T> items, @NotNull Function<? super T, String> toString) {
|
||||||
|
return join(items.stream().map(toString).toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> @NotNull String join(@NotNull String separator, @NotNull List<@NotNull T> items, @NotNull Function<? super T, String> toString) {
|
||||||
|
return items.stream().map(toString).collect(Collectors.joining(separator));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
package eu.jonahbauer.chat.bot.pizza.util;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
public record Pair<F, S>(F first, S second) {
|
||||||
|
public static <F, S> @NotNull Pair<F, S> of(F first, S second) {
|
||||||
|
return new Pair<>(first, second);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
import eu.jonahbauer.chat.bot.api.ChatBot;
|
||||||
|
import eu.jonahbauer.chat.bot.pizza.PizzaBot;
|
||||||
|
|
||||||
|
module eu.jonahbauer.chat.bot.pizza {
|
||||||
|
exports eu.jonahbauer.chat.bot.pizza.model.table to eu.jonahbauer.chat.bot.database;
|
||||||
|
exports eu.jonahbauer.chat.bot.pizza.model.view to eu.jonahbauer.chat.bot.database;
|
||||||
|
exports eu.jonahbauer.chat.bot.pizza.model to eu.jonahbauer.chat.bot.database;
|
||||||
|
|
||||||
|
requires eu.jonahbauer.chat.bot.api;
|
||||||
|
requires eu.jonahbauer.chat.bot.database;
|
||||||
|
|
||||||
|
requires org.slf4j;
|
||||||
|
requires static lombok;
|
||||||
|
|
||||||
|
provides ChatBot with PizzaBot;
|
||||||
|
}
|
@ -0,0 +1,115 @@
|
|||||||
|
CREATE TABLE "user"
|
||||||
|
(
|
||||||
|
"id" INTEGER PRIMARY KEY,
|
||||||
|
"username" VARCHAR(255) NOT NULL,
|
||||||
|
CONSTRAINT "uq_username" UNIQUE ("username")
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE "restaurant"
|
||||||
|
(
|
||||||
|
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
"name" VARCHAR(255) NOT NULL,
|
||||||
|
"city" VARCHAR(255) NOT NULL,
|
||||||
|
"phone" VARCHAR(255) NULL DEFAULT NULL,
|
||||||
|
"notes" VARCHAR(255) NULL DEFAULT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE "pizza"
|
||||||
|
(
|
||||||
|
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
"restaurant_id" INTEGER NOT NULL,
|
||||||
|
"number" INTEGER NOT NULL,
|
||||||
|
"name" VARCHAR(255) NOT NULL,
|
||||||
|
"ingredients" VARCHAR(255) NOT NULL,
|
||||||
|
"price" VARCHAR(255) NOT NULL,
|
||||||
|
CONSTRAINT "fk_pizza_restaurant" FOREIGN KEY ("restaurant_id") REFERENCES "restaurant" ("id") ON DELETE CASCADE,
|
||||||
|
CONSTRAINT "uq_pizza_number" UNIQUE ("restaurant_id", "number")
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE "event"
|
||||||
|
(
|
||||||
|
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
"name" VARCHAR(255) NOT NULL,
|
||||||
|
"admin_id" INTEGER NOT NULL,
|
||||||
|
"active" BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
CONSTRAINT "fk_event_admin" FOREIGN KEY ("admin_id") REFERENCES "user" ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX "uq_event_active" ON "event" ("active")
|
||||||
|
WHERE "active" = TRUE;
|
||||||
|
|
||||||
|
CREATE TABLE "order"
|
||||||
|
(
|
||||||
|
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
"event_id" INTEGER NOT NULL,
|
||||||
|
"restaurant_id" INTEGER NOT NULL,
|
||||||
|
"name" VARCHAR(255) NOT NULL,
|
||||||
|
"deadline" TIMESTAMP NOT NULL,
|
||||||
|
"payment" DOUBLE NULL,
|
||||||
|
CONSTRAINT "fk_order_event" FOREIGN KEY ("event_id") REFERENCES "event" ("id"),
|
||||||
|
CONSTRAINT "fk_order_restaurant" FOREIGN KEY ("restaurant_id") REFERENCES "restaurant" ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE "order_item"
|
||||||
|
(
|
||||||
|
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
"order_id" INTEGER NOT NULL,
|
||||||
|
"pizza_id" INTEGER NOT NULL,
|
||||||
|
"notes" VARCHAR(255),
|
||||||
|
CONSTRAINT "fk_order_item_order" FOREIGN KEY ("order_id") REFERENCES "order" ("id") ON DELETE CASCADE,
|
||||||
|
CONSTRAINT "fk_order_item_pizza" FOREIGN KEY ("pizza_id") REFERENCES "pizza" ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE "order_item_to_user"
|
||||||
|
(
|
||||||
|
"order_item_id" INTEGER NOT NULL,
|
||||||
|
"user_id" INTEGER NOT NULL,
|
||||||
|
"parts" INTEGER NOT NULL DEFAULT 1,
|
||||||
|
"payed" BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
PRIMARY KEY ("order_item_id", "user_id"),
|
||||||
|
CONSTRAINT "fk_order_item_to_user_order_item" FOREIGN KEY ("order_item_id") REFERENCES "order_item" ("id") ON DELETE CASCADE,
|
||||||
|
CONSTRAINT "fk_order_item_to_user_user_id" FOREIGN KEY ("user_id") REFERENCES "user" ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE VIEW "order_item_detail"
|
||||||
|
AS
|
||||||
|
SELECT "order_item"."id",
|
||||||
|
"order_item"."notes",
|
||||||
|
"order"."id" AS "order_id",
|
||||||
|
"order"."name" AS "order_name",
|
||||||
|
"order"."deadline" AS "order_deadline",
|
||||||
|
"order"."payment" AS "order_payment",
|
||||||
|
"order"."event_id" AS "event_id",
|
||||||
|
"restaurant"."id" AS "restaurant_id",
|
||||||
|
"restaurant"."name" AS "restaurant_name",
|
||||||
|
"pizza"."id" AS "pizza_id",
|
||||||
|
"pizza"."number" AS "pizza_number",
|
||||||
|
"pizza"."name" AS "pizza_name",
|
||||||
|
"pizza"."price" AS "pizza_price",
|
||||||
|
"order_item_to_user"."user_id" AS "user_id",
|
||||||
|
"order_item_to_user"."payed" AS "user_payed",
|
||||||
|
"order_item_to_user"."parts" AS "user_parts",
|
||||||
|
"user"."username" AS "user_name",
|
||||||
|
(SELECT SUM("parts") FROM "order_item_to_user" WHERE "order_item_id" = "order_item"."id") AS "user_count"
|
||||||
|
FROM "order"
|
||||||
|
JOIN "restaurant" ON "order"."restaurant_id" = "restaurant"."id"
|
||||||
|
JOIN "order_item" ON "order"."id" = "order_item"."order_id"
|
||||||
|
JOIN "pizza" ON "order_item"."pizza_id" = "pizza"."id"
|
||||||
|
JOIN "order_item_to_user" ON "order_item"."id" = "order_item_to_user"."order_item_id"
|
||||||
|
JOIN "user" ON "order_item_to_user"."user_id" = "user"."id";
|
||||||
|
|
||||||
|
CREATE VIEW "order_detail"
|
||||||
|
AS
|
||||||
|
SELECT "order"."id",
|
||||||
|
"order"."name",
|
||||||
|
"order"."deadline",
|
||||||
|
"order"."payment",
|
||||||
|
"event"."id" AS "event_id",
|
||||||
|
"event"."name" AS "event_name",
|
||||||
|
"event"."admin_id",
|
||||||
|
"event"."active",
|
||||||
|
"restaurant"."id" AS "restaurant_id",
|
||||||
|
"restaurant"."name" AS "restaurant_name"
|
||||||
|
FROM "event"
|
||||||
|
JOIN "order" ON "event"."id" = "order"."event_id"
|
||||||
|
JOIN "restaurant" ON "order"."restaurant_id" = "restaurant"."id";
|
@ -0,0 +1,38 @@
|
|||||||
|
INSERT INTO "restaurant"("name", "city", "phone", "notes")
|
||||||
|
VALUES ('Hell''s Pizza', 'Sonthofen', '08321 7881930', '2023');
|
||||||
|
|
||||||
|
WITH temp(id) AS (SELECT last_insert_rowid())
|
||||||
|
INSERT INTO "pizza"("restaurant_id", "number", "name", "ingredients", "price")
|
||||||
|
SELECT * FROM temp CROSS JOIN (
|
||||||
|
VALUES (10, 'Pizzabrot', 'mit Tomaten & Knoblauch', 6.50),
|
||||||
|
(11, 'Margherita', 'mit Tomaten, Mozzarella & Basilikum', 9.50),
|
||||||
|
(12, 'Salami', 'mit Tomaten, Mozzarella & Salami (gegen Aufpreis mit Rindersalami)', 10.00),
|
||||||
|
(13, 'Prosciutto', 'mit Tomaten, Mozzarella & Schinken', 10.00),
|
||||||
|
(14, 'Salsiccia', 'mit Tomaten, Mozzarella & scharfer Salami', 9.50),
|
||||||
|
(15, 'Funghi', 'mit Tomaten, Mozzarella & Champignons', 9.50),
|
||||||
|
(16, 'Romana', 'mit Tomaten, Mozzarella, Salami & Champignons', 10.50),
|
||||||
|
(17, 'Hawaii', 'mit Tomaten, Mozzarella, Schinken & Ananas', 10.50),
|
||||||
|
(18, 'Regina', 'mit Tomaten, Mozzarella, Schinken & Champignons', 10.50),
|
||||||
|
(19, 'Toscana', 'mit Tomaten, Mozzarella, Salami, Champignons & Peperoni', 11.00),
|
||||||
|
(20, 'Tonno', 'mit Tomaten, Mozzarella, Thunfisch, Zwiebeln & Oliven', 11.00),
|
||||||
|
(21, 'Quattro Formaggi', 'mit Tomaten & vier Käsesorten', 10.50),
|
||||||
|
(22, 'Calzone', 'mit Tomaten, Mozzarella, Schinken & Champignons', 10.50),
|
||||||
|
(24, 'Diavolo', 'mit Tomaten, Mozzarella, scharfer Salami, Peperoni & Oliven', 11.00),
|
||||||
|
(25, 'Rucoletta', 'mit Tomaten, Mozzarella, Rucola, Tomatenstücken & Parmesan', 11.00),
|
||||||
|
(26, 'Quattro Stagioni', 'mit Tomaten, Mozzarella, Schinken, Salami, Champignons, Artischocken, Sardellen & Oliven', 11.00),
|
||||||
|
(27, 'Vegana', 'mit Tomaten & verschiedenem Gemüse', 10.50),
|
||||||
|
(28, 'Siciliana', 'mit Tomaten, Mozzarella, Peperoni, Kapern, Sardellen, Oliven & Knoblauch', 10.00),
|
||||||
|
(29, 'Rustica', 'mit Tomaten, Mozzarella, Speck, Champignons, Gorgonzola & Knoblauch', 11.00),
|
||||||
|
(30, 'Caprese', 'mit Tomaten, Mozzarella, Tomatenscheiben & Basilikum', 11.00),
|
||||||
|
(31, 'Calabria', 'mit Tomaten, Mozzarella, scharfer Salami, Zwiebeln & original Schafskäse', 12.00),
|
||||||
|
(32, 'Verdura', 'mit Tomaten, Mozzarella, gegrilltem Gemüse & Knoblauch', 12.00),
|
||||||
|
(33, 'Mare', 'mit Tomaten, Meeresfrüchten & Knoblauch', 13.00),
|
||||||
|
(35, 'Mafiosa', 'mit Tomaten, Mozzarella, Speck, scharfer Salami, Champignons, Spinat & Peperoni', 12.00),
|
||||||
|
(36, 'Reale', 'mit Tomaten, Mozzarella, original Parmaschinken, Rucola & Parmesan', 13.90),
|
||||||
|
(38, 'Mediterranea', 'mit Tomatenscheiben, Mozzarella, Thunfisch, Kapern & Rucola', 12.00),
|
||||||
|
(39, 'Tricolore', 'mit Büffel-Mozzarella, Tomatenscheiben & Rucola-Pesto', 13.00),
|
||||||
|
(40, 'Alfio Spezial', 'mit Mozzarella, Tomatenstücken, Salsiccia, Zwiebeln, Parmesan, Oliven & Basilikum', 12.00),
|
||||||
|
(41, 'Allgäuer', 'mit Tomaten, Mozzarella, Speck, Bergkäse, Röstzwiebeln & Knoblauch', 11.50),
|
||||||
|
(42, 'Buon Gustaio', 'mit Tomaten, Mozzarella, Spinat, Gorgonzola & Knoblauch', 11.00),
|
||||||
|
(44, 'Mista', 'mit Tomaten, Mozzarella, Schinken, Salami, Pilzen & Thunfisch', 11.00)
|
||||||
|
);
|
@ -0,0 +1,46 @@
|
|||||||
|
-- Hell's Pizza Sonthofen
|
||||||
|
-- Stand: Oktober 2023
|
||||||
|
INSERT INTO "restaurant"("name", "city", "phone", "notes")
|
||||||
|
VALUES ('Hell''s Pizza', 'Sonthofen', '08321 7881930', '2024');
|
||||||
|
|
||||||
|
WITH temp(id) AS (SELECT last_insert_rowid())
|
||||||
|
INSERT INTO "pizza"("restaurant_id", "number", "name", "ingredients", "price")
|
||||||
|
SELECT * FROM temp CROSS JOIN (
|
||||||
|
VALUES (10, 'Pizzabrot', 'mit Tomaten & Knoblauch', 7.00),
|
||||||
|
(11, 'Margherita', 'mit Tomaten, Mozzarella & Basilikum', 10.00),
|
||||||
|
(12, 'Salami', 'mit Tomaten, Mozzarella & Salami (gegen Aufpreis mit Rindersalami)', 10.50),
|
||||||
|
(13, 'Prosciutto', 'mit Tomaten, Mozzarella & Schinken', 10.50),
|
||||||
|
(14, 'Salsiccia', 'mit Tomaten, Mozzarella & scharfer Salami', 10.50),
|
||||||
|
(15, 'Funghi', 'mit Tomaten, Mozzarella & Champignons', 10.00),
|
||||||
|
(16, 'Romana', 'mit Tomaten, Mozzarella, Salami & Champignons', 11.00),
|
||||||
|
(17, 'Hawaii', 'mit Tomaten, Mozzarella, Schinken & Ananas', 11.00),
|
||||||
|
(18, 'Regina', 'mit Tomaten, Mozzarella, Schinken & Champignons', 11.00),
|
||||||
|
(19, 'Toscana', 'mit Tomaten, Mozzarella, Salami, Champignons & Peperoni', 11.50),
|
||||||
|
(20, 'Tonno', 'mit Tomaten, Mozzarella, Thunfisch, Zwiebeln & Oliven', 11.50),
|
||||||
|
(21, 'Quattro Formaggi', 'mit Tomaten & vier Käsesorten', 11.00),
|
||||||
|
(22, 'Calzone', 'mit Tomaten, Mozzarella, Schinken & Champignons', 11.00),
|
||||||
|
(23, 'Capriccio', 'mit Tomaten, Mozzarella, Schinken, Champignons, Artischocken, Eier', 13.00),
|
||||||
|
(24, 'Diavolo', 'mit Tomaten, Mozzarella, scharfer Salami, Peperoni & Oliven', 11.50),
|
||||||
|
(25, 'Rucoletta', 'mit Tomaten, Mozzarella, Rucola, Tomatenstücken & Parmesan', 12.00),
|
||||||
|
(26, 'Quattro Stagioni', 'mit Tomaten, Mozzarella, Schinken, Salami, Champignons, Artischocken, Sardellen & Oliven', 12.90),
|
||||||
|
(27, 'Vegana', 'mit Tomaten & verschiedenem Gemüse', 11.00),
|
||||||
|
(28, 'Siciliana', 'mit Tomaten, Mozzarella, Peperoni, Kapern, Sardellen, Oliven & Knoblauch', 10.50),
|
||||||
|
(29, 'Rustica', 'mit Tomaten, Mozzarella, Speck, Champignons, Gorgonzola & Knoblauch', 11.50),
|
||||||
|
(30, 'Caprese', 'mit Tomaten, Mozzarella, Tomatenscheiben & Basilikum', 11.50),
|
||||||
|
(31, 'Calabria', 'mit Tomaten, Mozzarella, scharfer Salami, Zwiebeln & original Schafskäse', 12.00),
|
||||||
|
(32, 'Verdura', 'mit Tomaten, Mozzarella, gegrilltem Gemüse & Knoblauch', 12.00),
|
||||||
|
(33, 'Mare', 'mit Tomaten, Meeresfrüchten & Knoblauch', 13.50),
|
||||||
|
(34, 'Contadina', 'mit Büffelmozzarella, Parmaschinken Julienne, Champignons & Rucola', 13.90),
|
||||||
|
(35, 'Mafiosa', 'mit Tomaten, Mozzarella, Speck, scharfer Salami, Champignons, Spinat & Peperoni', 12.90),
|
||||||
|
(36, 'Reale', 'mit Tomaten, Mozzarella, original Parmaschinken, Rucola & Parmesan', 14.90),
|
||||||
|
(38, 'Mediterranea', 'mit Tomatenscheiben, Mozzarella, Thunfisch, Kapern & Rucola', 12.90),
|
||||||
|
(39, 'Tricolore', 'mit Büffel-Mozzarella, Tomatenscheiben & Rucola-Pesto', 13.90),
|
||||||
|
(40, 'Alfio Spezial', 'mit Mozzarella, Tomatenstücken, Salsiccia, Zwiebeln, Parmesan, Oliven & Basilikum', 12.90),
|
||||||
|
(41, 'Allgäuer', 'mit Tomaten, Mozzarella, Speck, Bergkäse, Röstzwiebeln & Knoblauch DAS ORIGINAL', 12.90),
|
||||||
|
(42, 'Buon Gustaio', 'mit Tomaten, Mozzarella, Spinat, Gorgonzola & Knoblauch', 11.90),
|
||||||
|
(44, 'Mista', 'mit Tomaten, Mozzarella, Schinken, Salami, Pilzen & Thunfisch', 11.90),
|
||||||
|
(48, 'Cudduruni', 'Tomatenstücken, Zwiebeln, Parmesan, Oliven, Basilikum, Chili-Olivenöl NEU', 13.90),
|
||||||
|
(49, 'Bufalina', 'mit Tomaten, Büffelmozzarella, frischer Basilikum und Olivenöl NEU', 14.90)
|
||||||
|
);
|
||||||
|
|
||||||
|
|
@ -0,0 +1,619 @@
|
|||||||
|
package eu.jonahbauer.chat.bot.pizza;
|
||||||
|
|
||||||
|
import eu.jonahbauer.chat.bot.api.Message.Post;
|
||||||
|
import eu.jonahbauer.chat.bot.pizza.model.*;
|
||||||
|
import eu.jonahbauer.chat.bot.pizza.model.table.*;
|
||||||
|
import eu.jonahbauer.chat.bot.test.MockChat;
|
||||||
|
import eu.jonahbauer.chat.bot.database.Database;
|
||||||
|
import eu.jonahbauer.chat.bot.test.ChatBotFactoryAccess;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Nested;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import static eu.jonahbauer.chat.bot.pizza.util.FormatUtils.*;
|
||||||
|
import static java.util.FormatProcessor.FMT;
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
class PizzaBotTest {
|
||||||
|
@TempDir
|
||||||
|
static Path temp;
|
||||||
|
|
||||||
|
Database database;
|
||||||
|
MockChat chat;
|
||||||
|
PizzaBot bot;
|
||||||
|
|
||||||
|
User admin;
|
||||||
|
User user;
|
||||||
|
User user2;
|
||||||
|
|
||||||
|
Event event;
|
||||||
|
Order order0; // alte bestellung (bestellannahmeschluss vorbei)
|
||||||
|
Order order1; // alte bestellung
|
||||||
|
Order order; // laufende bestellung
|
||||||
|
|
||||||
|
Pizza salami;
|
||||||
|
Pizza margherita;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void init() throws IOException, SQLException {
|
||||||
|
var file = Files.createTempFile(temp, "database", ".db");
|
||||||
|
database = new Database("jdbc:sqlite:" + file.toAbsolutePath());
|
||||||
|
run("init.sql");
|
||||||
|
run("restaurants/sonthofen_2023.sql");
|
||||||
|
|
||||||
|
chat = new MockChat();
|
||||||
|
bot = ChatBotFactoryAccess.create(() -> new PizzaBot(Collections.singleton("marek"), database));
|
||||||
|
|
||||||
|
admin = database.save(new User(0, "Admin"));
|
||||||
|
user = database.save(new User(1337, "User1"));
|
||||||
|
user2 = database.save(new User(42, "User2"));
|
||||||
|
|
||||||
|
event = database.save(new Event(0, "Veranstaltung", admin.id(), true));
|
||||||
|
order0 = database.save(new Order(0, event.id(), 1, "28.03.2024", Instant.ofEpochMilli(System.currentTimeMillis() - 3_600_000), 90.0));
|
||||||
|
order1 = database.save(new Order(0, event.id(), 1, "29.03.2024", Instant.ofEpochMilli(System.currentTimeMillis() + 3_600_000), null));
|
||||||
|
order = database.save(new Order(0, event.id(), 1, "30.03.2024", Instant.ofEpochMilli(System.currentTimeMillis() + 7_200_000), null));
|
||||||
|
|
||||||
|
salami = database.findWhere(Pizza.class, Pizza.Fields.name, "Salami").orElseThrow();
|
||||||
|
margherita = database.findWhere(Pizza.class, Pizza.Fields.name, "Margherita").orElseThrow();
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
void print() {
|
||||||
|
for (var message : chat.getMessages()) {
|
||||||
|
System.out.println("\n" + message.name() + "\n" + message.message());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void run(@NotNull String script) throws SQLException, IOException {
|
||||||
|
try (var conn = database.getConnection()) {
|
||||||
|
var runner = new ScriptRunner(conn, false, true);
|
||||||
|
runner.setLogWriter(null);
|
||||||
|
try (var stream = Objects.requireNonNull(PizzaBot.class.getResourceAsStream(script))) {
|
||||||
|
try (var reader = new InputStreamReader(stream, StandardCharsets.UTF_8)) {
|
||||||
|
runner.runScript(reader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deadline() throws SQLException {
|
||||||
|
database.delete(order);
|
||||||
|
database.delete(order1);
|
||||||
|
order = order0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void post(@NotNull User user, @NotNull String message) {
|
||||||
|
var post = new Post(0, user.username(), message, "marek", LocalDateTime.now(), 0L, user.id(), user.username(), "ffffff", 0);
|
||||||
|
bot.onMessage(chat, post);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class OrderPizza {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void orderByPizzaName() throws SQLException {
|
||||||
|
post(user, "!marek order SALAMI");
|
||||||
|
|
||||||
|
var items = database.findAll(OrderItem.class);
|
||||||
|
assertEquals(1, items.size());
|
||||||
|
|
||||||
|
var item = items.getFirst();
|
||||||
|
assertNull(item.notes());
|
||||||
|
assertEquals(order.id(), item.orderId());
|
||||||
|
assertEquals(salami.id(), item.pizzaId());
|
||||||
|
|
||||||
|
var mappings = database.findAll(OrderItemToUser.class);
|
||||||
|
assertEquals(List.of(new OrderItemToUser(item.id(), user.id(), false)), mappings);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void orderByPizzaNameWithNotes() throws SQLException {
|
||||||
|
post(user, "!marek order SALAMI Hello World");
|
||||||
|
|
||||||
|
var items = database.findAll(OrderItem.class);
|
||||||
|
assertEquals(1, items.size());
|
||||||
|
|
||||||
|
var item = items.getFirst();
|
||||||
|
assertEquals("Hello World", item.notes());
|
||||||
|
assertEquals(order.id(), item.orderId());
|
||||||
|
assertEquals(salami.id(), item.pizzaId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void orderByPizzaNameFailsAfterDeadline() throws SQLException {
|
||||||
|
deadline();
|
||||||
|
post(user, "!marek order SALAMI");
|
||||||
|
|
||||||
|
var items = database.findAll(OrderItem.class);
|
||||||
|
assertEquals(0, items.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void orderByPizzaNumber() throws SQLException {
|
||||||
|
post(user, "!marek order 13");
|
||||||
|
|
||||||
|
var items = database.findAll(OrderItem.class);
|
||||||
|
assertEquals(1, items.size());
|
||||||
|
|
||||||
|
var item = items.getFirst();
|
||||||
|
assertNull(item.notes());
|
||||||
|
assertEquals(order.id(), item.orderId());
|
||||||
|
|
||||||
|
var pizza = database.findById(Pizza.class, item.pizzaId());
|
||||||
|
assertTrue(pizza.isPresent());
|
||||||
|
assertEquals("Prosciutto", pizza.get().name());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void orderAddById() throws SQLException {
|
||||||
|
var item = database.save(new OrderItem(0, order.id(), salami.id(), null));
|
||||||
|
database.insert(new OrderItemToUser(item.id(), user.id(), false));
|
||||||
|
|
||||||
|
post(admin, STR."!marek order add \{item.id()} \{user2.username()}");
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
List.of(
|
||||||
|
new OrderItemToUser(item.id(), user.id(), false),
|
||||||
|
new OrderItemToUser(item.id(), user2.id(), false)
|
||||||
|
),
|
||||||
|
database.findAll(OrderItemToUser.class)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void orderAddByName() throws SQLException {
|
||||||
|
var item = database.save(new OrderItem(0, order.id(), salami.id(), null));
|
||||||
|
database.insert(new OrderItemToUser(item.id(), user.id(), false));
|
||||||
|
|
||||||
|
post(admin, STR."!marek order add \{user.username()} \{user2.username()}");
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
List.of(
|
||||||
|
new OrderItemToUser(item.id(), user.id(), false),
|
||||||
|
new OrderItemToUser(item.id(), user2.id(), false)
|
||||||
|
),
|
||||||
|
database.findAll(OrderItemToUser.class)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void orderAddByNameFailsWhenNotUnique() throws SQLException {
|
||||||
|
var item1 = database.save(new OrderItem(0, order.id(), salami.id(), null));
|
||||||
|
database.insert(new OrderItemToUser(item1.id(), user.id(), false));
|
||||||
|
var item2 = database.save(new OrderItem(0, order.id(), salami.id(), null));
|
||||||
|
database.insert(new OrderItemToUser(item2.id(), user.id(), false));
|
||||||
|
|
||||||
|
post(admin, STR."!marek order add \{user.username()} \{user2.username()}");
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
List.of(
|
||||||
|
new OrderItemToUser(item1.id(), user.id(), false),
|
||||||
|
new OrderItemToUser(item2.id(), user.id(), false)
|
||||||
|
),
|
||||||
|
database.findAll(OrderItemToUser.class)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void orderAddByIdFailsWhenAlreadyPayed() throws SQLException {
|
||||||
|
var item = database.save(new OrderItem(0, order.id(), salami.id(), null));
|
||||||
|
database.insert(new OrderItemToUser(item.id(), user.id(), false));
|
||||||
|
database.insert(new OrderItemToUser(item.id(), user2.id(), true));
|
||||||
|
|
||||||
|
post(admin, STR."!marek order add \{item.id()} \{admin.username()}");
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
List.of(
|
||||||
|
new OrderItemToUser(item.id(), user.id(), false),
|
||||||
|
new OrderItemToUser(item.id(), user2.id(), true)
|
||||||
|
),
|
||||||
|
database.findAll(OrderItemToUser.class)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class Revoke {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void orderRevoke() throws SQLException {
|
||||||
|
var item1 = database.save(new OrderItem(0, order.id(), margherita.id(), null));
|
||||||
|
database.insert(new OrderItemToUser(item1.id(), user.id(), false));
|
||||||
|
var item2 = database.save(new OrderItem(0, order.id(), margherita.id(), null));
|
||||||
|
database.insert(new OrderItemToUser(item2.id(), user2.id(), false));
|
||||||
|
|
||||||
|
post(user, "!marek order revoke");
|
||||||
|
|
||||||
|
var items = database.findAll(OrderItem.class);
|
||||||
|
assertEquals(List.of(item2), items);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void orderRevokeFailsAfterDeadline() throws SQLException {
|
||||||
|
deadline();
|
||||||
|
|
||||||
|
var item = database.save(new OrderItem(0, order.id(), margherita.id(), null));
|
||||||
|
database.insert(new OrderItemToUser(item.id(), user.id(), false));
|
||||||
|
|
||||||
|
post(user, "!marek order revoke");
|
||||||
|
|
||||||
|
var items = database.findAll(OrderItem.class);
|
||||||
|
assertEquals(List.of(item), items);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void orderRevokeFailsWithMultipleItems() throws SQLException {
|
||||||
|
var item1 = database.save(new OrderItem(0, order.id(), margherita.id(), null));
|
||||||
|
database.insert(new OrderItemToUser(item1.id(), user.id(), false));
|
||||||
|
var item2 = database.save(new OrderItem(0, order.id(), salami.id(), null));
|
||||||
|
database.insert(new OrderItemToUser(item2.id(), user.id(), false));
|
||||||
|
|
||||||
|
post(user, "!marek order revoke");
|
||||||
|
|
||||||
|
var items = database.findAll(OrderItem.class);
|
||||||
|
assertEquals(2, items.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void orderRevokeFailsWhenItemBelongsToOtherOrder() throws SQLException {
|
||||||
|
var item = database.save(new OrderItem(0, order1.id(), margherita.id(), null));
|
||||||
|
database.insert(new OrderItemToUser(item.id(), user.id(), false));
|
||||||
|
|
||||||
|
post(user, "!marek order revoke");
|
||||||
|
|
||||||
|
var items = database.findAll(OrderItem.class);
|
||||||
|
assertEquals(List.of(item), items);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void orderRevokeByNameWithMultipleItems() throws SQLException {
|
||||||
|
var item1 = database.save(new OrderItem(0, order.id(), margherita.id(), null));
|
||||||
|
database.insert(new OrderItemToUser(item1.id(), user.id(), false));
|
||||||
|
var item2 = database.save(new OrderItem(0, order.id(), salami.id(), null));
|
||||||
|
database.insert(new OrderItemToUser(item2.id(), user.id(), false));
|
||||||
|
|
||||||
|
post(user, "!marek order revoke SALAMI");
|
||||||
|
|
||||||
|
var items = database.findAll(OrderItem.class);
|
||||||
|
assertEquals(List.of(item1), items);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void orderRevokeByNameFailsWhenItemBelongsToOtherUser() throws SQLException {
|
||||||
|
var item = database.save(new OrderItem(0, order.id(), margherita.id(), null));
|
||||||
|
database.insert(new OrderItemToUser(item.id(), user.id(), false));
|
||||||
|
|
||||||
|
post(user2, "!marek order revoke MARGHERITA");
|
||||||
|
|
||||||
|
var items = database.findAll(OrderItem.class);
|
||||||
|
assertEquals(List.of(item), items);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void orderRevokeByNameFailsWhenItemBelongsToOtherOrder() throws SQLException {
|
||||||
|
var item = database.save(new OrderItem(0, order1.id(), margherita.id(), null));
|
||||||
|
database.insert(new OrderItemToUser(item.id(), user.id(), false));
|
||||||
|
|
||||||
|
post(user, "!marek order revoke MARGHERITA");
|
||||||
|
|
||||||
|
var items = database.findAll(OrderItem.class);
|
||||||
|
assertEquals(List.of(item), items);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void orderRevokeById() throws SQLException {
|
||||||
|
var item = database.save(new OrderItem(0, order.id(), margherita.id(), null));
|
||||||
|
database.insert(new OrderItemToUser(item.id(), user.id(), false));
|
||||||
|
|
||||||
|
post(user, "!marek order revoke " + item.id());
|
||||||
|
|
||||||
|
var items = database.findAll(OrderItem.class);
|
||||||
|
assertEquals(List.of(), items);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void orderRevokeByIdFailsWhenItemBelongsToOtherUser() throws SQLException {
|
||||||
|
var item = database.save(new OrderItem(0, order.id(), margherita.id(), null));
|
||||||
|
database.insert(new OrderItemToUser(item.id(), user.id(), false));
|
||||||
|
|
||||||
|
post(user2, "!marek order revoke " + item.id());
|
||||||
|
|
||||||
|
var items = database.findAll(OrderItem.class);
|
||||||
|
assertEquals(List.of(item), items);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void orderRevokeByIdFailsWhenItemBelongsToOtherOrder() throws SQLException {
|
||||||
|
var item = database.save(new OrderItem(0, order1.id(), margherita.id(), null));
|
||||||
|
database.insert(new OrderItemToUser(item.id(), user.id(), false));
|
||||||
|
|
||||||
|
post(user, "!marek order revoke " + item.id());
|
||||||
|
|
||||||
|
var items = database.findAll(OrderItem.class);
|
||||||
|
assertEquals(List.of(item), items);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void orderRevokeAll() throws SQLException {
|
||||||
|
var item1 = database.save(new OrderItem(0, order.id(), margherita.id(), null));
|
||||||
|
database.insert(new OrderItemToUser(item1.id(), user.id(), false));
|
||||||
|
var item2 = database.save(new OrderItem(0, order.id(), salami.id(), null));
|
||||||
|
database.insert(new OrderItemToUser(item2.id(), user.id(), false));
|
||||||
|
var item3 = database.save(new OrderItem(0, order.id(), salami.id(), null));
|
||||||
|
database.insert(new OrderItemToUser(item3.id(), user2.id(), false));
|
||||||
|
|
||||||
|
post(user, "!marek order revoke --all");
|
||||||
|
|
||||||
|
var items = database.findAll(OrderItem.class);
|
||||||
|
assertEquals(List.of(item3), items);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class Payment {
|
||||||
|
OrderItem item;
|
||||||
|
OrderItemToUser itemToUser;
|
||||||
|
OrderItemToUser itemToUser2;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void init() throws SQLException {
|
||||||
|
item = database.save(new OrderItem(0, order.id(), margherita.id(), null));
|
||||||
|
itemToUser = database.save(new OrderItemToUser(item.id(), user.id(), false));
|
||||||
|
itemToUser2 = database.save(new OrderItemToUser(item.id(), user2.id(), false));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void paymentConfirmById() throws SQLException {
|
||||||
|
post(admin, "!marek payment confirm " + item.id());
|
||||||
|
|
||||||
|
var mappings = database.findAll(OrderItemToUser.class);
|
||||||
|
assertEquals(List.of(itemToUser.withPayed(true), itemToUser2.withPayed(true)), mappings);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void paymentConfirmByIdFailsWhenNotAdmin() throws SQLException {
|
||||||
|
post(user, "!marek payment confirm " + item.id());
|
||||||
|
|
||||||
|
var mappings = database.findAll(OrderItemToUser.class);
|
||||||
|
assertEquals(List.of(itemToUser, itemToUser2), mappings);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void paymentConfirmByIdAndUser() throws SQLException {
|
||||||
|
post(admin, "!marek payment confirm " + item.id() + " " + user.username());
|
||||||
|
|
||||||
|
var mappings = database.findAll(OrderItemToUser.class);
|
||||||
|
assertEquals(List.of(itemToUser.withPayed(true), itemToUser2), mappings);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void paymentConfirmByIdAndUserFailsWhenNotAdmin() throws SQLException {
|
||||||
|
post(user, "!marek payment confirm " + item.id() + " " + user.username());
|
||||||
|
|
||||||
|
var mappings = database.findAll(OrderItemToUser.class);
|
||||||
|
assertEquals(List.of(itemToUser, itemToUser2), mappings);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void paymentConfirmByUser() throws SQLException {
|
||||||
|
var item2 = database.save(new OrderItem(0, order.id(), salami.id(), null));
|
||||||
|
var item2ToUser = database.save(new OrderItemToUser(item2.id(), user.id(), false));
|
||||||
|
var item2ToUser2 = database.save(new OrderItemToUser(item2.id(), user2.id(), false));
|
||||||
|
|
||||||
|
post(admin, "!marek payment confirm --all " + user.username());
|
||||||
|
|
||||||
|
var mappings = database.findAll(OrderItemToUser.class);
|
||||||
|
assertEquals(
|
||||||
|
List.of(itemToUser.withPayed(true), itemToUser2, item2ToUser.withPayed(true), item2ToUser2),
|
||||||
|
mappings
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class Pay {
|
||||||
|
@Test
|
||||||
|
void pay() throws SQLException {
|
||||||
|
post(admin, "!marek pay 42.5");
|
||||||
|
|
||||||
|
var order = database.findById(Order.class, PizzaBotTest.this.order.id());
|
||||||
|
assertTrue(order.isPresent());
|
||||||
|
assertEquals(42.5, order.get().payment());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void payFailsWhenNotAdmin() throws SQLException {
|
||||||
|
post(user, "!marek pay 42.5");
|
||||||
|
|
||||||
|
var order = database.findById(Order.class, PizzaBotTest.this.order.id());
|
||||||
|
assertTrue(order.isPresent());
|
||||||
|
assertEquals(PizzaBotTest.this.order, order.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void payWithOrderName() throws SQLException {
|
||||||
|
post(admin, "!marek pay " + order0.name() + " 42.5");
|
||||||
|
|
||||||
|
var order0 = database.findById(Order.class, PizzaBotTest.this.order0.id());
|
||||||
|
assertTrue(order0.isPresent());
|
||||||
|
assertEquals(42.5, order0.get().payment());
|
||||||
|
|
||||||
|
var order = database.findById(Order.class, PizzaBotTest.this.order.id());
|
||||||
|
assertTrue(order.isPresent());
|
||||||
|
assertNull(order.get().payment());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class CheckAndSummary {
|
||||||
|
OrderItem item1;
|
||||||
|
OrderItemToUser item1ToUser;
|
||||||
|
|
||||||
|
OrderItem item2;
|
||||||
|
OrderItemToUser item2ToUser;
|
||||||
|
|
||||||
|
OrderItem item3;
|
||||||
|
OrderItemToUser item3ToUser;
|
||||||
|
|
||||||
|
OrderItem item4;
|
||||||
|
OrderItemToUser item4ToUser2;
|
||||||
|
|
||||||
|
OrderItem item5;
|
||||||
|
OrderItemToUser item5ToUser;
|
||||||
|
OrderItemToUser item5ToUser2;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void init() throws SQLException {
|
||||||
|
item1 = database.save(new OrderItem(0, order0.id(), margherita.id(), null));
|
||||||
|
item1ToUser = database.save(new OrderItemToUser(item1.id(), user.id(), false));
|
||||||
|
|
||||||
|
item2 = database.save(new OrderItem(0, order.id(), margherita.id(), null));
|
||||||
|
item2ToUser = database.save(new OrderItemToUser(item2.id(), user.id(), false));
|
||||||
|
|
||||||
|
item3 = database.save(new OrderItem(0, order.id(), salami.id(), null));
|
||||||
|
item3ToUser = database.save(new OrderItemToUser(item3.id(), user.id(), false));
|
||||||
|
|
||||||
|
item4 = database.save(new OrderItem(0, order.id(), margherita.id(), null));
|
||||||
|
item4ToUser2 = database.save(new OrderItemToUser(item4.id(), user2.id(), false));
|
||||||
|
|
||||||
|
item5 = database.save(new OrderItem(0, order.id(), margherita.id(), null));
|
||||||
|
item5ToUser = database.save(new OrderItemToUser(item5.id(), user.id(), false));
|
||||||
|
item5ToUser2 = database.save(new OrderItemToUser(item5.id(), user2.id(), false));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void check() {
|
||||||
|
post(admin, "!marek check");
|
||||||
|
var message = chat.getMessages().getFirst();
|
||||||
|
var expected = FMT."""
|
||||||
|
Rechnung für %s\{order.name()}
|
||||||
|
alle Angaben ohne Gewähr
|
||||||
|
|
||||||
|
%s\{user.username()}: %.2f\{1.5 * margherita.price() + salami.price()} €
|
||||||
|
%s\{user2.username()}: %.2f\{1.5 * margherita.price()} €
|
||||||
|
""".trim();
|
||||||
|
assertEquals(expected, strip(message.message()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void checkTip() throws SQLException {
|
||||||
|
database.update(order.withPayment(40d));
|
||||||
|
|
||||||
|
post(admin, "!marek check");
|
||||||
|
var message = chat.getMessages().getFirst();
|
||||||
|
var expected = FMT."""
|
||||||
|
Rechnung für %s\{order.name()} (mit Trinkgeld)
|
||||||
|
alle Angaben ohne Gewähr
|
||||||
|
|
||||||
|
%s\{user.username()}: %.2f\{1.5 * margherita.price() + salami.price()} € (25.19 €)
|
||||||
|
%s\{user2.username()}: %.2f\{1.5 * margherita.price()} € (14.81 €)
|
||||||
|
""".trim();
|
||||||
|
assertEquals(expected, strip(message.message()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void checkPayed() throws SQLException {
|
||||||
|
database.executeUpdate("UPDATE order_item_to_user SET payed = TRUE WHERE user_id = " + user.id());
|
||||||
|
|
||||||
|
post(admin, "!marek check");
|
||||||
|
var message = chat.getMessages().getFirst();
|
||||||
|
var expected = FMT."""
|
||||||
|
Rechnung für %s\{order.name()}
|
||||||
|
alle Angaben ohne Gewähr
|
||||||
|
|
||||||
|
%s\{user.username()}: %.2f\{1.5 * margherita.price() + salami.price()} €
|
||||||
|
%s\{user2.username()}: %.2f\{1.5 * margherita.price()} €
|
||||||
|
""".trim();
|
||||||
|
assertEquals(expected, strip(message.message()));
|
||||||
|
assertEquals(strikethrough("User1: " + tabular(1.5 * margherita.price() + salami.price(), 0) + " €"), message.message().split("\n")[3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void checkPartialPayed() throws SQLException {
|
||||||
|
database.executeUpdate("UPDATE order_item_to_user SET payed = TRUE WHERE order_item_id = " + item5.id());
|
||||||
|
|
||||||
|
var user1Total = 1.5 * margherita.price() + salami.price();
|
||||||
|
var user1Remaining = margherita.price() + salami.price();
|
||||||
|
var user2Total = 1.5 * margherita.price();
|
||||||
|
var user2Remaining = margherita.price();
|
||||||
|
|
||||||
|
post(admin, "!marek check");
|
||||||
|
var message = chat.getMessages().getFirst();
|
||||||
|
var expected = FMT."""
|
||||||
|
Rechnung für %s\{order.name()}
|
||||||
|
alle Angaben ohne Gewähr
|
||||||
|
|
||||||
|
%s\{user.username()}: %.2f\{user1Total} € %.2f\{user1Remaining} €
|
||||||
|
%s\{user2.username()}: %.2f\{user2Total} € %.2f\{user2Remaining} €
|
||||||
|
""".trim();
|
||||||
|
assertEquals(expected, strip(message.message()));
|
||||||
|
assertEquals(
|
||||||
|
STR."\{user.username()}: \{strikethrough(tabular(user1Total, 0)+ " €")} \{tabular(user1Remaining, 0)} €",
|
||||||
|
message.message().split("\n")[3]
|
||||||
|
);
|
||||||
|
assertEquals(
|
||||||
|
STR."\{user2.username()}: \{strikethrough(tabular(user2Total, 0)+ " €")} \{tabular(user2Remaining, 0)} €",
|
||||||
|
message.message().split("\n")[4]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void checkAllTip() throws SQLException {
|
||||||
|
database.update(order.withPayment(40d));
|
||||||
|
|
||||||
|
post(admin, "!marek check --all");
|
||||||
|
var message = chat.getMessages().getFirst();
|
||||||
|
var expected = FMT."""
|
||||||
|
Rechnung für %s\{event.name()} (mit Trinkgeld)
|
||||||
|
alle Angaben ohne Gewähr
|
||||||
|
|
||||||
|
%s\{user.username()}: %.2f\{2.5 * margherita.price() + salami.price()} € (115.19 €)
|
||||||
|
%s\{user2.username()}: %.2f\{1.5 * margherita.price()} € (14.81 €)
|
||||||
|
""".trim();
|
||||||
|
assertEquals(expected, strip(message.message()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void summary() {
|
||||||
|
post(admin, "!marek summary");
|
||||||
|
var message = chat.getMessages().getFirst();
|
||||||
|
var expected = FMT."""
|
||||||
|
Übersicht %s\{order.name()}
|
||||||
|
|
||||||
|
3x \{margherita.name().toUpperCase(Locale.ROOT)} (\{user.username()}, \{user.username()}+\{user2.username()}, \{user2.username()})
|
||||||
|
1x \{salami.name().toUpperCase(Locale.ROOT)} (\{user.username()})
|
||||||
|
""".trim();
|
||||||
|
assertEquals(expected, strip(message.message()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void summaryAll() {
|
||||||
|
post(admin, "!marek summary --all");
|
||||||
|
var message = chat.getMessages().getFirst();
|
||||||
|
var expected = FMT."""
|
||||||
|
Übersicht %s\{event.name()}
|
||||||
|
|
||||||
|
4x \{margherita.name().toUpperCase(Locale.ROOT)} (2x \{user.username()}, \{user.username()}+\{user2.username()}, \{user2.username()})
|
||||||
|
1x \{salami.name().toUpperCase(Locale.ROOT)} (\{user.username()})
|
||||||
|
""".trim();
|
||||||
|
assertEquals(expected, strip(message.message()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,248 @@
|
|||||||
|
package eu.jonahbauer.chat.bot.pizza;
|
||||||
|
/*
|
||||||
|
* Slightly modified version of the com.ibatis.common.jdbc.ScriptRunner class
|
||||||
|
* from the iBATIS Apache project. Only removed dependency on Resource class
|
||||||
|
* and a constructor
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
* Copyright 2004 Clinton Begin
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
* http://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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.LineNumberReader;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.io.Reader;
|
||||||
|
import java.sql.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tool to run database scripts
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
public class ScriptRunner {
|
||||||
|
|
||||||
|
private static final String DEFAULT_DELIMITER = ";";
|
||||||
|
|
||||||
|
private Connection connection;
|
||||||
|
|
||||||
|
private boolean stopOnError;
|
||||||
|
private boolean autoCommit;
|
||||||
|
|
||||||
|
private PrintWriter logWriter = new PrintWriter(System.out);
|
||||||
|
private PrintWriter errorLogWriter = new PrintWriter(System.err);
|
||||||
|
|
||||||
|
private String delimiter = DEFAULT_DELIMITER;
|
||||||
|
private boolean fullLineDelimiter = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default constructor
|
||||||
|
*/
|
||||||
|
public ScriptRunner(Connection connection, boolean autoCommit,
|
||||||
|
boolean stopOnError) {
|
||||||
|
this.connection = connection;
|
||||||
|
this.autoCommit = autoCommit;
|
||||||
|
this.stopOnError = stopOnError;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDelimiter(String delimiter, boolean fullLineDelimiter) {
|
||||||
|
this.delimiter = delimiter;
|
||||||
|
this.fullLineDelimiter = fullLineDelimiter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setter for logWriter property
|
||||||
|
*
|
||||||
|
* @param logWriter
|
||||||
|
* - the new value of the logWriter property
|
||||||
|
*/
|
||||||
|
public void setLogWriter(PrintWriter logWriter) {
|
||||||
|
this.logWriter = logWriter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setter for errorLogWriter property
|
||||||
|
*
|
||||||
|
* @param errorLogWriter
|
||||||
|
* - the new value of the errorLogWriter property
|
||||||
|
*/
|
||||||
|
public void setErrorLogWriter(PrintWriter errorLogWriter) {
|
||||||
|
this.errorLogWriter = errorLogWriter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs an SQL script (read in using the Reader parameter)
|
||||||
|
*
|
||||||
|
* @param reader
|
||||||
|
* - the source of the script
|
||||||
|
*/
|
||||||
|
public void runScript(Reader reader) throws IOException, SQLException {
|
||||||
|
try {
|
||||||
|
boolean originalAutoCommit = connection.getAutoCommit();
|
||||||
|
try {
|
||||||
|
if (originalAutoCommit != this.autoCommit) {
|
||||||
|
connection.setAutoCommit(this.autoCommit);
|
||||||
|
}
|
||||||
|
runScript(connection, reader);
|
||||||
|
} finally {
|
||||||
|
connection.setAutoCommit(originalAutoCommit);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw e;
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw e;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Error running script. Cause: " + e, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs an SQL script (read in using the Reader parameter) using the
|
||||||
|
* connection passed in
|
||||||
|
*
|
||||||
|
* @param conn
|
||||||
|
* - the connection to use for the script
|
||||||
|
* @param reader
|
||||||
|
* - the source of the script
|
||||||
|
* @throws SQLException
|
||||||
|
* if any SQL errors occur
|
||||||
|
* @throws IOException
|
||||||
|
* if there is an error reading from the Reader
|
||||||
|
*/
|
||||||
|
private void runScript(Connection conn, Reader reader) throws IOException,
|
||||||
|
SQLException {
|
||||||
|
StringBuffer command = null;
|
||||||
|
try {
|
||||||
|
LineNumberReader lineReader = new LineNumberReader(reader);
|
||||||
|
String line = null;
|
||||||
|
while ((line = lineReader.readLine()) != null) {
|
||||||
|
if (command == null) {
|
||||||
|
command = new StringBuffer();
|
||||||
|
}
|
||||||
|
String trimmedLine = line.trim();
|
||||||
|
if (trimmedLine.startsWith("--")) {
|
||||||
|
println(trimmedLine);
|
||||||
|
} else if (trimmedLine.length() < 1
|
||||||
|
|| trimmedLine.startsWith("//")) {
|
||||||
|
// Do nothing
|
||||||
|
} else if (trimmedLine.length() < 1
|
||||||
|
|| trimmedLine.startsWith("--")) {
|
||||||
|
// Do nothing
|
||||||
|
} else if (!fullLineDelimiter
|
||||||
|
&& trimmedLine.endsWith(getDelimiter())
|
||||||
|
|| fullLineDelimiter
|
||||||
|
&& trimmedLine.equals(getDelimiter())) {
|
||||||
|
command.append(line.substring(0, line
|
||||||
|
.lastIndexOf(getDelimiter())));
|
||||||
|
command.append(" ");
|
||||||
|
Statement statement = conn.createStatement();
|
||||||
|
|
||||||
|
println(command);
|
||||||
|
|
||||||
|
boolean hasResults = false;
|
||||||
|
if (stopOnError) {
|
||||||
|
hasResults = statement.execute(command.toString());
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
statement.execute(command.toString());
|
||||||
|
} catch (SQLException e) {
|
||||||
|
e.fillInStackTrace();
|
||||||
|
printlnError("Error executing: " + command);
|
||||||
|
printlnError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (autoCommit && !conn.getAutoCommit()) {
|
||||||
|
conn.commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultSet rs = statement.getResultSet();
|
||||||
|
if (hasResults && rs != null) {
|
||||||
|
ResultSetMetaData md = rs.getMetaData();
|
||||||
|
int cols = md.getColumnCount();
|
||||||
|
for (int i = 0; i < cols; i++) {
|
||||||
|
String name = md.getColumnLabel(i);
|
||||||
|
print(name + "\t");
|
||||||
|
}
|
||||||
|
println("");
|
||||||
|
while (rs.next()) {
|
||||||
|
for (int i = 0; i < cols; i++) {
|
||||||
|
String value = rs.getString(i);
|
||||||
|
print(value + "\t");
|
||||||
|
}
|
||||||
|
println("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
command = null;
|
||||||
|
try {
|
||||||
|
statement.close();
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Ignore to workaround a bug in Jakarta DBCP
|
||||||
|
}
|
||||||
|
Thread.yield();
|
||||||
|
} else {
|
||||||
|
command.append(line);
|
||||||
|
command.append(" ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!autoCommit) {
|
||||||
|
conn.commit();
|
||||||
|
}
|
||||||
|
} catch (SQLException e) {
|
||||||
|
e.fillInStackTrace();
|
||||||
|
printlnError("Error executing: " + command);
|
||||||
|
printlnError(e);
|
||||||
|
throw e;
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.fillInStackTrace();
|
||||||
|
printlnError("Error executing: " + command);
|
||||||
|
printlnError(e);
|
||||||
|
throw e;
|
||||||
|
} finally {
|
||||||
|
conn.rollback();
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getDelimiter() {
|
||||||
|
return delimiter;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void print(Object o) {
|
||||||
|
if (logWriter != null) {
|
||||||
|
System.out.print(o);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void println(Object o) {
|
||||||
|
if (logWriter != null) {
|
||||||
|
logWriter.println(o);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void printlnError(Object o) {
|
||||||
|
if (errorLogWriter != null) {
|
||||||
|
errorLogWriter.println(o);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void flush() {
|
||||||
|
if (logWriter != null) {
|
||||||
|
logWriter.flush();
|
||||||
|
}
|
||||||
|
if (errorLogWriter != null) {
|
||||||
|
errorLogWriter.flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue