implemented storing of new transaction

Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
This commit is contained in:
2026-04-02 13:17:14 +02:00
parent 677d6c9797
commit c0ab71d71d
7 changed files with 125 additions and 7 deletions

View File

@@ -1,7 +1,9 @@
package de.srsoftware.umbrella.accounting; package de.srsoftware.umbrella.accounting;
import de.srsoftware.umbrella.core.model.Account; import de.srsoftware.umbrella.core.model.Account;
import de.srsoftware.umbrella.core.model.Transaction;
public interface AccountDb { public interface AccountDb {
Account save(Account account); Account save(Account account);
Transaction save(Transaction transaction);
} }

View File

@@ -12,15 +12,20 @@ import de.srsoftware.umbrella.core.constants.Text;
import de.srsoftware.umbrella.core.exceptions.UmbrellaException; import de.srsoftware.umbrella.core.exceptions.UmbrellaException;
import de.srsoftware.umbrella.core.model.Account; import de.srsoftware.umbrella.core.model.Account;
import de.srsoftware.umbrella.core.model.Token; import de.srsoftware.umbrella.core.model.Token;
import de.srsoftware.umbrella.core.model.Transaction;
import de.srsoftware.umbrella.core.model.UmbrellaUser; import de.srsoftware.umbrella.core.model.UmbrellaUser;
import org.json.JSONObject; import org.json.JSONObject;
import java.io.IOException; import java.io.IOException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.Optional; import java.util.Optional;
import static de.srsoftware.tools.Optionals.nullIfEmpty; import static de.srsoftware.tools.Optionals.nullIfEmpty;
import static de.srsoftware.umbrella.accounting.Constants.CONFIG_DATABASE; import static de.srsoftware.umbrella.accounting.Constants.CONFIG_DATABASE;
import static de.srsoftware.umbrella.core.ConnectionProvider.connect; import static de.srsoftware.umbrella.core.ConnectionProvider.connect;
import static de.srsoftware.umbrella.core.ModuleRegistry.userService;
import static de.srsoftware.umbrella.core.constants.Path.JSON; import static de.srsoftware.umbrella.core.constants.Path.JSON;
import static de.srsoftware.umbrella.core.constants.Path.SEARCH; import static de.srsoftware.umbrella.core.constants.Path.SEARCH;
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.invalidField; import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.invalidField;
@@ -42,7 +47,7 @@ public class AccountingModule extends BaseHandler implements AccountingService {
addCors(ex); addCors(ex);
try { try {
Optional<Token> token = SessionToken.from(ex).map(Token::of); Optional<Token> token = SessionToken.from(ex).map(Token::of);
var user = ModuleRegistry.userService().loadUser(token); var user = userService().loadUser(token);
if (user.isEmpty()) return unauthorized(ex); if (user.isEmpty()) return unauthorized(ex);
var head = path.pop(); var head = path.pop();
return switch (head) { return switch (head) {
@@ -60,13 +65,49 @@ public class AccountingModule extends BaseHandler implements AccountingService {
var json = json(ex); var json = json(ex);
if (!json.has(Field.ACCOUNT)) throw missingField(Field.ACCOUNT); if (!json.has(Field.ACCOUNT)) throw missingField(Field.ACCOUNT);
if (!(json.get(Field.ACCOUNT) instanceof JSONObject acc)) throw invalidField(Field.ACCOUNT,JSON); if (!(json.get(Field.ACCOUNT) instanceof JSONObject acc)) throw invalidField(Field.ACCOUNT,JSON);
// TODO more tests
if (!json.has(Field.DATE)) throw missingField(Field.DATE);
var date = LocalDate.parse(json.getString(Field.DATE));
var dateTime = LocalDateTime.now().withYear(date.getYear()).withMonth(date.getMonthValue()).withDayOfMonth(date.getDayOfMonth());
if (!json.has(Field.AMOUNT)) throw missingField(Field.AMOUNT);
if (!(json.get(Field.AMOUNT) instanceof Number amount)) throw invalidField(Field.AMOUNT,Text.NUMBER);
var purpose = json.has(Field.PURPOSE) ? json.getString(Field.PURPOSE) : null;
if (!json.has(Field.SOURCE)) throw missingField(Field.SOURCE);
if (!(json.get(Field.SOURCE) instanceof JSONObject sourceData)) throw invalidField(Field.SOURCE,JSON);
if (!json.has(Field.DESTINATION)) throw missingField(Field.DESTINATION);
if (!(json.get(Field.DESTINATION) instanceof JSONObject destinationData)) throw invalidField(Field.DESTINATION,JSON);
String source = null, destination = null;
if (sourceData.has(Field.USER_ID)) {
if (!(sourceData.get(Field.USER_ID) instanceof Number uid)) throw invalidField(String.join(".",Field.SOURCE,Field.USER_ID),Text.NUMBER);
var u = userService().loadUser(uid.longValue());
source = ""+u.id();
} else {
if (!sourceData.has(Field.DISPLAY)) throw missingField(String.join(".",Field.SOURCE,Field.DISPLAY));
source = sourceData.getString(Field.DISPLAY);
if (source.isBlank()) throw invalidField(String.join(".",Field.SOURCE,Field.DISPLAY),Text.STRING);
}
if (destinationData.has(Field.USER_ID)) {
if (!(destinationData.get(Field.USER_ID) instanceof Number uid)) throw invalidField(String.join(".",Field.DESTINATION,Field.USER_ID),Text.NUMBER);
var u = userService().loadUser(uid.longValue());
destination = ""+u;
} else {
if (!destinationData.has(Field.DISPLAY)) throw missingField(String.join(".",Field.DESTINATION,Field.DISPLAY));
destination = destinationData.getString(Field.DISPLAY);
if (destination.isBlank()) throw invalidField(String.join(".",Field.DESTINATION,Field.DISPLAY),Text.STRING);
}
Long accountId = null; Long accountId = null;
if (acc.has(Field.ID)) { if (acc.has(Field.ID)) {
if (!(acc.get(Field.ID) instanceof Number accId)) throw invalidField(String.join(".",Field.ACCOUNT,Field.ID), Text.NUMBER); if (!(acc.get(Field.ID) instanceof Number accId)) throw invalidField(String.join(".",Field.ACCOUNT,Field.ID), Text.NUMBER);
if (accId.longValue() != 0) accountId = accId.longValue(); if (accId.longValue() != 0) accountId = accId.longValue();
} }
Account newAccount = null;
if (accountId == null){ if (accountId == null){
if (!acc.has(Field.NAME)) throw missingField(String.join(".",Field.ACCOUNT,Field.NAME)); if (!acc.has(Field.NAME)) throw missingField(String.join(".",Field.ACCOUNT,Field.NAME));
var accountName = acc.getString(Field.NAME); var accountName = acc.getString(Field.NAME);
@@ -74,12 +115,12 @@ public class AccountingModule extends BaseHandler implements AccountingService {
var currency = acc.has(Field.CURRENCY) ? nullIfEmpty(acc.getString(Field.CURRENCY)) : null; var currency = acc.has(Field.CURRENCY) ? nullIfEmpty(acc.getString(Field.CURRENCY)) : null;
var account = accountDb.save(new Account(0, accountName, currency, user.id())); newAccount = accountDb.save(new Account(0, accountName, currency, user.id()));
accountId = account.id(); accountId = newAccount.id();
} }
// TODO: save entry var transaction = accountDb.save(new Transaction(accountId,dateTime,source,destination,amount.doubleValue(),purpose));
return notFound(ex); return sendContent(ex,newAccount != null ? newAccount : transaction);
} }
} }

View File

@@ -4,9 +4,11 @@ import de.srsoftware.tools.jdbc.Query;
import de.srsoftware.umbrella.core.BaseDb; import de.srsoftware.umbrella.core.BaseDb;
import de.srsoftware.umbrella.core.constants.Field; import de.srsoftware.umbrella.core.constants.Field;
import de.srsoftware.umbrella.core.model.Account; import de.srsoftware.umbrella.core.model.Account;
import de.srsoftware.umbrella.core.model.Transaction;
import java.sql.Connection; import java.sql.Connection;
import java.sql.SQLException; import java.sql.SQLException;
import java.time.ZoneOffset;
import static de.srsoftware.tools.NotImplemented.notImplemented; import static de.srsoftware.tools.NotImplemented.notImplemented;
import static de.srsoftware.umbrella.accounting.Constants.TABLE_ACCOUNTS; import static de.srsoftware.umbrella.accounting.Constants.TABLE_ACCOUNTS;
@@ -85,4 +87,17 @@ public class SqliteDb extends BaseDb implements AccountDb {
throw notImplemented(this,"save(account)"); throw notImplemented(this,"save(account)");
} }
} }
@Override
public Transaction save(Transaction transaction) {
try {
var timestamp = transaction.date().toEpochSecond(ZoneOffset.UTC);
Query.replaceInto(TABLE_TRANSACTIONS,Field.ACCOUNT,Field.TIMESTAMP,Field.SOURCE,Field.DESTINATION, Field.AMOUNT,Field.DESCRIPTION)
.values(transaction.accountId(),timestamp,transaction.source(),transaction.destination(),transaction.amount(),transaction.purpose())
.execute(db).close();
return transaction;
} catch (SQLException e) {
throw failedToStoreObject(transaction);
}
}
} }

View File

@@ -45,6 +45,7 @@ public class Field {
public static final String DELIVERY_DATE = "delivery_date"; public static final String DELIVERY_DATE = "delivery_date";
public static final String DESCRIPTION = "description"; public static final String DESCRIPTION = "description";
public static final String DESTINATION = "destination"; public static final String DESTINATION = "destination";
public static final String DISPLAY = "display";
public static final String DOCUMENT = "document"; public static final String DOCUMENT = "document";
public static final String DOCUMENT_ID = "document_id"; public static final String DOCUMENT_ID = "document_id";
public static final String DOC_TYPE_ID = "document_type_id"; public static final String DOC_TYPE_ID = "document_type_id";
@@ -129,6 +130,7 @@ public class Field {
public static final String PROJECT = "project"; public static final String PROJECT = "project";
public static final String PROJECT_ID = "project_id"; public static final String PROJECT_ID = "project_id";
public static final String PROPERTIES = "properties"; public static final String PROPERTIES = "properties";
public static final String PURPOSE = "purpose";
public static final String RECEIVERS = "receivers"; public static final String RECEIVERS = "receivers";
public static final String REDIRECT = "redirect"; public static final String REDIRECT = "redirect";

View File

@@ -1,7 +1,25 @@
package de.srsoftware.umbrella.core.model; package de.srsoftware.umbrella.core.model;
public record Account(long id, String name, String currency, long ownerId) { import de.srsoftware.tools.Mappable;
import de.srsoftware.umbrella.core.constants.Field;
import java.util.Map;
import static de.srsoftware.umbrella.core.ModuleRegistry.userService;
public record Account(long id, String name, String currency, long ownerId) implements Mappable {
public Account withId(long newId) { public Account withId(long newId) {
return new Account(newId,name,currency,ownerId); return new Account(newId,name,currency,ownerId);
} }
@Override
public Map<String, Object> toMap() {
var owner = userService().loadUser(ownerId);
return Map.of(
Field.ID, id,
Field.NAME, name,
Field.CURRENCY, currency,
Field.OWNER, owner.toMap()
);
}
} }

View File

@@ -0,0 +1,38 @@
package de.srsoftware.umbrella.core.model;
import de.srsoftware.tools.Mappable;
import de.srsoftware.umbrella.core.constants.Field;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Map;
import static de.srsoftware.umbrella.core.ModuleRegistry.userService;
public record Transaction(long accountId, LocalDateTime date, String source, String destination, double amount, String purpose) implements Mappable {
@Override
public Map<String, Object> toMap() {
var source = this.source;
try {
var userId = Long.parseLong(source);
var user = userService().loadUser(userId);
source = user.name();
} catch (NumberFormatException ignored) {}
var destination = this.destination;
try {
var userId = Long.parseLong(destination);
var user = userService().loadUser(userId);
destination = user.name();
} catch (NumberFormatException ignored) {}
return Map.of(
Field.ID, accountId,
Field.DATE, date.toLocalDate(),
Field.SOURCE, source,
Field.DESTINATION, destination,
Field.AMOUNT, amount,
Field.PURPOSE, purpose
);
}
}

View File

@@ -7,6 +7,8 @@
function newAccount(){ function newAccount(){
router.navigate('/accounting/new'); router.navigate('/accounting/new');
} }
</script> </script>
<fieldset> <fieldset>