feat: §5.2 finale — aderenza piano + export PDF
§5.2.5 Aderenza al piano alimentare - MealSlot.consumed Boolean + consumedAt DateTime? (migration) - POST /me/meal-plans/slots/:id/consumed (toggle, idempotente con body opzionale) - @ketopath/shared/planner/adherence: computeAdherence() su slot dei giorni passati, free-meal contati come "rispettati" - /plan: bottone ✓ in ogni SlotCard (oliva quando attivo) + 5° card nel summary settimanale "Aderenza: X% (n/m)" §5.2.6 Export PDF per medico/nutrizionista - pdfkit (Node, no headless Chrome) + @types/pdfkit - GET /me/tracking/export.pdf: profilo, storico peso (30 entries), piano corrente con aderenza, footer con disclaimer medico - Proxy Next.js /api/tracking-export per same-origin + cookie sessione - /tracking: link "Esporta PDF per medico/nutrizionista" sotto subtitle Schema migration: add_meal_slot_consumed. 81/81 unit test verdi. Lint, typecheck verdi. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
9327e75395
commit
83bab7b6d5
14 files changed
+347
-3
No files matched your search
@@ -283,6 +283,32 @@ export const planRoutes: FastifyPluginAsync = async (fastify) => {
|
||||
},
|
||||
);
|
||||
|
||||
// PRD §5.2 — aderenza: l'utente segna lo slot come consumato.
|
||||
fastify.post(
|
||||
'/me/meal-plans/slots/:slotId/consumed',
|
||||
{ preHandler: requireAuth() },
|
||||
async (request, reply) => {
|
||||
const slotId = (request.params as { slotId: string }).slotId;
|
||||
const userId = request.user!.id;
|
||||
const body = (request.body ?? {}) as { consumed?: unknown };
|
||||
const desired = typeof body.consumed === 'boolean' ? body.consumed : null;
|
||||
|
||||
const slot = await fastify.prisma.mealSlot.findFirst({
|
||||
where: { id: slotId, plan: { userId } },
|
||||
select: { id: true, consumed: true },
|
||||
});
|
||||
if (!slot) return reply.code(404).send({ error: 'slot_not_found' });
|
||||
|
||||
const next = desired ?? !slot.consumed;
|
||||
const updated = await fastify.prisma.mealSlot.update({
|
||||
where: { id: slotId },
|
||||
data: { consumed: next, consumedAt: next ? new Date() : null },
|
||||
select: { id: true, consumed: true, consumedAt: true },
|
||||
});
|
||||
return { slot: updated };
|
||||
},
|
||||
);
|
||||
|
||||
// PRD §5.1 — toggle "pasto libero". Disponibile solo in Fase 3.
|
||||
fastify.post(
|
||||
'/me/meal-plans/slots/:slotId/free-meal',
|
||||
@@ -478,7 +504,7 @@ export const planRoutes: FastifyPluginAsync = async (fastify) => {
|
||||
return reply.code(409).send({ error: 'no_recipes_available' });
|
||||
}
|
||||
|
||||
const newSelected = top[0];
|
||||
const newSelected = top[0]!;
|
||||
const altIds = top.slice(1).map((r) => r.id);
|
||||
|
||||
// Re-set alternatives via Prisma `set` per pulire la M2M.
|
||||
|
||||
Reference in new issue
Block a user