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;
|
|
|
|
|
}
|