working on loading of account data
Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
This commit is contained in:
@@ -4,9 +4,15 @@ import de.srsoftware.umbrella.core.model.Account;
|
|||||||
import de.srsoftware.umbrella.core.model.Transaction;
|
import de.srsoftware.umbrella.core.model.Transaction;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public interface AccountDb {
|
public interface AccountDb {
|
||||||
Collection<Account> listAccounts(long userId);
|
Collection<Account> listAccounts(long userId);
|
||||||
|
|
||||||
|
Account loadAccount(long accountId);
|
||||||
|
|
||||||
|
List<Transaction> loadTransactions(Account account);
|
||||||
|
|
||||||
Account save(Account account);
|
Account save(Account account);
|
||||||
|
|
||||||
Transaction save(Transaction transaction);
|
Transaction save(Transaction transaction);
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ import java.io.IOException;
|
|||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.ZoneOffset;
|
import java.time.ZoneOffset;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
import static de.srsoftware.tools.Optionals.nullIfEmpty;
|
import static de.srsoftware.tools.Optionals.nullIfEmpty;
|
||||||
@@ -53,10 +55,13 @@ public class AccountingModule extends BaseHandler implements AccountingService {
|
|||||||
var head = path.pop();
|
var head = path.pop();
|
||||||
return switch (head) {
|
return switch (head) {
|
||||||
case null -> getAccounts(user.get(),ex);
|
case null -> getAccounts(user.get(),ex);
|
||||||
default -> super.doGet(path,ex);
|
default -> {
|
||||||
|
try {
|
||||||
|
yield getAccount(user.get(),Long.parseLong(head),ex);
|
||||||
|
} catch (NumberFormatException ignored) {}
|
||||||
|
yield super.doGet(path,ex);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
} catch (NumberFormatException e){
|
|
||||||
return sendContent(ex,HTTP_BAD_REQUEST,"Invalid project id");
|
|
||||||
} catch (UmbrellaException e){
|
} catch (UmbrellaException e){
|
||||||
return send(ex,e);
|
return send(ex,e);
|
||||||
}
|
}
|
||||||
@@ -74,13 +79,41 @@ public class AccountingModule extends BaseHandler implements AccountingService {
|
|||||||
case null -> postEntry(user.get(),ex);
|
case null -> postEntry(user.get(),ex);
|
||||||
default -> super.doPost(path,ex);
|
default -> super.doPost(path,ex);
|
||||||
};
|
};
|
||||||
} catch (NumberFormatException e){
|
|
||||||
return sendContent(ex,HTTP_BAD_REQUEST,"Invalid project id");
|
|
||||||
} catch (UmbrellaException e){
|
} catch (UmbrellaException e){
|
||||||
return send(ex,e);
|
return send(ex,e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean getAccount(UmbrellaUser user, long accountId, HttpExchange ex) throws IOException {
|
||||||
|
var account = accountDb.loadAccount(accountId);
|
||||||
|
var transactions = accountDb.loadTransactions(account);
|
||||||
|
var userMap = new HashMap<Long,UmbrellaUser>();
|
||||||
|
var foundRequestingUser = false;
|
||||||
|
for (var i=0; i<transactions.size();i++){
|
||||||
|
var transaction = transactions.get(i);
|
||||||
|
try {
|
||||||
|
var userId = Long.parseLong(transaction.source());
|
||||||
|
var u = userMap.get(userId);
|
||||||
|
if (u == null) userMap.put(userId,u=userService().loadUser(userId));
|
||||||
|
if (!foundRequestingUser) foundRequestingUser = user.equals(u);
|
||||||
|
transaction = new Transaction(transaction.accountId(),transaction.date(),u.name(),transaction.destination(),transaction.amount(),transaction.purpose());
|
||||||
|
transactions.set(i,transaction);
|
||||||
|
} catch (NumberFormatException ignored){}
|
||||||
|
try {
|
||||||
|
var userId = Long.parseLong(transaction.destination());
|
||||||
|
var u = userMap.get(userId);
|
||||||
|
if (u == null) userMap.put(userId,u=userService().loadUser(userId));
|
||||||
|
if (!foundRequestingUser) foundRequestingUser = user.equals(u);
|
||||||
|
transaction = new Transaction(transaction.accountId(),transaction.date(),transaction.source(),u.name(),transaction.amount(),transaction.purpose());
|
||||||
|
transactions.set(i,transaction);
|
||||||
|
} catch (NumberFormatException ignored){}
|
||||||
|
}
|
||||||
|
return sendContent(ex, Map.of(
|
||||||
|
Field.ACCOUNT,account.toMap(),
|
||||||
|
Field.TRANSACTIONS,transactions.stream().map(Transaction::toMap).toList()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
private boolean getAccounts(UmbrellaUser user, HttpExchange ex) throws IOException {
|
private boolean getAccounts(UmbrellaUser user, HttpExchange ex) throws IOException {
|
||||||
return sendContent(ex,accountDb.listAccounts(user.id()).stream().map(Account::toMap));
|
return sendContent(ex,accountDb.listAccounts(user.id()).stream().map(Account::toMap));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,11 +11,14 @@ 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.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import static de.srsoftware.tools.NotImplemented.notImplemented;
|
import static de.srsoftware.tools.NotImplemented.notImplemented;
|
||||||
import static de.srsoftware.tools.jdbc.Condition.equal;
|
import static de.srsoftware.tools.jdbc.Condition.equal;
|
||||||
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.TABLE_ACCOUNTS;
|
import static de.srsoftware.umbrella.accounting.Constants.TABLE_ACCOUNTS;
|
||||||
import static de.srsoftware.umbrella.accounting.Constants.TABLE_TRANSACTIONS;
|
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.*;
|
||||||
@@ -80,14 +83,14 @@ public class SqliteDb extends BaseDb implements AccountDb {
|
|||||||
public HashSet<Account> listAccounts(long userId) {
|
public HashSet<Account> listAccounts(long userId) {
|
||||||
try {
|
try {
|
||||||
var accountIds = new HashSet<Long>();
|
var accountIds = new HashSet<Long>();
|
||||||
var rs = Query.select("DISTINCT " + Field.ACCOUNT).from(TABLE_TRANSACTIONS).where(Field.SOURCE, equal(userId)).exec(db);
|
var rs = select("DISTINCT " + Field.ACCOUNT).from(TABLE_TRANSACTIONS).where(Field.SOURCE, equal(userId)).exec(db);
|
||||||
while (rs.next()) accountIds.add(rs.getLong(1));
|
while (rs.next()) accountIds.add(rs.getLong(1));
|
||||||
rs.close();
|
rs.close();
|
||||||
rs = Query.select("DISTINCT " + Field.ACCOUNT).from(TABLE_TRANSACTIONS).where(Field.DESTINATION, equal(userId)).exec(db);
|
rs = select("DISTINCT " + Field.ACCOUNT).from(TABLE_TRANSACTIONS).where(Field.DESTINATION, equal(userId)).exec(db);
|
||||||
while (rs.next()) accountIds.add(rs.getLong(1));
|
while (rs.next()) accountIds.add(rs.getLong(1));
|
||||||
rs.close();
|
rs.close();
|
||||||
var accounts = new HashSet<Account>();
|
var accounts = new HashSet<Account>();
|
||||||
rs = Query.select(ALL).from(TABLE_ACCOUNTS).where(Field.ID, Condition.in(accountIds.toArray())).exec(db);
|
rs = select(ALL).from(TABLE_ACCOUNTS).where(Field.ID, Condition.in(accountIds.toArray())).exec(db);
|
||||||
while (rs.next()) accounts.add(Account.of(rs));
|
while (rs.next()) accounts.add(Account.of(rs));
|
||||||
rs.close();
|
rs.close();
|
||||||
return accounts;
|
return accounts;
|
||||||
@@ -96,6 +99,33 @@ public class SqliteDb extends BaseDb implements AccountDb {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Account loadAccount(long accountId) {
|
||||||
|
try {
|
||||||
|
var rs = select(ALL).from(TABLE_ACCOUNTS).where(Field.ID,equal(accountId)).exec(db);
|
||||||
|
Account account = null;
|
||||||
|
if (rs.next()) account = Account.of(rs);
|
||||||
|
rs.close();
|
||||||
|
if (account==null) throw failedToLoadObject(Text.ACCOUNT,accountId);
|
||||||
|
return account;
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Transaction> loadTransactions(Account account) {
|
||||||
|
try {
|
||||||
|
var list = new ArrayList<Transaction>();
|
||||||
|
var rs = select(ALL).from(TABLE_TRANSACTIONS).where(Field.ACCOUNT,equal(account.id())).exec(db);
|
||||||
|
while (rs.next()) list.add(Transaction.of(rs));
|
||||||
|
rs.close();
|
||||||
|
return list;
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw failedToLoadMembers(account);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Account save(Account account) {
|
public Account save(Account account) {
|
||||||
if (account.id() == 0) try { // new account
|
if (account.id() == 0) try { // new account
|
||||||
|
|||||||
@@ -175,6 +175,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 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";
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ package de.srsoftware.umbrella.core.constants;
|
|||||||
* This is a collection of messages that appear throughout the project
|
* This is a collection of messages that appear throughout the project
|
||||||
*/
|
*/
|
||||||
public class Text {
|
public class Text {
|
||||||
|
public static final String ACCOUNT = "account";
|
||||||
public static final String ACCOUNTING = "accounting";
|
public static final String ACCOUNTING = "accounting";
|
||||||
|
|
||||||
public static final String BOOKMARK = "bookmark";
|
public static final String BOOKMARK = "bookmark";
|
||||||
|
|||||||
@@ -3,13 +3,27 @@ package de.srsoftware.umbrella.core.model;
|
|||||||
import de.srsoftware.tools.Mappable;
|
import de.srsoftware.tools.Mappable;
|
||||||
import de.srsoftware.umbrella.core.constants.Field;
|
import de.srsoftware.umbrella.core.constants.Field;
|
||||||
|
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.ZoneOffset;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import static de.srsoftware.umbrella.core.ModuleRegistry.userService;
|
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 {
|
public record Transaction(long accountId, LocalDateTime date, String source, String destination, double amount, String purpose) implements Mappable {
|
||||||
|
public static Transaction of(ResultSet rs) throws SQLException {
|
||||||
|
var accountId = rs.getLong(Field.ACCOUNT);
|
||||||
|
var timestamp = rs.getLong(Field.TIMESTAMP);
|
||||||
|
var date = LocalDateTime.ofEpochSecond(timestamp,0, ZoneOffset.UTC);
|
||||||
|
var source = rs.getString(Field.SOURCE);
|
||||||
|
var destination = rs.getString(Field.DESTINATION);
|
||||||
|
var amount = rs.getDouble(Field.AMOUNT);
|
||||||
|
var purpose = rs.getString(Field.DESCRIPTION);
|
||||||
|
return new Transaction(accountId,date,source,destination,amount,purpose);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<String, Object> toMap() {
|
public Map<String, Object> toMap() {
|
||||||
var source = this.source;
|
var source = this.source;
|
||||||
@@ -27,7 +41,7 @@ public record Transaction(long accountId, LocalDateTime date, String source, Str
|
|||||||
} catch (NumberFormatException ignored) {}
|
} catch (NumberFormatException ignored) {}
|
||||||
|
|
||||||
return Map.of(
|
return Map.of(
|
||||||
Field.ID, accountId,
|
Field.ACCOUNT, accountId,
|
||||||
Field.DATE, date.toLocalDate(),
|
Field.DATE, date.toLocalDate(),
|
||||||
Field.SOURCE, source,
|
Field.SOURCE, source,
|
||||||
Field.DESTINATION, destination,
|
Field.DESTINATION, destination,
|
||||||
|
|||||||
@@ -1,5 +1,50 @@
|
|||||||
<script>
|
<script>
|
||||||
let { id } = $props();
|
import { onMount } from 'svelte';
|
||||||
|
import { api, get } from '../../urls.svelte';
|
||||||
|
import { error, yikes } from '../../warn.svelte';
|
||||||
|
import { t } from '../../translations.svelte';
|
||||||
|
|
||||||
|
let { id } = $props();
|
||||||
|
let account = $state(null);
|
||||||
|
let transactions = [];
|
||||||
|
|
||||||
|
async function load(){
|
||||||
|
let url = api(`accounting/${id}`);
|
||||||
|
let res = await get(url);
|
||||||
|
if (res.ok) {
|
||||||
|
yikes();
|
||||||
|
let json = await res.json();
|
||||||
|
transactions = json.transactions;
|
||||||
|
account = json.account;
|
||||||
|
} else error(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(load);
|
||||||
</script>
|
</script>
|
||||||
<h1>Account {id}</h1>
|
{#if account}
|
||||||
|
<fieldset>
|
||||||
|
<legend>{account.name}</legend>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{t('date')}</th>
|
||||||
|
<th>{t('source')}</th>
|
||||||
|
<th>{t('destination')}</th>
|
||||||
|
<th>{t('amount')}</th>
|
||||||
|
<th>{t('purpose')}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{#each transactions as transaction, i}
|
||||||
|
<tr>
|
||||||
|
<td>{transaction.date}</td>
|
||||||
|
<td>{transaction.source}</td>
|
||||||
|
<td>{transaction.destination}</td>
|
||||||
|
<td>{transaction.amount} {account.currency}</td>
|
||||||
|
<td>{transaction.purpose}</td>
|
||||||
|
</tr>
|
||||||
|
{/each}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</fieldset>
|
||||||
|
{/if}
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
<ul>
|
<ul>
|
||||||
{#each accounts as account (account.id)}
|
{#each accounts as account (account.id)}
|
||||||
<li>
|
<li>
|
||||||
<a {onclick} href="account/{account.id}">{account.name} ({account.id})</a>
|
<a {onclick} href="/account/{account.id}">{account.name} ({account.id})</a>
|
||||||
</li>
|
</li>
|
||||||
{/each}
|
{/each}
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
Reference in New Issue
Block a user