139 lines
5.5 KiB
JavaScript
139 lines
5.5 KiB
JavaScript
// Generated, do not edit. sha256:297c7a1d9ed86e15053cd37bb7126f5fb5b54d94b67e8da901fb66f370ca9301
|
|
import * as db from '../db.js';
|
|
import { ENUMS } from '../../shared/config.js';
|
|
import { nowUtcIso } from '../../shared/time.js';
|
|
import { assertEnum } from './_shared.js';
|
|
import { capacityReachedFor } from './_limits.js';
|
|
import { fail } from '../../shared/app-error.js';
|
|
import { eventsCountByAccount, eventsNetByAccount } from './events.js';
|
|
import { trackDelete, trackUpdate } from './change-log.js';
|
|
import { emitChange } from './_events.js';
|
|
|
|
export function listValuations() {
|
|
return db.all(`SELECT v.*, ac.name AS account_name, a.symbol AS asset_symbol, a.name AS asset_name,
|
|
a.decimals AS asset_decimals, a.unit AS asset_unit, a.subunit AS asset_subunit,
|
|
a.displayUnit AS asset_display_unit, a.kind AS asset_kind
|
|
FROM Valuation v
|
|
LEFT JOIN Account ac ON ac.id = v.accountId
|
|
JOIN Asset a ON a.id = v.assetId
|
|
ORDER BY v.valuedOn DESC, v.rateAt DESC, v.id DESC`);
|
|
}
|
|
|
|
export function anchorAt(accountId, assetId, atIso) {
|
|
return db.one(
|
|
`SELECT * FROM Valuation
|
|
WHERE accountId = ?1 AND assetId = ?2 ${atIso ? 'AND rateAt <= ?3' : ''}
|
|
ORDER BY rateAt DESC, id DESC LIMIT 1`,
|
|
atIso ? [accountId, assetId, atIso] : [accountId, assetId]) || null;
|
|
}
|
|
|
|
export function latestAnchors(atIso, { ownOnly = false } = {}) {
|
|
return db.all(
|
|
`SELECT * FROM (
|
|
SELECT *, ROW_NUMBER() OVER (PARTITION BY accountId, assetId ORDER BY rateAt DESC, id DESC) AS rn
|
|
FROM Valuation WHERE accountId IS NOT NULL AND (?1 IS NULL OR rateAt <= ?1)
|
|
AND (?2 = 0 OR accountId IN (SELECT id FROM Account WHERE wealthScope IS NULL))
|
|
) WHERE rn = 1`, [atIso ?? null, ownOnly ? 1 : 0]);
|
|
}
|
|
|
|
export function anchorsBetween(accountId, assetId, fromIso, untilIso) {
|
|
return db.all(
|
|
`SELECT * FROM Valuation
|
|
WHERE accountId = ?1 AND assetId = ?2 AND rateAt > ?3 AND rateAt <= ?4
|
|
ORDER BY rateAt ASC, id ASC`, [accountId, assetId, fromIso, untilIso]);
|
|
}
|
|
|
|
export function anchorDaysBetween(accountId, assetId, vanDag, totDag) {
|
|
return db.all(
|
|
`SELECT DISTINCT valuedOn FROM Valuation
|
|
WHERE accountId = ?1 AND assetId = ?2 AND valuedOn > ?3 AND valuedOn < ?4
|
|
ORDER BY valuedOn`, [accountId, assetId, vanDag, totDag]).map(r => r.valuedOn);
|
|
}
|
|
|
|
export function firstAnchorDate() {
|
|
const r = db.one(`SELECT MIN(rateAt) AS first FROM Valuation WHERE accountId IS NOT NULL`);
|
|
return r && r.first ? r.first : null;
|
|
}
|
|
|
|
export function valuationDiff(v) {
|
|
if (!v.accountId) return null;
|
|
return v.balance - eventsNetByAccount(v.accountId, v.assetId, v.rateAt);
|
|
}
|
|
|
|
export function anchorBefore(accountId, assetId, atIso) {
|
|
return db.one(
|
|
`SELECT * FROM Valuation
|
|
WHERE accountId = ?1 AND assetId = ?2 AND rateAt < ?3
|
|
ORDER BY rateAt DESC, id DESC LIMIT 1`, [accountId, assetId, atIso]) || null;
|
|
}
|
|
|
|
export function valuationGap(v) {
|
|
if (!v.accountId) return null;
|
|
const vorige = anchorBefore(v.accountId, v.assetId, v.rateAt);
|
|
if (!vorige) return valuationDiff(v);
|
|
const sindsVorige = eventsNetByAccount(v.accountId, v.assetId, v.rateAt)
|
|
- eventsNetByAccount(v.accountId, v.assetId, vorige.rateAt);
|
|
return v.balance - (vorige.balance + sindsVorige);
|
|
}
|
|
|
|
export function valuationReconcilable(v) {
|
|
if (!v.accountId) return false;
|
|
return eventsCountByAccount(v.accountId, v.assetId, v.rateAt) > 0;
|
|
}
|
|
|
|
function verrijkt() {
|
|
const nu = nowUtcIso();
|
|
return listValuations().map(v => ({
|
|
...v,
|
|
diff: valuationDiff(v),
|
|
gap: valuationGap(v),
|
|
reconcilable: valuationReconcilable(v),
|
|
accepted: !!v.gapAcceptedAt,
|
|
future: v.rateAt > nu,
|
|
}));
|
|
}
|
|
|
|
export function valuationDiffs() {
|
|
return verrijkt().map(({ id, diff, gap, reconcilable, accepted, future }) =>
|
|
({ id, diff, gap, reconcilable, accepted, future }));
|
|
}
|
|
|
|
export function listOpenValuationDiffs() {
|
|
return verrijkt().filter(v => !v.future && !v.accepted && v.reconcilable && v.gap != null && v.gap !== 0);
|
|
}
|
|
export async function updateValuation(id, v) {
|
|
assertEnum('source', ENUMS.VALUATION_SOURCE, v.source);
|
|
trackUpdate('Valuation', id, () =>
|
|
db.run(`UPDATE Valuation SET assetId=?, valuedOn=?, accountId =?, balance=?, fiatRate=?, rateAt=?, source=? WHERE id=?`,
|
|
[v.assetId, v.valuedOn, v.accountId ?? null, v.balance, v.fiatRate, v.rateAt, v.source, id]));
|
|
await db.persist();
|
|
emitChange('ledger-changed');
|
|
}
|
|
export async function acceptValuationGap(id, aan = true) {
|
|
trackUpdate('Valuation', id, () =>
|
|
db.run(`UPDATE Valuation SET gapAcceptedAt = ? WHERE id = ?`, [aan ? nowUtcIso() : null, id]));
|
|
await db.persist();
|
|
emitChange('ledger-changed');
|
|
}
|
|
|
|
export async function deleteValuation(id) {
|
|
trackDelete('Valuation', id, () => db.run(`DELETE FROM Valuation WHERE id=?`, [id]));
|
|
await db.persist();
|
|
emitChange('ledger-changed');
|
|
}
|
|
export async function createValuation(v) {
|
|
assertEnum('source', ENUMS.VALUATION_SOURCE, v.source);
|
|
const max = capacityReachedFor('valuations');
|
|
if (max !== null) {
|
|
fail('valuations.maxFree', { max },
|
|
`The free version holds ${max} balance(s). A licence lifts that; nothing is removed in the meantime.`);
|
|
}
|
|
db.run(`INSERT INTO Valuation (assetId, valuedOn, accountId, balance, fiatRate, rateAt, source)
|
|
VALUES (?,?,?,?,?,?,?)`,
|
|
[v.assetId, v.valuedOn, v.accountId ?? null, v.balance, v.fiatRate, v.rateAt, v.source]);
|
|
const id = db.lastId();
|
|
await db.persist();
|
|
emitChange('ledger-changed');
|
|
return id;
|
|
}
|