Files
Forge-Engine/screens/data_actions/apply_data_action.py
T
williamandClaude Sonnet 5 bb84b7b377
Build and deploy / test (push) Failing after 13s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Phase 0 : factorise compute_new_value + ajoute pytest/node test à la CI
1. Duplication éliminée avant que la Phase 2 (hasard/opérations
   mathématiques) n'en ajoute 6 de plus aux DEUX fichiers : la chaîne
   d'opérations quasi identique entre screens/data_actions/
   apply_data_action.py (champ d'objet) et apply_variable_action.py
   (variable globale) est factorisée dans un nouveau
   compute_operation.py::compute_new_value(operation, current, raw_value,
   is_decimal), réutilisé par les deux. Nouveau tests/test_compute_operation.py
   verrouille le comportement des 7 opérations existantes (dont les cas
   limites : valeur invalide, type décimal vs entier, opération inconnue)
   avant d'en ajouter d'autres.

2. .gitea/workflows/deploy.yml déployait en prod à chaque push sur main
   sans jamais exécuter la suite de tests — rien ne bloquait
   techniquement un commit cassé. Nouveau job "test" (pytest + node:test
   sur la logique pure de static/js/play/, via des conteneurs officiels
   plutôt que des actions du marketplace, cohérent avec le choix déjà
   fait dans ce fichier) tourne sur CHAQUE push (main ET dev, utile pour
   ce dépôt qui travaille sur dev) ; "build-and-push"/"deploy" gagnent un
   "needs: test" et restent réservés à main (filtre sur gitea.ref) — un
   push sur dev ne redéploie jamais la prod, seulement les tests.

Vérifié : 223 tests passent (8 nouveaux), YAML validé.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-30 15:00:23 +02:00

59 lines
2.4 KiB
Python

import db
from .compute_operation import compute_new_value
def apply_data_action(slug, action):
"""Exécute au moment du clic (mode jouable) une action "modifier_donnee" :
lit l'objet/la ligne/le champ visés dans l'action, calcule la nouvelle
valeur selon l'opération choisie, et l'enregistre — un vrai UPDATE SQL,
limité à cette seule colonne (les autres champs de la ligne ne sont pas
touchés)."""
definition_id = action.get("target_definition_id")
row_id = action.get("target_row_id")
field_name = action.get("target_field")
operation = action.get("data_operation")
raw_value = action.get("data_value")
if not (definition_id and row_id and field_name and operation):
return False
definition = db.get_definition(slug, definition_id)
if not definition:
return False
field_def = next((f for f in definition["fields"] if f["name"] == field_name), None)
if not field_def:
return False
row = db.get_row(slug, definition, row_id)
if not row:
return False
col = db.slugify(field_def["name"]).replace("-", "_")
if field_def["type"] == "relation":
col = col + "_id"
current = row.get(col)
try:
new_value = compute_new_value(operation, current, raw_value, field_def["type"] == "nombre_decimal")
except ValueError:
return False
new_value = _clamp_to_field_bounds(new_value, field_def)
db.update_row_field(slug, definition, row_id, field_def, new_value)
return True
def _clamp_to_field_bounds(value, field_def):
"""2.2 — bornage automatique : ramène la valeur dans [min_value,
max_value] si l'un ou l'autre est réglé sur ce champ, pour qu'une
jauge (voir render_jauge.py) ne puisse jamais dépasser 100 % ni
descendre sous 0 % (ou toute autre borne définie), quel que soit le
nombre d'actions "Augmenter de..."/"Diminuer de..." posées dans le
graphe de logique. Ne s'applique qu'aux champs numériques — un champ
non numérique n'a pas de min_value/max_value (voir create_definition)."""
if field_def["type"] not in ("nombre_entier", "nombre_decimal"):
return value
min_v, max_v = field_def.get("min_value"), field_def.get("max_value")
if min_v is not None and value < min_v:
value = min_v
if max_v is not None and value > max_v:
value = max_v
return value if field_def["type"] == "nombre_decimal" else int(value)