89 lines
5.0 KiB
JavaScript
89 lines
5.0 KiB
JavaScript
// 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]);
|
|
}
|