diff --git a/accounting/build.gradle.kts b/accounting/build.gradle.kts index efceaa32..adb67f35 100644 --- a/accounting/build.gradle.kts +++ b/accounting/build.gradle.kts @@ -1,5 +1,6 @@ description = "Umbrella : Accounting" dependencies{ + implementation(project(":bus")) implementation(project(":core")) } \ No newline at end of file diff --git a/accounting/src/main/java/de/srsoftware/umbrella/accounting/AccountingModule.java b/accounting/src/main/java/de/srsoftware/umbrella/accounting/AccountingModule.java index 16c2ae65..b9866382 100644 --- a/accounting/src/main/java/de/srsoftware/umbrella/accounting/AccountingModule.java +++ b/accounting/src/main/java/de/srsoftware/umbrella/accounting/AccountingModule.java @@ -10,6 +10,9 @@ import static de.srsoftware.umbrella.core.Util.mapValues; import static de.srsoftware.umbrella.core.constants.Path.*; import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.invalidField; import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.missingField; +import static de.srsoftware.umbrella.messagebus.MessageBus.messageBus; +import static de.srsoftware.umbrella.messagebus.events.Event.EventType.CREATE; +import static de.srsoftware.umbrella.messagebus.events.Event.EventType.UPDATE; import static java.lang.System.Logger.Level.WARNING; import com.sun.net.httpserver.HttpExchange; @@ -27,6 +30,9 @@ import java.io.IOException; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.*; + +import de.srsoftware.umbrella.messagebus.events.Event; +import de.srsoftware.umbrella.messagebus.events.TransactionEvent; import org.json.JSONArray; import org.json.JSONObject; @@ -177,6 +183,14 @@ public class AccountingModule extends BaseHandler implements AccountingService { private boolean getAccount(UmbrellaUser user, long accountId, HttpExchange ex) throws IOException { LOG.log(WARNING,"Missing authorization check in AccountingModule.getAccount(…)!"); + return sendContent(ex, loadAccount(accountId)); + } + + private boolean getAccounts(UmbrellaUser user, HttpExchange ex) throws IOException { + return sendContent(ex,accountDb.listAccounts(user.id()).stream().map(Account::toMap)); + } + + public AccountData loadAccount(long accountId){ var account = accountDb.loadAccount(accountId); var transactions = accountDb.loadTransactions(account); var userMap = new HashMap(); @@ -193,20 +207,9 @@ public class AccountingModule extends BaseHandler implements AccountingService { if (!userMap.containsKey(userId)) userMap.put(destination.id(),userService().loadUser(userId)); } } - - return sendContent(ex, Map.of( - Field.ACCOUNT,account.toMap(), - Field.TRANSACTIONS,transactions.stream().map(Transaction::toMap).toList(), - Field.USER_LIST,mapValues(userMap) - )); + return AccountData.of(account, transactions, userMap); } - private boolean getAccounts(UmbrellaUser user, HttpExchange ex) throws IOException { - return sendContent(ex,accountDb.listAccounts(user.id()).stream().map(Account::toMap)); - } - - - private static boolean noNumbers(String s){ try { Long.parseLong(s); @@ -226,7 +229,9 @@ public class AccountingModule extends BaseHandler implements AccountingService { if (json.has(Field.PURPOSE)) transaction.purpose(json.getString(Field.PURPOSE)); if (json.has(Field.SOURCE)) transaction.source(IdOrString.of(json.getString(Field.SOURCE))); if (json.has(Field.TAG)) transaction.tags().add(json.getString(Field.TAG)); - return sendContent(ex,accountDb.save(transaction)); + var patched = accountDb.save(transaction); + messageBus().dispatch(new TransactionEvent(user,patched,UPDATE)); + return sendContent(ex,patched); } private boolean postEntry(UmbrellaUser user, HttpExchange ex) throws IOException { @@ -291,7 +296,7 @@ public class AccountingModule extends BaseHandler implements AccountingService { var transaction = accountDb.save(new Transaction(0,accountId,dateTime,source,destination,amount.doubleValue(),purpose,tags)); - + messageBus().dispatch(new TransactionEvent(user,transaction, CREATE)); return sendContent(ex,newAccount != null ? newAccount : transaction); } diff --git a/accounting/src/main/java/de/srsoftware/umbrella/accounting/SqliteDb.java b/accounting/src/main/java/de/srsoftware/umbrella/accounting/SqliteDb.java index a889eadd..5d56a4c3 100644 --- a/accounting/src/main/java/de/srsoftware/umbrella/accounting/SqliteDb.java +++ b/accounting/src/main/java/de/srsoftware/umbrella/accounting/SqliteDb.java @@ -10,6 +10,8 @@ import static de.srsoftware.umbrella.accounting.Constants.*; import static de.srsoftware.umbrella.core.constants.Field.*; import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.*; import static de.srsoftware.umbrella.core.model.Translatable.t; +import static de.srsoftware.umbrella.messagebus.MessageBus.messageBus; +import static de.srsoftware.umbrella.messagebus.events.Event.EventType.UPDATE; import static java.text.MessageFormat.format; import de.srsoftware.tools.jdbc.Condition; @@ -19,6 +21,8 @@ import de.srsoftware.umbrella.core.constants.Field; import de.srsoftware.umbrella.core.constants.Text; import de.srsoftware.umbrella.core.model.Account; import de.srsoftware.umbrella.core.model.Transaction; +import de.srsoftware.umbrella.messagebus.events.TransactionEvent; + import java.sql.Connection; import java.sql.SQLException; import java.time.ZoneOffset; diff --git a/bus/src/main/java/de/srsoftware/umbrella/messagebus/MessageApi.java b/bus/src/main/java/de/srsoftware/umbrella/messagebus/MessageApi.java index 9081fc56..09c8136a 100644 --- a/bus/src/main/java/de/srsoftware/umbrella/messagebus/MessageApi.java +++ b/bus/src/main/java/de/srsoftware/umbrella/messagebus/MessageApi.java @@ -65,7 +65,7 @@ public class MessageApi extends BaseHandler{ } } - private void sendEvent(PrintWriter out, Event event) { + private void sendEvent(PrintWriter out, Event event) { if (event == null) return; out.print("event: "); out.println(event.eventType()); diff --git a/bus/src/main/java/de/srsoftware/umbrella/messagebus/events/TransactionEvent.java b/bus/src/main/java/de/srsoftware/umbrella/messagebus/events/TransactionEvent.java new file mode 100644 index 00000000..4da0e1f6 --- /dev/null +++ b/bus/src/main/java/de/srsoftware/umbrella/messagebus/events/TransactionEvent.java @@ -0,0 +1,44 @@ +package de.srsoftware.umbrella.messagebus.events; + +import de.srsoftware.umbrella.core.ModuleRegistry; +import de.srsoftware.umbrella.core.constants.Module; +import de.srsoftware.umbrella.core.constants.Text; +import de.srsoftware.umbrella.core.model.Transaction; +import de.srsoftware.umbrella.core.model.Translatable; +import de.srsoftware.umbrella.core.model.UmbrellaUser; +import de.srsoftware.umbrella.core.model.UnTranslatable; + +import java.util.Collection; +import java.util.List; + +import static de.srsoftware.umbrella.core.ModuleRegistry.accountingService; +import static de.srsoftware.umbrella.core.model.Translatable.t; + +public class TransactionEvent extends Event { + private Collection audience; + + public TransactionEvent(UmbrellaUser initiator, Transaction transaction, EventType type) { + super(initiator, Module.ACCOUNTING, transaction, type); + audience = null; + } + + @Override + public Collection audience() { + if (audience == null) audience = accountingService().loadAccount(payload().accountId()).userMap().values(); + return audience; + } + + @Override + public Translatable describe() { + return new UnTranslatable(payload().purpose()); + } + + @Override + public Translatable subject() { + return switch (eventType()){ + case CREATE -> t("user_created_entity",initiator().name(), Text.TRANSACTION); + case UPDATE -> t("user_updated_entity",initiator().name(), Text.TRANSACTION); + case null, default -> t("TODO"); // TODO + }; + } +} diff --git a/core/src/main/java/de/srsoftware/umbrella/core/ModuleRegistry.java b/core/src/main/java/de/srsoftware/umbrella/core/ModuleRegistry.java index 5ecb60ee..55e4a664 100644 --- a/core/src/main/java/de/srsoftware/umbrella/core/ModuleRegistry.java +++ b/core/src/main/java/de/srsoftware/umbrella/core/ModuleRegistry.java @@ -53,6 +53,10 @@ public class ModuleRegistry { } } + public static AccountingService accountingService() { + return singleton.accountingService; + } + public static BookmarkService bookmarkService(){ return singleton.bookmarkService; } diff --git a/core/src/main/java/de/srsoftware/umbrella/core/api/AccountingService.java b/core/src/main/java/de/srsoftware/umbrella/core/api/AccountingService.java index 4cbc8c58..886a97ae 100644 --- a/core/src/main/java/de/srsoftware/umbrella/core/api/AccountingService.java +++ b/core/src/main/java/de/srsoftware/umbrella/core/api/AccountingService.java @@ -1,5 +1,33 @@ /* © SRSoftware 2025 */ package de.srsoftware.umbrella.core.api; +import de.srsoftware.tools.Mappable; +import de.srsoftware.umbrella.core.constants.Field; +import de.srsoftware.umbrella.core.model.Account; +import de.srsoftware.umbrella.core.model.Transaction; +import de.srsoftware.umbrella.core.model.UmbrellaUser; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static de.srsoftware.umbrella.core.Util.mapValues; + public interface AccountingService { + public record AccountData(Account account, List transactions, HashMap userMap) implements Mappable { + public static AccountData of(Account account, List transactions, HashMap userMap) { + return new AccountData(account, transactions, userMap); + } + + @Override + public Map toMap() { + return Map.of( + Field.ACCOUNT,account.toMap(), + Field.TRANSACTIONS,transactions.stream().map(Transaction::toMap).toList(), + Field.USER_LIST,mapValues(userMap) + ); + } + } + + AccountData loadAccount(long accountId); } diff --git a/frontend/src/routes/accounting/account.svelte b/frontend/src/routes/accounting/account.svelte index 721a4f96..eac1340f 100644 --- a/frontend/src/routes/accounting/account.svelte +++ b/frontend/src/routes/accounting/account.svelte @@ -1,6 +1,6 @@