Files

88 lines
5.0 KiB
JavaScript
Raw Permalink Normal View History

2026-10-29 11:57:29 +01:00
// Generated, do not edit. sha256:8eb5f3330fcdfdbe9052fd4560a6ccd48f5f3aa7ef341ffd35df63e368fe3a71
import * as db from '../db.js';
import { toSearchName } from '../../shared/search-name.js';
import { ENUMS } from '../../shared/config.js';
import { assertEnum } from './_shared.js';
import { fail } from '../../shared/app-error.js';
import { trackDelete, trackUpdate } from './change-log.js';
export function hasMetalAssets() {
return db.one(`SELECT 1 AS x FROM Asset WHERE kind = 'metal' LIMIT 1`) != null;
}
export function listMetalAssets() {
return db.all(`SELECT * FROM Asset WHERE kind = 'metal' ORDER BY symbol`);
}
export function listMetalProducts() {
return db.all(`SELECT mp.*, a.symbol AS asset_symbol, a.name AS asset_name
FROM MetalProduct mp JOIN Asset a ON a.id = mp.assetId
ORDER BY a.symbol, mp.form, mp.fineGrams`);
}
export function listMetalProductsForAsset(assetId) {
return db.all(`SELECT * FROM MetalProduct WHERE assetId = ? ORDER BY form, fineGrams`, [assetId]);
}
function assertMetalProduct({ assetId, name, form, fineGrams }) {
assertEnum('form', ENUMS.METAL_FORM, form);
if (!Number.isInteger(assetId)) fail('metalProducts.assetRequired', null, 'Pick a valid metal asset.');
if (!String(name || '').trim()) fail('metalProducts.nameRequired', null, 'The name is required.');
if (!Number.isInteger(fineGrams) || fineGrams <= 0) fail('metalProducts.fineGramsPositive', null, 'The fine weight has to be greater than 0.');
const a = db.one(`SELECT kind FROM Asset WHERE id = ?`, [assetId]);
if (!a || a.kind !== 'metal') fail('metalProducts.notMetal', null, 'The chosen asset is not a metal.');
}
export async function createMetalProduct(p) {
assertMetalProduct(p);
db.run(`INSERT INTO MetalProduct (assetId, name, searchName, form, fineGrams) VALUES (?,?,?,?,?)`,
[p.assetId, p.name.trim(), toSearchName(p.name), p.form, p.fineGrams]);
const id = db.lastId();
await db.persist();
return id;
}
export async function updateMetalProduct(id, p) {
assertMetalProduct(p);
trackUpdate('MetalProduct', id, () =>
db.run(`UPDATE MetalProduct SET assetId = ?, name = ?, searchName = ?, form = ?, fineGrams = ? WHERE id = ?`,
[p.assetId, p.name.trim(), toSearchName(p.name), p.form, p.fineGrams, id]));
await db.persist();
}
export async function deleteMetalProduct(id) {
const used = db.one(`SELECT COUNT(*) AS n FROM Event WHERE productId = ?`, [id]).n;
if (used > 0) fail('metalProducts.inUse', { n: used }, `Product is used by ${used} event(s); remove those first.`);
trackDelete('MetalProduct', id, () => db.run(`DELETE FROM MetalProduct WHERE id = ?`, [id]));
await db.persist();
}
export function metalHoldings({ ownOnly = false } = {}) {
return db.all(`
SELECT mp.id, mp.name, mp.form, mp.fineGrams, mp.assetId,
a.symbol AS asset_symbol, a.name AS asset_name,
COALESCE(SUM(CASE
WHEN e.type = 'buy' AND e.toIn THEN e.productCount
WHEN e.type = 'sell' AND e.fromIn THEN -e.productCount
-- Only a transfer between two ACCOUNTS of which one is own: a transfer out of the
-- administration counts no pieces in either mode, so the two modes agree without a scope.
WHEN e.type = 'transfer' AND ?1 = 1 AND e.toAccountId IS NOT NULL AND e.fromAccountId IS NOT NULL THEN
(CASE WHEN e.toIn AND NOT e.fromIn THEN e.productCount ELSE 0 END)
- (CASE WHEN e.fromIn AND NOT e.toIn THEN e.productCount ELSE 0 END)
ELSE 0 END), 0) AS net_count,
COALESCE(SUM(CASE WHEN e.type = 'buy' AND e.toIn THEN e.productCount ELSE 0 END), 0) AS buy_count,
COALESCE(SUM(CASE WHEN e.type = 'buy' AND e.toIn AND e.priceOrigin IS NOT 'none' THEN 1 ELSE 0 END), 0) AS buy_events,
COALESCE(SUM(CASE WHEN e.type = 'buy' AND e.toIn AND e.priceOrigin IS NOT 'none' THEN COALESCE(e.fiatFee,0) ELSE 0 END), 0) AS buy_premium,
COALESCE(SUM(CASE WHEN e.type = 'buy' AND e.toIn AND e.priceOrigin IS NOT 'none' THEN e.amount ELSE 0 END), 0) AS buy_amount,
COALESCE(SUM(CASE WHEN e.type = 'buy' AND e.toIn AND e.priceOrigin IS NOT 'none' THEN e.amount * COALESCE(e.fiatPrice,0) ELSE 0 END), 0) AS buy_spot_raw,
COALESCE(SUM(CASE WHEN e.priceOrigin IS 'none' THEN
(CASE WHEN e.type = 'buy' AND e.toIn THEN e.amount WHEN e.type = 'sell' AND e.fromIn THEN -e.amount ELSE 0 END)
ELSE 0 END), 0) AS gift_amount
FROM MetalProduct mp
JOIN Asset a ON a.id = mp.assetId
LEFT JOIN (
SELECT Event.*,
(?1 = 0 OR toAccount.wealthScope IS NULL) AS toIn,
(?1 = 0 OR fromAccount.wealthScope IS NULL) AS fromIn
FROM Event
LEFT JOIN Account toAccount ON toAccount.id = Event.toAccountId
LEFT JOIN Account fromAccount ON fromAccount.id = Event.fromAccountId
WHERE Event.productCount IS NOT NULL
) e ON e.productId = mp.id
GROUP BY mp.id
HAVING net_count > 0
ORDER BY a.symbol, mp.form, mp.fineGrams`, [ownOnly ? 1 : 0]);
}