Files

88 lines
4.9 KiB
JavaScript
Raw Permalink Normal View History

2026-10-30 12:28:48 +01:00
// Generated, do not edit. sha256:36119ec87b42a3428d74ef9e16a174eb6f028b536ffdcea60da0bd3e970bac77
2026-10-29 11:57:29 +01:00
import * as db from '../db.js';
import { ACCOUNT_SOURCE_KINDS } from '../../shared/config.js';
import { fail } from '../../shared/app-error.js';
2026-10-30 12:28:48 +01:00
import { hasFeature } from '../license.mjs';
2026-10-29 11:57:29 +01:00
import { eventsNetByAccount } from './events.js';
import { trackUpdate, trackUpdateWhere, trackDelete, trackDeleteWhere, withOrigin } from './change-log.js';
function mergeCompatibility(survivor, victim) {
const S = ACCOUNT_SOURCE_KINDS[survivor.kind] || [];
const V = ACCOUNT_SOURCE_KINDS[victim.kind] || [];
if ((S.length === 0) !== (V.length === 0)) {
return { ok: false, reason: 'sourceMismatch' };
}
if (V.some(k => !S.includes(k))) {
return { ok: false, reason: 'kindCannotCarry' };
}
return { ok: true, reason: null };
}
export function mergeCheck(survivorId, victimId) {
if (!survivorId || !victimId) return { ok: false, reason: 'pickTwo', counts: null };
if (Number(survivorId) === Number(victimId)) return { ok: false, reason: 'sameAccount', counts: null };
const survivor = db.one(`SELECT * FROM Account WHERE id=?`, [survivorId]);
const victim = db.one(`SELECT * FROM Account WHERE id=?`, [victimId]);
if (!survivor || !victim) return { ok: false, reason: 'notFound', counts: null };
const compat = mergeCompatibility(survivor, victim);
const counts = {
sources: db.one(`SELECT COUNT(*) AS n FROM WalletSource WHERE accountId=?`, [victimId]).n,
events: db.one(`SELECT COUNT(*) AS n FROM Event WHERE fromAccountId=?1 OR toAccountId=?1`, [victimId]).n,
schedules: db.one(`SELECT COUNT(*) AS n FROM InterestSchedule WHERE accountId=?`, [victimId]).n,
valuations: db.one(`SELECT COUNT(*) AS n FROM Valuation WHERE accountId =?`, [victimId]).n,
};
return { ok: compat.ok, reason: compat.reason, counts, survivorName: survivor.name, victimName: victim.name };
}
export async function mergeAccounts(survivorId, victimId) {
2026-10-30 12:28:48 +01:00
if (!hasFeature('accountMerge')) fail('accounts.mergePaid', null, 'Merging accounts belongs to the full edition.');
2026-10-29 11:57:29 +01:00
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}).`);
return withOrigin('merge', victimId, () => voerSamenvoegingUit(survivorId, victimId, chk));
}
async function voerSamenvoegingUit(survivorId, victimId, chk) {
const preDiff = new Map();
for (const v of db.all(`SELECT id, accountId, assetId, rateAt, balance FROM Valuation WHERE accountId = ?1 OR accountId = ?2`, [survivorId, victimId])) {
preDiff.set(v.id, v.balance - eventsNetByAccount(v.accountId, v.assetId, v.rateAt));
}
trackUpdateWhere('Event', `fromAccountId = ?`, [victimId], () =>
db.run(`UPDATE Event SET fromAccountId=?1 WHERE fromAccountId=?2`, [survivorId, victimId]));
trackUpdateWhere('Event', `toAccountId = ?`, [victimId], () =>
db.run(`UPDATE Event SET toAccountId=?1 WHERE toAccountId=?2`, [survivorId, victimId]));
trackUpdateWhere('WalletSource', `accountId = ?`, [victimId], () =>
db.run(`UPDATE WalletSource SET accountId=?1 WHERE accountId=?2`, [survivorId, victimId]));
db.run(`UPDATE WalletAddress SET accountId=?1 WHERE accountId=?2`, [survivorId, victimId]);
db.run(`UPDATE WalletTransaction SET accountId=?1 WHERE accountId=?2`, [survivorId, victimId]);
db.run(`UPDATE ScanLog SET accountId=?1 WHERE accountId=?2`, [survivorId, victimId]);
db.run(`UPDATE ReconcileProposal SET accountId=?1 WHERE accountId=?2`, [survivorId, victimId]);
db.run(`UPDATE ImportBatch SET accountId=?1 WHERE accountId=?2`, [survivorId, victimId]);
trackUpdateWhere('InterestSchedule', `accountId = ?`, [victimId], () =>
db.run(`UPDATE InterestSchedule SET accountId=?1 WHERE accountId=?2`, [survivorId, victimId]));
trackDeleteWhere('Valuation', `accountId = ?2
AND EXISTS (SELECT 1 FROM Valuation v2 WHERE v2.accountId = ?1
AND v2.assetId=Valuation.assetId AND v2.valuedOn=Valuation.valuedOn)`,
[survivorId, victimId], () =>
db.run(`DELETE FROM Valuation WHERE accountId =?2
AND EXISTS (SELECT 1 FROM Valuation v2 WHERE v2.accountId =?1
AND v2.assetId=Valuation.assetId AND v2.valuedOn=Valuation.valuedOn)`,
[survivorId, victimId]));
trackUpdateWhere('Valuation', `accountId = ?`, [victimId], () =>
db.run(`UPDATE Valuation SET accountId =?1 WHERE accountId = ?2`, [survivorId, victimId]));
for (const [id, diff] of preDiff) {
const v = db.one(`SELECT id, assetId, rateAt FROM Valuation WHERE id=?`, [id]);
if (!v) continue;
const newNet = eventsNetByAccount(survivorId, v.assetId, v.rateAt);
trackUpdate('Valuation', id, () =>
db.run(`UPDATE Valuation SET balance=? WHERE id=?`, [newNet + diff, id]));
}
trackDelete('Account', victimId, () => db.run(`DELETE FROM Account WHERE id=?`, [victimId]));
await db.persist();
return chk.counts;
}