MAJ
This commit is contained in:
@@ -2012,4 +2012,54 @@ console.log('[DB] Migrations 2FA OK');
|
||||
}
|
||||
}
|
||||
|
||||
// ── Backfill : garantir un profil principal + un compte courant par utilisateur ──
|
||||
// Certains parcours de création de compte (admin, invitation) ne créaient pas
|
||||
// systématiquement le profil investisseur principal et/ou son compte courant,
|
||||
// contrairement à /auth/register. Ce backfill est idempotent (NOT EXISTS partout)
|
||||
// et s'exécute à chaque démarrage pour rattraper les comptes existants.
|
||||
{
|
||||
// 1) Utilisateurs sans aucun investisseur (ex : comptes créés via invitation)
|
||||
const usersWithoutInvestisseur = db.prepare(`
|
||||
SELECT id, email, display_name FROM users u
|
||||
WHERE NOT EXISTS (SELECT 1 FROM investisseurs i WHERE i.user_id = u.id)
|
||||
`).all();
|
||||
for (const u of usersWithoutInvestisseur) {
|
||||
const fullName = u.display_name || u.email.split('@')[0];
|
||||
const prenom = fullName.includes(' ') ? fullName.split(' ')[0] : null;
|
||||
db.prepare(
|
||||
`INSERT INTO investisseurs (user_id, nom, prenom, type, type_fiscal, is_principal) VALUES (?, ?, ?, 'famille', 'PP', 1)`
|
||||
).run(u.id, fullName, prenom);
|
||||
console.log(`[DB] backfill: profil investisseur principal créé pour user #${u.id} (${u.email})`);
|
||||
}
|
||||
|
||||
// 2) Utilisateurs ayant des investisseurs mais aucun marqué principal (ex : comptes créés par un admin)
|
||||
const usersWithoutPrincipal = db.prepare(`
|
||||
SELECT DISTINCT user_id FROM investisseurs i
|
||||
WHERE NOT EXISTS (SELECT 1 FROM investisseurs p WHERE p.user_id = i.user_id AND p.is_principal = 1)
|
||||
`).all();
|
||||
for (const { user_id } of usersWithoutPrincipal) {
|
||||
const candidate = db.prepare(`
|
||||
SELECT id FROM investisseurs WHERE user_id = ? ORDER BY (type = 'famille') DESC, id ASC LIMIT 1
|
||||
`).get(user_id);
|
||||
if (candidate) {
|
||||
db.prepare('UPDATE investisseurs SET is_principal = 1 WHERE id = ?').run(candidate.id);
|
||||
console.log(`[DB] backfill: investisseur #${candidate.id} marqué principal pour user #${user_id}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 3) Investisseurs principaux sans compte courant
|
||||
const principalsWithoutCompte = db.prepare(`
|
||||
SELECT i.id AS investisseur_id, i.user_id, i.nom
|
||||
FROM investisseurs i
|
||||
WHERE i.is_principal = 1
|
||||
AND NOT EXISTS (SELECT 1 FROM comptes c WHERE c.investisseur_id = i.id)
|
||||
`).all();
|
||||
for (const inv of principalsWithoutCompte) {
|
||||
db.prepare(
|
||||
'INSERT INTO comptes (user_id, nom, type, investisseur_id) VALUES (?,?,?,?)'
|
||||
).run(inv.user_id, `Compte courant — ${inv.nom}`, 'compte_courant', inv.investisseur_id);
|
||||
console.log(`[DB] backfill: compte courant créé pour l'investisseur principal #${inv.investisseur_id} (user #${inv.user_id})`);
|
||||
}
|
||||
}
|
||||
|
||||
export default db;
|
||||
|
||||
@@ -107,10 +107,17 @@ router.post('/users', (req, res, next) => {
|
||||
const userId = result.lastInsertRowid;
|
||||
const fullName = body.displayName || body.email.split('@')[0];
|
||||
const prenom = fullName.includes(' ') ? fullName.split(' ')[0] : null;
|
||||
db.prepare(
|
||||
`INSERT INTO investisseurs (user_id, nom, prenom, type, type_fiscal) VALUES (?, ?, ?, 'famille', 'PP')`
|
||||
|
||||
// Auto-créer le profil principal (= l'utilisateur lui-même), comme dans /auth/register
|
||||
const invResult = db.prepare(
|
||||
`INSERT INTO investisseurs (user_id, nom, prenom, type, type_fiscal, is_principal) VALUES (?, ?, ?, 'famille', 'PP', 1)`
|
||||
).run(userId, fullName, prenom);
|
||||
|
||||
// Auto-créer un compte courant pour le profil principal, comme dans /auth/register
|
||||
db.prepare(
|
||||
'INSERT INTO comptes (user_id, nom, type, investisseur_id) VALUES (?,?,?,?)'
|
||||
).run(userId, `Compte courant — ${fullName}`, 'compte_courant', invResult.lastInsertRowid);
|
||||
|
||||
audit(req, { action: 'user_created', category: 'account', actorId: req.user.id, targetUserId: userId, details: { email: body.email, role: body.role, created_by_admin: true } });
|
||||
res.status(201).json({ id: userId, email: body.email, display_name: body.displayName || null, role: body.role });
|
||||
} catch (e) { next(e); }
|
||||
|
||||
@@ -167,7 +167,8 @@ router.post('/:token/register', async (req, res, next) => {
|
||||
const pendingUser = db.prepare('SELECT id FROM users WHERE LOWER(email) = LOWER(?) AND email_verified = 0').get(inv.email);
|
||||
if (!pendingUser) throw new HttpError(409, 'Ce compte a déjà été activé ou supprimé.');
|
||||
|
||||
const hash = await bcrypt.hash(password, 12);
|
||||
const hash = await bcrypt.hash(password, 12);
|
||||
const fullName = displayName || nameFromEmail(inv.email);
|
||||
|
||||
// Finaliser le compte : mot de passe, nom, vérification
|
||||
db.prepare(`
|
||||
@@ -177,7 +178,18 @@ router.post('/:token/register', async (req, res, next) => {
|
||||
email_verified = 1,
|
||||
updated_at = datetime('now')
|
||||
WHERE id = ?
|
||||
`).run(hash, displayName || nameFromEmail(inv.email), pendingUser.id);
|
||||
`).run(hash, fullName, pendingUser.id);
|
||||
|
||||
// Auto-créer le profil principal (= l'utilisateur lui-même) + son compte courant,
|
||||
// comme dans /auth/register — sans quoi l'utilisateur invité se retrouve sans
|
||||
// aucun investisseur ni compte courant à sa première connexion.
|
||||
const prenom = fullName.includes(' ') ? fullName.split(' ')[0] : null;
|
||||
const invResult = db.prepare(
|
||||
`INSERT INTO investisseurs (user_id, nom, prenom, type, type_fiscal, is_principal) VALUES (?, ?, ?, 'famille', 'PP', 1)`
|
||||
).run(pendingUser.id, fullName, prenom);
|
||||
db.prepare(
|
||||
'INSERT INTO comptes (user_id, nom, type, investisseur_id) VALUES (?,?,?,?)'
|
||||
).run(pendingUser.id, `Compte courant — ${fullName}`, 'compte_courant', invResult.lastInsertRowid);
|
||||
|
||||
// Marquer l'invitation comme utilisée
|
||||
db.prepare("UPDATE invitations SET used_at = datetime('now') WHERE id = ?").run(inv.id);
|
||||
|
||||
Reference in New Issue
Block a user