Versie 0.3.3

This commit is contained in:
Atreides Assets committed 2026-10-30 12:28:48 +01:00
1 parent 1cf6eeed29
commit 6e741a51fe
57 files changed
+1362 -2135

No files matched your search

+1 -4
View File
@@ -1,7 +1,6 @@
// Generated, do not edit. sha256:37f1d463b03d2b5d702238cb632740786fc53e2072e4691719327d700c59d56c
// Generated, do not edit. sha256:fd977989705c07dc15f67feef3edd971f7d3fa3c94e064e7618ee1e171f4ce7e
import { capacityReached } from '../license.mjs';
import * as db from '../db.js';
import { counts } from './counts.js';
import { RATE_ONLY_ASSET } from './_shared.js';
import { getBaseCurrency } from './portfolio.js';
@@ -15,9 +14,7 @@ function assetCount() {
}
export function currentCount(soort) {
const c = counts();
if (soort === 'assets') return assetCount();
if (soort === 'valuations') return c.valuations;
if (soort === 'fiatAccounts') return fiatAccountCount();
throw new Error(`No counter for capacity '${soort}' yet.`);
}
+3 -1
View File
@@ -1,7 +1,8 @@
// Generated, do not edit. sha256:ebad02dba29ae50bcbaf9c21bb59936cf43f6be868e86ac00d0e6055f404b26f
// Generated, do not edit. sha256:36119ec87b42a3428d74ef9e16a174eb6f028b536ffdcea60da0bd3e970bac77
import * as db from '../db.js';
import { ACCOUNT_SOURCE_KINDS } from '../../shared/config.js';
import { fail } from '../../shared/app-error.js';
import { hasFeature } from '../license.mjs';
import { eventsNetByAccount } from './events.js';
import { trackUpdate, trackUpdateWhere, trackDelete, trackDeleteWhere, withOrigin } from './change-log.js';
@@ -34,6 +35,7 @@ export function mergeCheck(survivorId, victimId) {
}
export async function mergeAccounts(survivorId, victimId) {
if (!hasFeature('accountMerge')) fail('accounts.mergePaid', null, 'Merging accounts belongs to the full edition.');
survivorId = Number(survivorId); victimId = Number(victimId);
const chk = mergeCheck(survivorId, victimId);
if (!chk.ok) fail(`accounts.mergeReason_${chk.reason}`, null, `Merge not possible (${chk.reason}).`);
+7 -3
View File
@@ -1,4 +1,4 @@
// Generated, do not edit. sha256:ebca807e87ebabedc0e0ca214ca3bec781ace05d2be4dc4ed8d664c767fe1d63
// Generated, do not edit. sha256:1cab6c030597184175a0db5bb668aea9074d983a3f04b5255055f6a7df3c07d8
import * as db from '../db.js';
import { DASHBOARD } from '../../shared/config.js';
import { toSearchName } from '../../shared/search-name.js';
@@ -84,16 +84,20 @@ function assertWidgetType(type) {
}
}
export async function addWidget(profileId, { instanceKey, type, gridX = 0, gridY = 0, assetId = null, settings = null }) {
export async function addWidget(profileId, { instanceKey, type, gridX = 0, gridY = 0, assetId = null, settings = null, spanWidth = null }) {
if (!db.one(`SELECT id FROM DashboardProfile WHERE id = ?`, [profileId])) {
fail('dashboard.unknownProfile', { id: profileId }, `Dashboard profile #${profileId} does not exist.`);
}
assertWidgetType(type);
const span = spanWidth ?? DASHBOARD.WIDGETS[type].defaultSpan;
if (!Number.isInteger(span) || span < 1 || span > DASHBOARD.GRID_COLUMNS) {
throw new Error(`Widget width ${spanWidth} is not 1 to ${DASHBOARD.GRID_COLUMNS} columns.`);
}
const key = instanceKey || type;
if (db.one(`SELECT 1 AS x FROM DashboardWidget WHERE profileId = ? AND instanceKey = ?`, [profileId, key])) return false;
const json = settings && typeof settings === 'object' && Object.keys(settings).length ? JSON.stringify(settings) : null;
db.run(`INSERT INTO DashboardWidget (profileId, assetId, instanceKey, gridX, gridY, spanWidth, spanHeight, type, settings)
VALUES (?,?,?,?,?,?,NULL,?,?)`, [profileId, assetId, key, gridX, gridY, DASHBOARD.WIDGETS[type].defaultSpan, type, json]);
VALUES (?,?,?,?,?,?,NULL,?,?)`, [profileId, assetId, key, gridX, gridY, span, type, json]);
await db.persist();
return true;
}
+5 -3
View File
@@ -1,4 +1,4 @@
// Generated, do not edit. sha256:329990ff910102e84cb67048624a913db4948fa6ef673de3eb40c65c633864ba
// Generated, do not edit. sha256:bf3b89b4669e19b78df9f4741f695e6025ab616c2c0a5f205b84e84c1ce21f92
import * as db from '../db.js';
import { ASSET_DEFINITIONS, DASHBOARD } from '../../shared/config.js';
import { toSearchName } from '../../shared/search-name.js';
@@ -38,6 +38,8 @@ async function ensureAccount({ name, kind }) {
return createAccount({ name, kind, isOwnCustody: own, scanEnabled: false, taxCategory: null });
}
const SETUP_SPAN = 4;
function placeInFreeSpace(rows, items) {
const cols = DASHBOARD.GRID_COLUMNS;
const heightOf = (type, h) => (h > 0 ? h : DASHBOARD.WIDGETS[type]?.defaultH || 1);
@@ -47,7 +49,7 @@ function placeInFreeSpace(rows, items) {
for (let c = r.gridX; c < Math.min(cols, r.gridX + r.spanWidth); c++) bottom[c] = Math.max(bottom[c], end);
}
return items.map(({ prefer, ...w }) => {
const span = DASHBOARD.WIDGETS[w.type].defaultSpan;
const span = w.spanWidth ?? DASHBOARD.WIDGETS[w.type].defaultSpan;
let bestX = 0, bestY = Infinity;
for (let x = 0; x + span <= cols; x++) {
const y = Math.max(...bottom.slice(x, x + span));
@@ -74,7 +76,7 @@ export async function completeSetup({ assets = [], persona = 'broad', accounts =
const add = [];
if (widgets.includes('standing')) add.push({ type: 'standing', prefer: 4 });
if (widgets.includes('signal') && persona === 'bitcoin') add.push({ type: 'signal', prefer: 4 });
if (widgets.includes('spot')) assetIds.forEach(id => add.push({ type: 'spot', instanceKey: `spot-${id}`, assetId: id, prefer: 0, settings: { momentum: false } }));
if (widgets.includes('spot')) assetIds.forEach(id => add.push({ type: 'spot', instanceKey: `spot-${id}`, assetId: id, prefer: 0, settings: { momentum: false }, spanWidth: SETUP_SPAN }));
let widgetCount = 0;
for (const w of placeInFreeSpace(listWidgets(profileId), add)) if (await addWidget(profileId, w)) widgetCount++;
+1 -7
View File
@@ -1,9 +1,8 @@
// Generated, do not edit. sha256:297c7a1d9ed86e15053cd37bb7126f5fb5b54d94b67e8da901fb66f370ca9301
// Generated, do not edit. sha256:bd828bb299e2fd6141c6257cc9c20d5c7b2b7cc88932040bd9b98ce4dfeddd09
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';
@@ -123,11 +122,6 @@ export async function deleteValuation(id) {
}
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]);