preparing storage of tags

Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
This commit is contained in:
2026-04-09 09:09:17 +02:00
parent b4b3173cc7
commit 85efb0ec02
5 changed files with 106 additions and 19 deletions

View File

@@ -25,10 +25,7 @@ import de.srsoftware.umbrella.core.model.*;
import java.io.IOException; import java.io.IOException;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.ArrayList; import java.util.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONObject; import org.json.JSONObject;
@@ -180,10 +177,13 @@ public class AccountingModule extends BaseHandler implements AccountingService {
accountId = newAccount.id(); accountId = newAccount.id();
} }
var transaction = accountDb.save(new Transaction(accountId,dateTime,source,destination,amount.doubleValue(),purpose)); var tagList = json.has(Field.TAGS) && json.get(Field.TAGS) instanceof JSONArray t ? t : List.of();
var tags = new HashSet<String>();
tagList.forEach(e -> tags.add(e.toString()));
var transaction = accountDb.save(new Transaction(0,accountId,dateTime,source,destination,amount.doubleValue(),purpose,tags));
var tags = json.has(Field.TAGS) && json.get(Field.TAGS) instanceof JSONArray t ? t : null;
if (tags != null) LOG.log(WARNING, "Tagging transactions not implemented!");
return sendContent(ex,newAccount != null ? newAccount : transaction); return sendContent(ex,newAccount != null ? newAccount : transaction);
} }

View File

@@ -5,5 +5,7 @@ public class Constants {
public static final String CONFIG_DATABASE = "umbrella.modules.accounting.database"; public static final String CONFIG_DATABASE = "umbrella.modules.accounting.database";
public static final String TABLE_ACCOUNTS = "accounts"; public static final String TABLE_ACCOUNTS = "accounts";
public static final String TABLE_TAGS = "tag";
public static final String TABLE_TAGS_TRANSACTIONS = "tags_transactions";
public static final String TABLE_TRANSACTIONS = "transactions"; public static final String TABLE_TRANSACTIONS = "transactions";
} }

View File

@@ -3,10 +3,9 @@ package de.srsoftware.umbrella.accounting;
import static de.srsoftware.tools.NotImplemented.notImplemented; import static de.srsoftware.tools.NotImplemented.notImplemented;
import static de.srsoftware.tools.jdbc.Condition.*; import static de.srsoftware.tools.jdbc.Condition.*;
import static de.srsoftware.tools.jdbc.Query.*;
import static de.srsoftware.tools.jdbc.Query.SelectQuery.ALL; import static de.srsoftware.tools.jdbc.Query.SelectQuery.ALL;
import static de.srsoftware.tools.jdbc.Query.select; import static de.srsoftware.umbrella.accounting.Constants.*;
import static de.srsoftware.umbrella.accounting.Constants.TABLE_ACCOUNTS;
import static de.srsoftware.umbrella.accounting.Constants.TABLE_TRANSACTIONS;
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.*; import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.*;
import static java.text.MessageFormat.format; import static java.text.MessageFormat.format;
@@ -15,12 +14,14 @@ 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.constants.Text; import de.srsoftware.umbrella.core.constants.Text;
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.Transaction; 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 java.time.ZoneOffset;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
@@ -36,6 +37,8 @@ public class SqliteDb extends BaseDb implements AccountDb {
case 0: case 0:
createAccountsTable(); createAccountsTable();
createTransactionsTable(); createTransactionsTable();
createTagsTable();
createTagsTransactionsTable();
} }
return setCurrentVersion(1); return setCurrentVersion(1);
} }
@@ -59,9 +62,45 @@ public class SqliteDb extends BaseDb implements AccountDb {
} }
} }
private void createTagsTable(){
var sql = """
CREATE TABLE IF NOT EXISTS {0} (
{1} INTEGER PRIMARY KEY,
{2} VARCHAR(32) UNIQUE NOT NULL
);
""";
try {
sql = format(sql,TABLE_TAGS,Field.ID,Field.TAG);
var stmt = db.prepareStatement(sql);
stmt.execute();
stmt.close();
} catch (SQLException e) {
throw failedToCreateTable(TABLE_TAGS).causedBy(e);
}
}
private void createTagsTransactionsTable(){
var sql = """
CREATE TABLE IF NOT EXISTS {0} (
{1} LONG NOT NULL,
{2} LONG NOT NULL,
PRIMARY KEY({1}, {2})
);
""";
try {
sql = format(sql,TABLE_TAGS_TRANSACTIONS,Field.TRANSACTION_ID,Field.TAG_ID);
var stmt = db.prepareStatement(sql);
stmt.execute();
stmt.close();
} catch (SQLException e) {
throw failedToCreateTable(TABLE_TAGS).causedBy(e);
}
}
private void createTransactionsTable() { private void createTransactionsTable() {
var sql = """ var sql = """
CREATE TABLE IF NOT EXISTS {0} ( CREATE TABLE IF NOT EXISTS {0} (
{7} INTEGER PRIMARY KEY,
{1} LONG NOT NULL, {1} LONG NOT NULL,
{2} LONG NOT NULL, {2} LONG NOT NULL,
{3} VARCHAR(255) NOT NULL, {3} VARCHAR(255) NOT NULL,
@@ -70,7 +109,7 @@ public class SqliteDb extends BaseDb implements AccountDb {
{6} TEXT {6} TEXT
);"""; );""";
try { try {
sql = format(sql,TABLE_TRANSACTIONS,Field.ACCOUNT,Field.TIMESTAMP,Field.SOURCE,Field.DESTINATION, Field.AMOUNT,Field.DESCRIPTION); sql = format(sql,TABLE_TRANSACTIONS,Field.ACCOUNT,Field.TIMESTAMP,Field.SOURCE,Field.DESTINATION, Field.AMOUNT,Field.DESCRIPTION, Field.ID);
var stmt = db.prepareStatement(sql); var stmt = db.prepareStatement(sql);
stmt.execute(); stmt.execute();
stmt.close(); stmt.close();
@@ -144,13 +183,49 @@ public class SqliteDb extends BaseDb implements AccountDb {
@Override @Override
public Transaction save(Transaction transaction) { public Transaction save(Transaction transaction) {
if (transaction.id() == 0) {
try {
var timestamp = transaction.date().toEpochSecond(ZoneOffset.UTC);
var rs = Query.insertInto(TABLE_TRANSACTIONS, Field.ACCOUNT, Field.TIMESTAMP, Field.SOURCE, Field.DESTINATION, Field.AMOUNT, Field.DESCRIPTION)
.values(transaction.accountId(), timestamp, transaction.source().value(), transaction.destination().value(), transaction.amount(), transaction.purpose())
.execute(db).getGeneratedKeys();
if (rs.next()) transaction = transaction.withId(rs.getLong(1));
rs.close();
} catch (SQLException e) {
throw failedToStoreObject(transaction);
}
} else { // TODO : implement update
throw UmbrellaException.failedToStoreObject(transaction);
}
saveTags(transaction);
return transaction;
}
private void saveTags(Transaction transaction) {
var remaining = new HashSet<String>(transaction.tags());
var existingTags = new HashMap<String,Long>();
try { try {
var timestamp = transaction.date().toEpochSecond(ZoneOffset.UTC); var rs = select(ALL).from(TABLE_TAGS).where(Field.TAG,in(transaction.tags().toArray())).exec(db);
Query.replaceInto(TABLE_TRANSACTIONS,Field.ACCOUNT,Field.TIMESTAMP,Field.SOURCE,Field.DESTINATION, Field.AMOUNT,Field.DESCRIPTION) while (rs.next()) existingTags.put(rs.getString(Field.TAG), rs.getLong(Field.ID));
.values(transaction.accountId(),timestamp,transaction.source().value(),transaction.destination().value(),transaction.amount(),transaction.purpose()) rs.close();
.execute(db).close(); } catch (SQLException e){
return transaction; throw failedToLoadMembers(transaction);
} catch (SQLException e) { }
remaining.removeAll(existingTags.keySet());
for (var tag : remaining) {
try {
var rs = insertInto(TABLE_TAGS, Field.TAG).values(tag).execute(db).getGeneratedKeys();
if (rs.next()) existingTags.put(tag,rs.getLong(1));
rs.close();
} catch (SQLException e){
throw failedToStoreObject(tag);
}
}
try {
var query = replaceInto(TABLE_TAGS_TRANSACTIONS, Field.TRANSACTION_ID, Field.TAG_ID);
for (var tag_id : existingTags.values()) query.values(transaction.id(), tag_id);
query.execute(db).close();
} catch (SQLException e){
throw failedToStoreObject(transaction); throw failedToStoreObject(transaction);
} }
} }

View File

@@ -157,6 +157,7 @@ public class Field {
public static final String TAG = "tag"; public static final String TAG = "tag";
public static final String TAGS = "tags"; public static final String TAGS = "tags";
public static final String TAG_COLORS = "tag_colors"; public static final String TAG_COLORS = "tag_colors";
public static final String TAG_ID = "tag_id";
public static final String TASK = "task"; public static final String TASK = "task";
public static final String TASK_IDS = "task_ids"; public static final String TASK_IDS = "task_ids";
public static final String TASKS = "tasks"; public static final String TASKS = "tasks";
@@ -175,6 +176,7 @@ public class Field {
public static final String TO = "to"; public static final String TO = "to";
public static final String TOKEN = "token"; public static final String TOKEN = "token";
public static final String TOTAL_PRIO = "total_prio"; public static final String TOTAL_PRIO = "total_prio";
public static final String TRANSACTION_ID = "transaction_id";
public static final String TRANSACTIONS = "transactions"; public static final String TRANSACTIONS = "transactions";
public static final String TYPE = "type"; public static final String TYPE = "type";
public static final String TYPE_ID = "type_id"; public static final String TYPE_ID = "type_id";

View File

@@ -7,9 +7,11 @@ import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.ZoneOffset; import java.time.ZoneOffset;
import java.util.HashSet;
import java.util.Map; import java.util.Map;
import java.util.Set;
public record Transaction(long accountId, LocalDateTime date, IdOrString source, IdOrString destination, double amount, String purpose) implements Mappable { public record Transaction(long id, long accountId, LocalDateTime date, IdOrString source, IdOrString destination, double amount, String purpose, Set<String> tags) implements Mappable {
public static Transaction of(ResultSet rs) throws SQLException { public static Transaction of(ResultSet rs) throws SQLException {
@@ -20,12 +22,14 @@ public record Transaction(long accountId, LocalDateTime date, IdOrString source,
var destination = IdOrString.of(rs.getString(Field.DESTINATION)); var destination = IdOrString.of(rs.getString(Field.DESTINATION));
var amount = rs.getDouble(Field.AMOUNT); var amount = rs.getDouble(Field.AMOUNT);
var purpose = rs.getString(Field.DESCRIPTION); var purpose = rs.getString(Field.DESCRIPTION);
return new Transaction(accountId,date,source,destination,amount,purpose); var id = rs.getLong(Field.ID);
return new Transaction(id, accountId, date, source, destination, amount, purpose, new HashSet<>());
} }
@Override @Override
public Map<String, Object> toMap() { public Map<String, Object> toMap() {
return Map.of( return Map.of(
Field.ID, id,
Field.ACCOUNT, accountId, Field.ACCOUNT, accountId,
Field.DATE, date.toLocalDate(), Field.DATE, date.toLocalDate(),
Field.SOURCE, source.toMap(), Field.SOURCE, source.toMap(),
@@ -34,4 +38,8 @@ public record Transaction(long accountId, LocalDateTime date, IdOrString source,
Field.PURPOSE, purpose Field.PURPOSE, purpose
); );
} }
public Transaction withId(long id) {
return new Transaction(id, accountId, date, source, destination, amount, purpose, new HashSet<>(tags));
}
} }