Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Option pour remplacer un ingrédient demandé par un ingrédient existant #1334

Open
wants to merge 23 commits into
base: staging
Choose a base branch
from

Conversation

hfroot
Copy link
Collaborator

@hfroot hfroot commented Dec 2, 2024

Dans le scope : fonctionnalité pour trouver un ingrédient de même type et remplacer une demande avec.

Hors du scope : ajout de synonyme en même temps; remplacement d'une demande par un ingrédient d'autre type (par ex microorganisme avec plante), à faire à la main pour l'instant

Control de la demande

Il y a une explication quand le remplacement est bloqué au cause du type

Screenshot 2024-12-19 at 21-20-16 Novella - Compl'Alim

Quand la demande a été remplacée :

Screenshot 2024-12-19 at 21-20-06 Novella - Compl'Alim

MAJ du badge pour les instructrices

Screenshot 2024-12-19 at 21-36-32 Instruction - Compl'Alim

NB: Les déclarants vont voir le nom du remplacement si ils vont regarder la déclaration.

Calculation de substances

Le tableau de substances est déjà automatiquement recalculé. Cette PR ajoute un avertissement au dessus si il y a des remplacements qui ont des substances.

NB: pour l'instant, cet avertissement est toujours affiché pour l'instructrice. Question: est-ce que c'est acceptable en premier temps ?

Screenshot 2024-12-19 at 22-22-10 Instruction - Compl'Alim

@hfroot hfroot force-pushed the replace-ingredient-request branch from 276a7b6 to 803d024 Compare December 3, 2024 16:43
Base automatically changed from declared-element-note to staging December 5, 2024 10:30
@@ -103,7 +103,23 @@ def update(self, instance, validated_data):
)


class DeclaredIngredientCommonSerializer(PrivateFieldsSerializer):
class DeclaredElementNestedField:
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cette refacto avait du sens quand j'ai commencé la PR. Maintenant, elle paraît un peu hors scope mais je l'ai laissée dedans car je trouve que c'est cool quand même de ne pas dupliquer la logique de create

@@ -1638,7 +1638,9 @@ def test_get_single_declared_plant(self):
declaration = DeclarationFactory()
plant = DeclaredPlantFactory(declaration=declaration)

response = self.client.get(reverse("api:declared_plant", kwargs={"pk": plant.id}), format="json")
response = self.client.get(
reverse("api:declared_element", kwargs={"pk": plant.id, "type": "plant"}), format="json"
Copy link
Collaborator Author

@hfroot hfroot Dec 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dans cette PR, j'ai du changer l'API de REST vers RPC pour avoir un meilleur contrôle sur les actions de demande de plus d'infos (fait dans une PR avant), refus (aussi dans une autre PR), et le remplacement (le cible de cette PR).

Surtout, la refacto nous donne la possibilité de ne pas envoyer le statut et aussi de modifier des champs liés, comme mettre le champ new à False

Pendant ce changement, j'ai remarqué que c'est possible de changer l'API pour reduire la duplication de code entre 4 types d'element et 4 actions par demande.

Alors ici, on voit que j'ai remplacé l'api:declared_plant avec api:declared_element... "type": "plant"

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

D'accord avec toi, ces actions semblent être plus transactionnelles qu'autre chose, du coup une approche RPC me semble plus adapté 👍

reverse("api:declared_microorganism", kwargs={"pk": microorganism.id}),
{"requestStatus": DeclaredMicroorganism.AddableStatus.INFORMATION, "requestPrivateNotes": "some notes"},
response = self.client.post(
reverse("api:declared_element_request_info", kwargs={"pk": microorganism.id, "type": "microorganism"}),
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

avec la refacto, un appel REST vers api:declared_x est devenu un appel vers api:declared_x_action

self.assertEqual(microorganism.request_status, DeclaredMicroorganism.AddableStatus.INFORMATION)

@authenticate
def test_reject_declared_element(self):
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Il y avait pas un test pour le refus d'ingrédient avant

@@ -1753,3 +1808,46 @@ def test_status_visible_to_instructor(self):
m = declared_microorganisms[0]
self.assertIn("requestStatus", m)
self.assertIn("requestPrivateNotes", m)

@authenticate
def test_replace_declared_plant(self):
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

enfin on arrive aux tests ajoutés pour vérifier la vraie nouvelle fonctionnalité de cette PR 🎉

@@ -21,18 +21,22 @@
path("elements/autocomplete/", views.AutocompleteView.as_view(), name="substance_autocomplete"),
# Declared elements
path("new-declared-elements/", views.DeclaredElementsView.as_view(), name="list_new_declared_elements"),
path("declared-elements/plants/<int:pk>", views.DeclaredPlantView.as_view(), name="declared_plant"),
path("declared-elements/<str:type>/<int:pk>", views.DeclaredElementView.as_view(), name="declared_element"),
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maintenant le nouveau api c'est:

pour lire: declared-elements/<type>/<id>
pour prendre une decision: declared-elements/<type>/<id>/<action name>

(avant c'etait declared-elements/<type en pluriel>/<id> REST)

permission_classes = [(IsInstructor | IsVisor)]
serializer_class = DeclaredPlantSerializer
queryset = DeclaredPlant.objects.all()
class ElementMappingMixin:
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

au lieu d'avoir une vue dédiée pour chaque type d'element, ce mixin nous donne les clés pour avoir une vue par besoin (Retrieve, Refus, Demande d'infos, Remplacement, etc...)

@@ -77,6 +77,15 @@
/>

<p class="font-bold mt-8">Substances contenues dans la composition :</p>
<DsfrAlert v-if="replacedRequestsWithSubstances.length" type="warning" class="mb-4">
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

est-ce que c'est un pb que cet avertissement va rester même après l'aller-retour instructrice - déclarant - instructrice ?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Peut-être on pourra ajuster le message 🤔

« Les ingrédients suivantes, ajoutés pour remplacer une demande, rajoutent des substances dans la composition.
Veuillez vérifier que les doses totales des substances restent pertinentes. »

Ou enfin qqchose du genre, sans forcément dire qu'il faut toujours contacter le pro.

@blur="hasFocus = false"
@keydown="checkKeyboardNav($event)"
/>
<div :class="hideSearchButton ? '' : 'flex items-end'">
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

avant, quand hideSearchButton est false le label du champ DsfrInput (quand donné) est affiché à côté du champ et non pas au dessus. Ce changement repare ça

@@ -0,0 +1,48 @@
<template>
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pendant une refacto, j'ai créé ce fichier pour améliorer le focus du code principal dans index.vue

@hfroot hfroot marked this pull request as ready for review December 19, 2024 22:02
validated_data[self.nested_field_name] = self.nested_model.objects.get(pk=element.get("id"))
except self.nested_model.DoesNotExist:
raise ProjectAPIException(
field_errors={f"declared_{self.nested_field_name}s": "L'ingrédient spécifiée n'existe pas."}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Il faudra adapter aussi le message de l'exception non ? Genre

field_errors={f"declared_{self.nested_field_name}s": f"L'objet « ${self.nested_field_name} » spécifiée n'existe pas."}

@@ -1638,7 +1638,9 @@ def test_get_single_declared_plant(self):
declaration = DeclarationFactory()
plant = DeclaredPlantFactory(declaration=declaration)

response = self.client.get(reverse("api:declared_plant", kwargs={"pk": plant.id}), format="json")
response = self.client.get(
reverse("api:declared_element", kwargs={"pk": plant.id, "type": "plant"}), format="json"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

D'accord avec toi, ces actions semblent être plus transactionnelles qu'autre chose, du coup une approche RPC me semble plus adapté 👍

@@ -1753,3 +1808,46 @@ def test_status_visible_to_instructor(self):
m = declared_microorganisms[0]
self.assertIn("requestStatus", m)
self.assertIn("requestPrivateNotes", m)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Est-ce que ça vaut la peine d'ajouter un test sur les permissions pour ces nouveaux endpoints ? Similaire à test_get_declared_element_not_allowed_not_instructor

@@ -77,6 +77,15 @@
/>

<p class="font-bold mt-8">Substances contenues dans la composition :</p>
<DsfrAlert v-if="replacedRequestsWithSubstances.length" type="warning" class="mb-4">
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Peut-être on pourra ajuster le message 🤔

« Les ingrédients suivantes, ajoutés pour remplacer une demande, rajoutent des substances dans la composition.
Veuillez vérifier que les doses totales des substances restent pertinentes. »

Ou enfin qqchose du genre, sans forcément dire qu'il faut toujours contacter le pro.

@@ -85,6 +87,10 @@ const props = defineProps({
type: Boolean,
default: false,
},
optional: {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Est-ce que ça serait plus cohérent avec l'API des inputs de le nommer required ? et donc par la suite dans la ligne 11 :required="required".

Surtout que de cette façon les autres composants utilisant ElementAutocomplete et n'envoyant pas explicitement la prop se retrouveront avec un changement de comportement : leur champ deviendra obligatoire.

<DsfrBadge v-if="result.novelFood" label="Novel Food" small type="new" />
<ElementStatusBadge v-if="result.status" :text="result.status" />
</div>
<!-- TODO: make this a tag and align with badges, check DSFR -->
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

D'ailleurs on essaie de faire les commentaires en français dans le code. On n'est pas non plus hyper strictes mais p-e pour les prochains mieux de le faire en FR pour avoir une certaine cohérence.

<div v-if="modalToOpen === 'replace'">
<p v-if="cannotReplace">
Ce n'est pas possible pour l'instant de remplacer une demande avec un ingrédient d'un type different.
Veuillez contacter l'équipe Compl'Alim pour résoudre le substitution.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Veuillez contacter l'équipe Compl'Alim pour résoudre le substitution.
Veuillez contacter l'équipe Compl'Alim pour effectuer la substitution.

.json()
// TODO: is this handling errors nicely?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On peut le tester en envoyant un BadRequest programmatiquement, si tu veux on peut regarder ensemble

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.

ETQ instructrice, je veux remplacer une demande d'ajout d'ingrédient avec un ingrédient qui existe en base
2 participants