Compare commits
2 Commits
7051681531
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 174fc6872e | |||
| 9c48217c36 |
@@ -270,13 +270,17 @@ app.patch('/api/activities/:id', (req, res) => {
|
||||
const emoji = req.body.emoji ?? existing.emoji;
|
||||
const image_url = req.body.image_url !== undefined ? req.body.image_url : existing.image_url;
|
||||
const cost_shells = req.body.cost_shells !== undefined ? Number(req.body.cost_shells) : existing.cost_shells;
|
||||
const status = req.body.status ?? existing.status;
|
||||
const current_shells = req.body.current_shells !== undefined ? Math.max(0, Number(req.body.current_shells)) : existing.current_shells;
|
||||
let status = req.body.status ?? existing.status;
|
||||
if (current_shells >= cost_shells && status === 'ACTIVE') {
|
||||
status = 'UNLOCKED';
|
||||
}
|
||||
|
||||
db.prepare(`
|
||||
UPDATE activities
|
||||
SET title = ?, description = ?, emoji = ?, image_url = ?, cost_shells = ?, status = ?
|
||||
SET title = ?, description = ?, emoji = ?, image_url = ?, cost_shells = ?, current_shells = ?, status = ?
|
||||
WHERE id = ?
|
||||
`).run(title, description, emoji, image_url, cost_shells, status, id);
|
||||
`).run(title, description, emoji, image_url, cost_shells, current_shells, status, id);
|
||||
|
||||
res.json({ message: 'Activité mise à jour.' });
|
||||
});
|
||||
@@ -290,6 +294,71 @@ app.delete('/api/activities/:id', (req, res) => {
|
||||
res.json({ message: 'Activité supprimée.' });
|
||||
});
|
||||
|
||||
// 6b. POST /api/activities/:id/direct-contribute (Attribution directe de coquillages par les parents pour un enfant)
|
||||
app.post('/api/activities/:id/direct-contribute', (req, res) => {
|
||||
const activityId = Number(req.params.id);
|
||||
const activity = db.prepare('SELECT * FROM activities WHERE id = ?').get(activityId);
|
||||
if (!activity) return res.status(404).json({ error: 'Activité introuvable.' });
|
||||
|
||||
const { child_id, shells, source_type = 'PHYSICAL' } = req.body;
|
||||
const child = db.prepare('SELECT * FROM children WHERE id = ?').get(Number(child_id));
|
||||
if (!child) return res.status(404).json({ error: 'Enfant introuvable.' });
|
||||
|
||||
const count = Number(shells);
|
||||
if (!count || count <= 0) {
|
||||
return res.status(400).json({ error: 'Veuillez saisir un nombre de coquillages supérieur à 0.' });
|
||||
}
|
||||
|
||||
const isVirtual = source_type === 'VIRTUAL';
|
||||
if (isVirtual && child.available_shells < count) {
|
||||
return res.status(400).json({
|
||||
error: `Solde virtuel insuffisant : ${child.name} n'a que ${child.available_shells} coquillages disponibles.`
|
||||
});
|
||||
}
|
||||
|
||||
const now = new Date().toISOString();
|
||||
|
||||
// Déduire si c'est virtuel
|
||||
if (isVirtual) {
|
||||
db.prepare('UPDATE children SET available_shells = available_shells - ? WHERE id = ?').run(count, child.id);
|
||||
}
|
||||
|
||||
// Créditer l'activité
|
||||
const newTotal = activity.current_shells + count;
|
||||
const newStatus = newTotal >= activity.cost_shells ? 'UNLOCKED' : activity.status;
|
||||
db.prepare('UPDATE activities SET current_shells = ?, status = ? WHERE id = ?').run(newTotal, newStatus, activity.id);
|
||||
|
||||
// Enregistrer la contribution
|
||||
db.prepare(`
|
||||
INSERT INTO activity_contributions (activity_id, child_id, shells, created_at)
|
||||
VALUES (?, ?, ?, ?)
|
||||
`).run(activity.id, child.id, count, now);
|
||||
|
||||
// Résoudre d'éventuelles requêtes Alexa PENDING pour cet enfant sur cette activité afin de déverrouiller son bouton
|
||||
db.prepare(`
|
||||
UPDATE pending_requests
|
||||
SET status = 'APPROVED', resolved_at = ?
|
||||
WHERE activity_id = ? AND child_id = ? AND status = 'PENDING'
|
||||
`).run(now, activity.id, child.id);
|
||||
|
||||
// Enregistrer la transaction
|
||||
const sourceLabel = isVirtual ? 'déduits du compte virtuel' : 'coquillages physiques remis';
|
||||
db.prepare(`
|
||||
INSERT INTO transactions (child_id, type, shells_delta, euros_delta, description, created_at)
|
||||
VALUES (?, 'CONTRIBUTION', ?, 0, ?, ?)
|
||||
`).run(
|
||||
child.id,
|
||||
isVirtual ? -count : 0,
|
||||
`${child.name} a mis ${count} 🐚 (${sourceLabel}) pour "${activity.title}" (${newTotal}/${activity.cost_shells})`,
|
||||
now
|
||||
);
|
||||
|
||||
res.json({
|
||||
message: `${count} 🐚 attribués avec succès à "${activity.title}" pour ${child.name} !`,
|
||||
activity: getEnrichedActivities().find((a) => a.id === activityId)
|
||||
});
|
||||
});
|
||||
|
||||
// 7. POST /api/requests
|
||||
app.post('/api/requests', (req, res) => {
|
||||
const {
|
||||
@@ -701,6 +770,125 @@ app.post('/api/children/:id/adjust', (req, res) => {
|
||||
res.json({ message: 'Compte mis à jour.', child: getEnrichedChildren().find((c) => c.id === id) });
|
||||
});
|
||||
|
||||
// 13b. PUT /api/children/:id (Édition directe des soldes de l'enfant par les parents)
|
||||
app.put('/api/children/:id', (req, res) => {
|
||||
const id = Number(req.params.id);
|
||||
const child = db.prepare('SELECT * FROM children WHERE id = ?').get(id);
|
||||
if (!child) return res.status(404).json({ error: 'Enfant introuvable.' });
|
||||
|
||||
const { available_shells, locked_shells, euros_balance } = req.body;
|
||||
const newAvailable = available_shells !== undefined ? Math.max(0, Number(available_shells)) : child.available_shells;
|
||||
const newLocked = locked_shells !== undefined ? Math.max(0, Number(locked_shells)) : child.locked_shells;
|
||||
const newEuros = euros_balance !== undefined ? Math.max(0, Number(euros_balance)) : child.euros_balance;
|
||||
const now = new Date().toISOString();
|
||||
|
||||
db.prepare(`
|
||||
UPDATE children
|
||||
SET available_shells = ?, locked_shells = ?, euros_balance = ?
|
||||
WHERE id = ?
|
||||
`).run(newAvailable, newLocked, newEuros, id);
|
||||
|
||||
db.prepare(`
|
||||
INSERT INTO transactions (child_id, type, shells_delta, euros_delta, description, created_at)
|
||||
VALUES (?, 'ADMIN_EDIT', ?, ?, ?, ?)
|
||||
`).run(
|
||||
id,
|
||||
newAvailable - child.available_shells,
|
||||
newEuros - child.euros_balance,
|
||||
`Mise à jour des soldes de ${child.name} : ${newAvailable} 🐚 dispo, ${newLocked} 🔒 bloqués, ${newEuros.toFixed(2)} €`,
|
||||
now
|
||||
);
|
||||
|
||||
res.json({
|
||||
message: `Soldes de ${child.name} enregistrés avec succès !`,
|
||||
child: getEnrichedChildren().find((c) => c.id === id)
|
||||
});
|
||||
});
|
||||
|
||||
// 13c. POST /api/children/:id/transfer (Transfert réel <-> virtuel, blocage/déblocage rapide)
|
||||
app.post('/api/children/:id/transfer', (req, res) => {
|
||||
const id = Number(req.params.id);
|
||||
const child = db.prepare('SELECT * FROM children WHERE id = ?').get(id);
|
||||
if (!child) return res.status(404).json({ error: 'Enfant introuvable.' });
|
||||
|
||||
const { type, shells, reason = '' } = req.body;
|
||||
const count = Number(shells);
|
||||
if (!count || count <= 0) {
|
||||
return res.status(400).json({ error: 'Le nombre de coquillages doit être supérieur à 0.' });
|
||||
}
|
||||
|
||||
const now = new Date().toISOString();
|
||||
|
||||
switch (type) {
|
||||
case 'REAL_TO_AVAILABLE': {
|
||||
// Coquillages physiques reçus -> crédités sur le compte disponible
|
||||
db.prepare('UPDATE children SET available_shells = available_shells + ? WHERE id = ?').run(count, id);
|
||||
db.prepare(`
|
||||
INSERT INTO transactions (child_id, type, shells_delta, euros_delta, description, created_at)
|
||||
VALUES (?, 'TRANSFER_REAL_AVAILABLE', ?, 0, ?, ?)
|
||||
`).run(id, count, `Dépôt réel : +${count} 🐚 physiques ajoutés au compte disponible de ${child.name} (${reason || 'Remis en main propre'})`, now);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'REAL_TO_LOCKED': {
|
||||
// Coquillages physiques reçus -> directement bloqués sur le livret (+intérêts)
|
||||
db.prepare('UPDATE children SET locked_shells = locked_shells + ? WHERE id = ?').run(count, id);
|
||||
db.prepare(`
|
||||
INSERT INTO transactions (child_id, type, shells_delta, euros_delta, description, created_at)
|
||||
VALUES (?, 'TRANSFER_REAL_LOCKED', ?, 0, ?, ?)
|
||||
`).run(id, count, `Dépôt réel bloqué : +${count} 🔒 bloqués pour ${child.name} (${reason || 'Livret épargne'})`, now);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'AVAILABLE_TO_REAL': {
|
||||
// L'enfant reprend/utilise des coquillages réels -> débités du compte virtuel disponible
|
||||
if (child.available_shells < count) {
|
||||
return res.status(400).json({ error: `Solde insuffisant : ${child.name} n'a que ${child.available_shells} coquillages disponibles.` });
|
||||
}
|
||||
db.prepare('UPDATE children SET available_shells = available_shells - ? WHERE id = ?').run(count, id);
|
||||
db.prepare(`
|
||||
INSERT INTO transactions (child_id, type, shells_delta, euros_delta, description, created_at)
|
||||
VALUES (?, 'TRANSFER_AVAILABLE_REAL', ?, 0, ?, ?)
|
||||
`).run(id, -count, `Retrait réel : -${count} 🐚 utilisés dans la vraie vie par ${child.name} (${reason || 'Utilisation réelle'})`, now);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'LOCK': {
|
||||
// Transfert interne : disponible -> bloqué
|
||||
if (child.available_shells < count) {
|
||||
return res.status(400).json({ error: `Solde disponible insuffisant (${child.available_shells} 🐚 dispo).` });
|
||||
}
|
||||
db.prepare('UPDATE children SET available_shells = available_shells - ?, locked_shells = locked_shells + ? WHERE id = ?').run(count, count, id);
|
||||
db.prepare(`
|
||||
INSERT INTO transactions (child_id, type, shells_delta, euros_delta, description, created_at)
|
||||
VALUES (?, 'LOCK_VIRTUAL', 0, 0, ?, ?)
|
||||
`).run(id, `${child.name} a bloqué ${count} 🐚 vers son livret épargne`, now);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'UNLOCK': {
|
||||
// Transfert interne : bloqué -> disponible
|
||||
if (child.locked_shells < count) {
|
||||
return res.status(400).json({ error: `Solde bloqué insuffisant (${child.locked_shells} 🔒 bloqués).` });
|
||||
}
|
||||
db.prepare('UPDATE children SET available_shells = available_shells + ?, locked_shells = locked_shells - ? WHERE id = ?').run(count, count, id);
|
||||
db.prepare(`
|
||||
INSERT INTO transactions (child_id, type, shells_delta, euros_delta, description, created_at)
|
||||
VALUES (?, 'UNLOCK_VIRTUAL', 0, 0, ?, ?)
|
||||
`).run(id, `${count} 🐚 bloqués reversés sur le compte disponible de ${child.name}`, now);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
return res.status(400).json({ error: `Type de transfert inconnu: ${type}` });
|
||||
}
|
||||
|
||||
res.json({
|
||||
message: `Opération enregistrée pour ${child.name} !`,
|
||||
child: getEnrichedChildren().find((c) => c.id === id)
|
||||
});
|
||||
});
|
||||
|
||||
// 14. POST /api/alexa/voice-intent (Avec support Personalisation Voice ID, cartes illustrées & APL Echo Show)
|
||||
app.post('/api/alexa/voice-intent', (req, res) => {
|
||||
const { intent, slots = {}, personId = null, personGivenName = null } = req.body;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user