Skip to content

feat(meals): import the Tandoor meal plan / importer le planning Tandoor (+ 2 fixes) - #107

Merged
NexaFlowFrance merged 3 commits into
NexaFlowFrance:mainfrom
foulek57:feat/tandoor-meal-plan-import
Sep 27, 2026
Merged

NexaFlowFrance merged 3 commits into
NexaFlowFrance:mainfrom
foulek57:feat/tandoor-meal-plan-import

Conversation

@foulek57

Copy link
Copy Markdown
Contributor

🇬🇧 English

Why
The Tandoor integration only imports recipes. A family that plans its week in Tandoor sees an empty meal planning in OpenFamily and has to re-type every meal.

What's new: an "Import from Tandoor" button on the meal planning page

The button is visible to parents only, once a Tandoor integration is connected. It opens a dialog that:

  1. reads the Tandoor meal plan of the displayed week;
  2. matches each Tandoor meal type to an OpenFamily meal (breakfast / lunch / dinner / snack), or skips it. Tandoor meal types are free text ("Lunch", "Midi", "Almuerzo"…), so a best guess is pre-selected and can be changed;
  3. previews the result day by day. Meals already planned are kept unless "Replace meals that are already planned" is ticked.

How it works on the server:

  • GET /api/integrations/tandoor/meal-plan returns the preview, and POST /api/integrations/tandoor/meal-plan/import does the import. On import, the server fetches the entries again itself; the client only sends its matching and the overwrite choice.
  • A Tandoor recipe already imported by the recipe sync is linked by name. Otherwise the meal is saved as a custom meal. Several Tandoor entries on the same day and meal are merged into one.
  • Both routes are parent-only, re-check the integration URL (assertSafeIntegrationUrl) and accept at most 31 days.
  • Translations added for en, fr, es, pt, ru and zh. The README integration table is updated.

Fixes

  • Tandoor authentication: Tandoor 2.x rejects the legacy Token scheme on /api/recipe/ (403), while /api/user-preferences/ still accepts it. So the connection test passed but every sync failed. The code now sends Bearer, the documented scheme.
  • Home Assistant todo lists: todo/item/list answers { items: [...] }, not an object keyed by entity_id. As a result, lists such as local_todo always imported 0 items.

Tested

  • npm run build, check:i18n and check:public pass.
  • On a self-hosted instance with Tandoor 2.x, where the meal types are "Midi" and "Soir": 14 entries read for one week, all linked to existing recipes, and the new routes refuse requests without a session (401).
  • Home Assistant with a local_todo list: 17 items imported, against 0 before the fix.

The three commits are independent, and I can split them into separate PRs if you prefer.


🇫🇷 Français

Pourquoi
L'intégration Tandoor n'importe que les recettes. Une famille qui planifie sa semaine dans Tandoor voit un planning des repas vide dans OpenFamily et doit tout ressaisir.

Nouveauté : un bouton « Importer depuis Tandoor » sur le planning des repas

Le bouton n'apparaît que pour les parents, et seulement quand l'intégration Tandoor est connectée. Il ouvre une fenêtre qui :

  1. lit le planning Tandoor de la semaine affichée ;
  2. associe chaque type de repas Tandoor à un repas OpenFamily (petit-déjeuner / déjeuner / dîner / snack), ou l'ignore. Dans Tandoor ces types sont du texte libre (« Midi », « Lunch »…), donc une correspondance est proposée d'office et reste modifiable ;
  3. affiche un aperçu jour par jour. Les repas déjà planifiés sont conservés, sauf si la case « Remplacer les repas déjà planifiés » est cochée.

Fonctionnement côté serveur :

  • GET /api/integrations/tandoor/meal-plan renvoie l'aperçu, POST /api/integrations/tandoor/meal-plan/import fait l'import. À l'import, le serveur relit lui-même le planning ; le client n'envoie que ses choix de correspondance et l'option de remplacement.
  • Une recette Tandoor déjà importée par la synchro des recettes est liée par son nom. Sinon, le repas est enregistré comme repas personnalisé. Plusieurs repas Tandoor sur le même jour et le même repas sont regroupés en un seul.
  • Les deux routes sont réservées aux parents, revérifient l'URL de l'intégration et acceptent 31 jours au maximum.
  • Traductions ajoutées en 6 langues, et tableau des intégrations du README mis à jour.

Corrections

  • Authentification Tandoor : Tandoor 2.x refuse l'ancien schéma Token sur la liste des recettes (erreur 403), alors que le test de connexion, lui, passait. Toutes les synchros échouaient donc. Le code utilise maintenant Bearer, le schéma documenté.
  • Listes Home Assistant : la réponse de todo/item/list contient directement { items: [...] }, et non un objet rangé par entité. Les listes comme local_todo importaient donc toujours 0 article.

Tests

  • La compilation complète et les contrôles i18n et public passent.
  • Sur une instance auto-hébergée avec Tandoor 2.x, où les types de repas sont « Midi » et « Soir » : 14 repas lus sur une semaine, tous liés à des recettes existantes, et les nouvelles routes refusent les appels sans session (401).
  • Home Assistant avec une liste local_todo : 17 articles importés, contre 0 avant la correction.

Les trois commits sont indépendants, et je peux les séparer en trois PR si vous préférez.

EN: Tandoor 2.x rejects the legacy "Token <key>" scheme on most endpoints
(/api/recipe/ answers 403) while /api/user-preferences/ still accepts it,
so the connection test passed but every sync failed. "Bearer <key>" is
the documented scheme for Tandoor API tokens and works everywhere.

FR : Tandoor 2.x refuse l'ancien schéma « Token <clé> » sur la plupart
des routes (/api/recipe/ répond 403) alors que /api/user-preferences/
l'accepte encore : le test de connexion passait mais chaque synchro
échouait. « Bearer <clé> » est le schéma documenté et fonctionne partout.
EN: The todo/item/list WebSocket command answers { items: [...] } for the
requested entity; it is not keyed by entity_id (that shape belongs to the
todo.get_items service). Modern todo lists (local_todo…) therefore always
imported 0 items.

FR : La commande WebSocket todo/item/list renvoie { items: [...] } pour
l'entité demandée, et non un objet rangé par entity_id (c'est le format
du service todo.get_items). Les listes modernes (local_todo…) importaient
donc toujours 0 article.
EN: The Tandoor integration only imported recipes, so a family planning
its meals in Tandoor saw an empty week in OpenFamily.

Adds an "Import from Tandoor" button on the meal planning page (parents
only, once a Tandoor integration is connected). The dialog reads the
Tandoor meal plan of the displayed week, lets the user match each Tandoor
meal type (free text: "Lunch", "Midi"…) to an OpenFamily meal or skip it
(best guess pre-selected), and previews the result slot by slot. Meals
already planned are kept unless "Replace meals that are already planned"
is ticked.

Server: fetchTandoorMealPlan() (paginated or plain-array answers,
multi-day entries expanded), GET /api/integrations/tandoor/meal-plan
(preview) and POST /api/integrations/tandoor/meal-plan/import (entries
fetched again server-side, the client only sends its choices). Recipes
already imported by the sync are linked by name, otherwise a custom meal
is stored; entries on the same slot are merged. Parent-only, URL
re-validated, 31-day max range. Translations for en, fr, es, pt, ru, zh.

FR : L'intégration Tandoor n'importait que les recettes : une famille qui
planifie ses repas dans Tandoor voyait une semaine vide dans OpenFamily.

Ajoute un bouton « Importer depuis Tandoor » sur le planning des repas
(parents uniquement, intégration Tandoor connectée). La fenêtre lit le
planning Tandoor de la semaine affichée, fait associer chaque type de
repas Tandoor (texte libre : « Midi », « Lunch »…) à un repas OpenFamily
ou l'ignorer (correspondance proposée d'office), et montre un aperçu
créneau par créneau. Les repas déjà planifiés sont conservés sauf si
« Remplacer les repas déjà planifiés » est coché.

Serveur : fetchTandoorMealPlan() (réponses paginées ou en tableau,
repas sur plusieurs jours dépliés), GET /api/integrations/tandoor/meal-plan
(aperçu) et POST /api/integrations/tandoor/meal-plan/import (planning
relu côté serveur, le client n'envoie que ses choix). Les recettes déjà
importées par la synchro sont liées par leur nom, sinon repas
personnalisé ; plusieurs repas sur un même créneau sont regroupés.
Réservé aux parents, URL revérifiée, 31 jours max. Traductions en, fr,
es, pt, ru, zh.
@NexaFlowFrance
NexaFlowFrance merged commit 049ec28 into NexaFlowFrance:main Sep 27, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants