Files
Umbrella/frontend/src/routes/accounting/transaction.svelte
T
2026-05-06 08:20:37 +02:00

145 lines
3.9 KiB
Svelte

<script>
import LineEditor from '../../Components/LineEditor.svelte';
import Autocomplete from '../../Components/Autocomplete.svelte';
import { api, drop, patch, post } from '../../urls.svelte';
import { error, yikes } from '../../warn.svelte';
import { t } from '../../translations.svelte';
let { account, addToFilter = tag => {}, transaction, users } = $props();
let hidden = $state(false);
function deleteTransaction(ev){
if (confirm(t('confirm_delete',{element:transaction.purpose}))){
const url = api(`accounting/transaction/${transaction.id}`);
const res = drop(url);
if (res.ok){
yikes();
} else error(res);
}
}
async function dropTag(tag){
var url = api(`accounting/transaction/${transaction.id}/tag`)
var res = await drop(url,{tag});
if (res.ok){
yikes();
transaction.tags = transaction.tags.filter(t => t != tag);
return true;
}
error(res);
return false;
}
async function getCandidates(key){
if (!key) return;
var url = api(`accounting/${account.id}/tags`)
var res = await post(url,key);
if (res.ok){
yikes();
const input = await res.json();
return Object.values(input).map(mapDisplay);
} else {
error(res);
return {};
}
}
function mapDisplay(object){
if (object.display){
return object;
} else if (object.name) {
return {...object, display: object.name};
} else {
return { display : object }
}
}
async function onCommit(tag){
let url = api(`accounting/transaction/${transaction.id}`);
let res = await patch(url,{tag:tag.display});
if (res.ok) {
yikes();
transaction.tags.push(tag.display);
transaction.tags.sort();
return true;
}
error(res);
return false;
}
async function setAmount(amount){
let result = await update({amount});
hidden = (amount == 0);
return result;
}
async function setDate(date){
return await update({date});
}
async function setDestination(destination){
return await update({destination});
}
async function setPurpose(purpose){
return await update({purpose});
}
async function setSource(source){
return await update({source});
}
async function update(changes){
let url = api('accounting/transaction/'+transaction.id);
let res = await patch(url,changes);
if (res.ok){
yikes();
for (let [k,v] of Object.entries(changes)) transaction[k]=v;
return true;
}
error(res);
return false;
}
</script>
{#if !hidden}
<tr>
<td>
<LineEditor type="date" wrapper="span" editable="true" value={transaction.date} onSet={setDate} />
</td>
{#each Object.entries(users) as [id,user]}
<td class="amount">
{#if id == transaction.source.id}
-<LineEditor type="number" wrapper="span" editable="true" value={(+transaction.amount).toFixed(2)} onSet={setAmount} title={t('Set to zero in order to drop the transaction')} />&nbsp;{account.currency}
{/if}
{#if id == transaction.destination.id}
<LineEditor type="number" wrapper="span" editable="true" value={(+transaction.amount).toFixed(2)} onSet={setAmount} title={t('Set to zero in order to drop the transaction')} />&nbsp;{account.currency}
{/if}
</td>
{/each}
<td class="party">
{#if !transaction.source.id}
<LineEditor wrapper="span" editable="true" value={transaction.source.value} onSet={setSource} />
{/if}
{#if !transaction.destination.id}
<LineEditor wrapper="span" editable="true" value={transaction.destination.value} onSet={setDestination} />
{/if}
</td>
<td class="purpose">
<LineEditor wrapper="span" editable="true" value={transaction.purpose} onSet={setPurpose} />
</td>
<td class="taglist">
{#each transaction.tags as tag,i}
<span class="tag">
<span onclick={() => addToFilter(tag)}>{tag}</span>&nbsp;<button onclick={() => dropTag(tag)} class="symbol"></button>
</span>
{/each}
<span class="tag editor">
<Autocomplete {getCandidates} {onCommit} />
</span>
</td>
<td class="actions">
<button class="symbol" onclick={deleteTransaction}></button>
</td>
</tr>
{/if}