Merge branch 'accounting' into dev
This commit is contained in:
@@ -5,11 +5,14 @@ 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;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
public interface AccountDb {
|
public interface AccountDb {
|
||||||
void dropTransactionTag(long transactionId, String tag);
|
void dropTransactionTag(long transactionId, String tag);
|
||||||
|
|
||||||
|
Optional<Transaction> lastTransaction(long accountId, String source, String dest, double amount);
|
||||||
|
|
||||||
Collection<Account> listAccounts(long userId);
|
Collection<Account> listAccounts(long userId);
|
||||||
|
|
||||||
Collection<String> listTags(long accountId, String source, String destination);
|
Collection<String> listTags(long accountId, String source, String destination);
|
||||||
|
|||||||
@@ -295,6 +295,22 @@ public class AccountingModule extends BaseHandler implements AccountingService {
|
|||||||
return sendContent(ex,newAccount != null ? newAccount : transaction);
|
return sendContent(ex,newAccount != null ? newAccount : transaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean postGetLastTransaction(long accountId, UmbrellaUser user, HttpExchange ex) throws IOException {
|
||||||
|
var json = json(ex);
|
||||||
|
if (!json.has(Field.SOURCE)) throw missingField(Field.SOURCE);
|
||||||
|
if (!(json.get(Field.SOURCE) instanceof JSONObject src)) throw invalidField(Field.SOURCE,JSON);
|
||||||
|
var source = src.get(src.has(Field.ID) ? Field.ID : Field.DISPLAY).toString();
|
||||||
|
if (!json.has(Field.DESTINATION)) throw missingField(Field.DESTINATION);
|
||||||
|
if (!(json.get(Field.DESTINATION) instanceof JSONObject dst)) throw invalidField(Field.SOURCE,JSON);
|
||||||
|
var dest = dst.get(dst.has(Field.ID) ? Field.ID : Field.DISPLAY).toString();
|
||||||
|
if (!json.has(Field.AMOUNT)) throw missingField(Field.AMOUNT);
|
||||||
|
if (!(json.get(Field.AMOUNT) instanceof Number amt)) throw invalidField(Field.AMOUNT,Text.NUMBER);
|
||||||
|
var amount = amt.doubleValue();
|
||||||
|
|
||||||
|
var transaction = accountDb.lastTransaction(accountId, source, dest, amount);
|
||||||
|
return transaction.isPresent() ? sendContent(ex,transaction.get()) : notFound(ex);
|
||||||
|
}
|
||||||
|
|
||||||
public boolean postSearchDestinations(UmbrellaUser user, HttpExchange ex) throws IOException {
|
public boolean postSearchDestinations(UmbrellaUser user, HttpExchange ex) throws IOException {
|
||||||
return sendContent(ex,searchOptions(user, Field.DESTINATION, body(ex)));
|
return sendContent(ex,searchOptions(user, Field.DESTINATION, body(ex)));
|
||||||
}
|
}
|
||||||
@@ -327,7 +343,9 @@ public class AccountingModule extends BaseHandler implements AccountingService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean postToAccount(long accountId, Path path, UmbrellaUser user, HttpExchange ex) throws IOException {
|
private boolean postToAccount(long accountId, Path path, UmbrellaUser user, HttpExchange ex) throws IOException {
|
||||||
return switch (path.pop()) {
|
var head = path.pop();
|
||||||
|
return switch (head) {
|
||||||
|
case PURPOSES -> postGetLastTransaction(accountId,user,ex);
|
||||||
case TAGS -> postSearchTags(accountId,user,ex);
|
case TAGS -> postSearchTags(accountId,user,ex);
|
||||||
case null, default -> super.doPost(path,ex);
|
case null, default -> super.doPost(path,ex);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
package de.srsoftware.umbrella.accounting;
|
package de.srsoftware.umbrella.accounting;
|
||||||
|
|
||||||
import static de.srsoftware.tools.NotImplemented.notImplemented;
|
import static de.srsoftware.tools.NotImplemented.notImplemented;
|
||||||
|
import static de.srsoftware.tools.Optionals.nullable;
|
||||||
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.*;
|
||||||
import static de.srsoftware.tools.jdbc.Query.SelectQuery.ALL;
|
import static de.srsoftware.tools.jdbc.Query.SelectQuery.ALL;
|
||||||
@@ -130,6 +131,29 @@ public class SqliteDb extends BaseDb implements AccountDb {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<Transaction> lastTransaction(long accountId, String source, String dest, double amount) {
|
||||||
|
try {
|
||||||
|
var rs = select(ALL).from(TABLE_TRANSACTIONS)
|
||||||
|
.where(ACCOUNT,equal(accountId)).where(SOURCE,equal(source)).where(DESTINATION,equal(dest)).where(AMOUNT,equal(amount))
|
||||||
|
.sort(ID+" DESC")
|
||||||
|
.limit(1)
|
||||||
|
.exec(db);
|
||||||
|
Transaction ta = null;
|
||||||
|
if (rs.next()) ta = Transaction.of(rs);
|
||||||
|
rs.close();
|
||||||
|
if (ta != null){
|
||||||
|
var tags = ta.tags();
|
||||||
|
rs = select(TAG).from(TABLE_TAGS_TRANSACTIONS).leftJoin(TAG_ID,TABLE_TAGS,ID).where(TRANSACTION_ID,equal(ta.id())).exec(db);
|
||||||
|
while (rs.next()) tags.add(rs.getString(1));
|
||||||
|
rs.close();
|
||||||
|
}
|
||||||
|
return nullable(ta);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw failedToSearchDb(t(Text.ACCOUNTING));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HashSet<Account> listAccounts(long userId) {
|
public HashSet<Account> listAccounts(long userId) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -145,7 +145,7 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<span class="autocomplete">
|
<span class="autocomplete">
|
||||||
<input type="text" bind:value={candidate.display} {onkeyup} autofocus={autofocus} />
|
<input type="text" bind:value={candidate.display} {onkeyup} autofocus={autofocus} {id} />
|
||||||
{#if candidates && candidates.length > 0}
|
{#if candidates && candidates.length > 0}
|
||||||
<ul bind:this={list_elem} class="suggestions">
|
<ul bind:this={list_elem} class="suggestions">
|
||||||
{#each candidates as candidate,i}
|
{#each candidates as candidate,i}
|
||||||
|
|||||||
@@ -42,7 +42,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function focusOnEnter(ev,id){
|
function focusOnEnter(ev,id){
|
||||||
if (ev.key == 'Enter') document.getElementById(id).focus();
|
if (ev.key == 'Enter') {
|
||||||
|
proposePurpose();
|
||||||
|
document.getElementById(id).focus();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getAccountTags(text){
|
async function getAccountTags(text){
|
||||||
@@ -93,6 +96,20 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function proposePurpose(){
|
||||||
|
const source = entry.source;
|
||||||
|
const destination = entry.destination;
|
||||||
|
const amount = entry.amount;
|
||||||
|
const url = api(`accounting/${account.id}/purposes`);
|
||||||
|
const res = await post(url,{source,destination,amount});
|
||||||
|
if (res.ok) {
|
||||||
|
yikes();
|
||||||
|
var lastTransaction = await res.json();
|
||||||
|
entry.purpose = { display: lastTransaction.purpose};
|
||||||
|
entry.tags = lastTransaction.tags;
|
||||||
|
} else error(res);
|
||||||
|
}
|
||||||
|
|
||||||
async function save(){
|
async function save(){
|
||||||
let data = {
|
let data = {
|
||||||
...entry,
|
...entry,
|
||||||
|
|||||||
Reference in New Issue
Block a user