working on autocomplete fields

Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
This commit is contained in:
2026-04-06 22:12:35 +02:00
parent 3d840b9f8a
commit 43208c5030
15 changed files with 134 additions and 56 deletions

View File

@@ -1,10 +1,11 @@
/* © SRSoftware 2025 */
package de.srsoftware.umbrella.accounting;
import de.srsoftware.umbrella.core.model.Account;
import de.srsoftware.umbrella.core.model.Transaction;
import java.util.Collection;
import java.util.List;
import java.util.Set;
public interface AccountDb {
Collection<Account> listAccounts(long userId);
@@ -16,4 +17,6 @@ public interface AccountDb {
Account save(Account account);
Transaction save(Transaction transaction);
Set<String> searchField(long userId, String field, String key);
}

View File

@@ -1,5 +1,15 @@
/* © SRSoftware 2025 */
package de.srsoftware.umbrella.accounting;
import static de.srsoftware.tools.Optionals.nullIfEmpty;
import static de.srsoftware.umbrella.accounting.Constants.CONFIG_DATABASE;
import static de.srsoftware.umbrella.core.ConnectionProvider.connect;
import static de.srsoftware.umbrella.core.ModuleRegistry.userService;
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 com.sun.net.httpserver.HttpExchange;
import de.srsoftware.configuration.Configuration;
import de.srsoftware.tools.Path;
@@ -11,26 +21,14 @@ import de.srsoftware.umbrella.core.constants.Field;
import de.srsoftware.umbrella.core.constants.Text;
import de.srsoftware.umbrella.core.exceptions.UmbrellaException;
import de.srsoftware.umbrella.core.model.*;
import org.json.JSONObject;
import static de.srsoftware.umbrella.core.constants.Path.SOURCES;
import static de.srsoftware.umbrella.core.constants.Path.DESTINATIONS;
import java.io.IOException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import static de.srsoftware.tools.Optionals.nullIfEmpty;
import static de.srsoftware.umbrella.accounting.Constants.CONFIG_DATABASE;
import static de.srsoftware.umbrella.core.ConnectionProvider.connect;
import static de.srsoftware.umbrella.core.ModuleRegistry.userService;
import static de.srsoftware.umbrella.core.Util.mapValues;
import static de.srsoftware.umbrella.core.constants.Path.JSON;
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.invalidField;
import static de.srsoftware.umbrella.core.exceptions.UmbrellaException.missingField;
import org.json.JSONObject;
public class AccountingModule extends BaseHandler implements AccountingService {
private final AccountDb accountDb;
@@ -68,14 +66,14 @@ public class AccountingModule extends BaseHandler implements AccountingService {
public boolean doPost(Path path, HttpExchange ex) throws IOException {
addCors(ex);
try {
Optional<Token> token = SessionToken.from(ex).map(Token::of);
var user = userService().loadUser(token);
var user = userService().refreshSession(ex);
if (user.isEmpty()) return unauthorized(ex);
var head = path.pop();
return switch (head) {
case null -> postEntry(user.get(),ex);
case SOURCES -> postSearchSources(user.get(),ex);
case DESTINATIONS -> postSearchDestinations(user.get(),ex);
case PURPOSES -> postSearchPurposes(user.get(),ex);
default -> super.doPost(path,ex);
};
} catch (UmbrellaException e){
@@ -114,6 +112,15 @@ public class AccountingModule extends BaseHandler implements AccountingService {
private static boolean noNumbers(String s){
try {
Long.parseLong(s);
return false;
} catch (NumberFormatException e) {
return true;
}
}
private boolean postEntry(UmbrellaUser user, HttpExchange ex) throws IOException {
var json = json(ex);
if (!json.has(Field.ACCOUNT)) throw missingField(Field.ACCOUNT);
@@ -176,16 +183,26 @@ public class AccountingModule extends BaseHandler implements AccountingService {
}
public boolean postSearchDestinations(UmbrellaUser user, HttpExchange ex) throws IOException {
var key = body(ex);
var users = userService().search(key);
// TODO: search known transactions for possible destinations
return sendContent(ex,mapValues(users));
return sendContent(ex,searchOptions(user, Field.DESTINATION, body(ex)));
}
public boolean postSearchSources(UmbrellaUser user, HttpExchange ex) throws IOException {
public boolean postSearchPurposes(UmbrellaUser user, HttpExchange ex) throws IOException {
var key = body(ex);
var purposes = accountDb.searchField(user.id(),Field.DESCRIPTION,key);
return sendContent(ex,purposes);
}
public boolean postSearchSources(UmbrellaUser user, HttpExchange ex) throws IOException {
return sendContent(ex,searchOptions(user, Field.SOURCE, body(ex)));
}
public ArrayList<Map<String,?>> searchOptions(UmbrellaUser user, String field, String key){
var users = userService().search(key);
// TODO: search known transactions for possible sources
return sendContent(ex,mapValues(users));
var destinations = accountDb.searchField(user.id(), field, key);
var optionList = new ArrayList<Map<String,?>>();
users.values().stream().map(UmbrellaUser::toMap).forEach(optionList::add);
destinations.stream().filter(AccountingModule::noNumbers).map(s -> Map.of(Field.DISPLAY,s)).forEach(optionList::add);
return optionList;
}
}

View File

@@ -1,3 +1,4 @@
/* © SRSoftware 2025 */
package de.srsoftware.umbrella.accounting;
public class Constants {

View File

@@ -1,5 +1,15 @@
/* © SRSoftware 2025 */
package de.srsoftware.umbrella.accounting;
import static de.srsoftware.tools.NotImplemented.notImplemented;
import static de.srsoftware.tools.jdbc.Condition.*;
import static de.srsoftware.tools.jdbc.Query.SelectQuery.ALL;
import static de.srsoftware.tools.jdbc.Query.select;
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 java.text.MessageFormat.format;
import de.srsoftware.tools.jdbc.Condition;
import de.srsoftware.tools.jdbc.Query;
import de.srsoftware.umbrella.core.BaseDb;
@@ -7,7 +17,6 @@ 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 java.sql.Connection;
import java.sql.SQLException;
import java.time.ZoneOffset;
@@ -15,15 +24,6 @@ import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import static de.srsoftware.tools.NotImplemented.notImplemented;
import static de.srsoftware.tools.jdbc.Condition.equal;
import static de.srsoftware.tools.jdbc.Query.SelectQuery.ALL;
import static de.srsoftware.tools.jdbc.Query.select;
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 java.text.MessageFormat.format;
public class SqliteDb extends BaseDb implements AccountDb {
public SqliteDb(Connection connection) {
super(connection);
@@ -154,4 +154,19 @@ public class SqliteDb extends BaseDb implements AccountDb {
throw failedToStoreObject(transaction);
}
}
@Override
public HashSet<String> searchField(long userId, String field , String key) {
var accounts = listAccounts(userId);
var accountIds = accounts.stream().map(Account::id).toArray();
var destinations = new HashSet<String>();
try {
var rs = Query.select("DISTINCT "+field).from(TABLE_TRANSACTIONS).where(Field.ACCOUNT,in(accountIds)).where(field,like("%"+key+"%")).exec(db);
while (rs.next()) destinations.add(rs.getString(1));
rs.close();
return destinations;
} catch (SQLException e) {
throw failedToReadFromTable(field,TABLE_TRANSACTIONS).causedBy(e);
}
}
}