diff --git a/app/api/formations.py b/app/api/formations.py
index e618716b0faf1cae9302dca2ab78cbbf9e049dfb..def1c12e7ef7e6f0006dd50e247b16597874018f 100644
--- a/app/api/formations.py
+++ b/app/api/formations.py
@@ -21,6 +21,7 @@ from app import db, log
 from app.api import api_bp as bp, api_web_bp
 from app.api import api_permission_required as permission_required
 from app.decorators import scodoc
+from app.formations import edit_module
 from app.models import APO_CODE_STR_LEN
 from app.scodoc.sco_utils import json_error
 from app.models import (
@@ -493,8 +494,7 @@ def formation_module_edit(module_id: int):
         query = query.join(Formation).filter_by(dept_id=g.scodoc_dept_id)
     module: Module = query.first_or_404()
     args = request.get_json(force=True)  # may raise 400 Bad Request
-    module.from_dict(args)
-    db.session.commit()
+    edit_module.do_module_edit(args, module=module)
     db.session.refresh(module)
     log(f"API module_edit: module_id={module.id} args={args}")
     r = module.to_dict(convert_objects=True, with_parcours_ids=True)
diff --git a/app/formations/edit_module.py b/app/formations/edit_module.py
index 3885527744bd3ed624841c20e594c93938d9abf5..6fe09e8fc7285b13ec285fa50a0a326d24b4b3ee 100644
--- a/app/formations/edit_module.py
+++ b/app/formations/edit_module.py
@@ -33,7 +33,7 @@ from flask import flash, url_for, render_template
 from flask import g, request
 from flask_login import current_user
 
-from app import db
+from app import db, log
 from app import models
 from app.models import APO_CODE_STR_LEN
 from app.models import Formation, Matiere, Module, UniteEns
@@ -98,14 +98,42 @@ def module_delete(module_id=None):
     return flask.redirect(dest_url)
 
 
-def do_module_edit(vals: dict) -> None:
-    "edit a module"
+def do_module_edit(vals: dict, module: Module | None = None) -> None:
+    """edit a module (donné par module ou vals["module_id"]).
+    Gère les changements d'UEs.
+    commit et inval cache.
+    """
     # check
-    module = Module.get_instance(vals["module_id"])
+    if module is None:
+        module: Module = Module.get_instance(vals["module_id"])
+    # Changement d'UE ?
+    if (
+        not module.is_locked()
+        and "ue_id" in vals
+        and vals["ue_id"]
+        and vals["ue_id"] != module.ue_id
+    ):
+        # Affecte le module à la première matière de l'UE destination
+        ue_dest = UniteEns.get_ue(vals["ue_id"])
+        log(f"do_module_edit: changing UE of {module} to {ue_dest}")
+        mat_dest = ue_dest.matieres.first()
+        if not mat_dest:
+            log("do_module_edit: creating new Matiere in dest UE")
+            mat_dest = Matiere.create_from_dict(
+                {
+                    "ue_id": ue_dest.id,
+                    "titre": ue_dest.titre or "",
+                    "numero": 1,
+                }
+            )
+            db.session.add(mat_dest)
+        module.matiere = mat_dest
+        module.ue = ue_dest
     # edit
     modif = module.from_dict(vals)
     if modif:
         module.formation.invalidate_cached_sems()
+    db.session.commit()
 
 
 def module_create(
@@ -735,9 +763,8 @@ def module_edit(
                     tf[2]["app_critiques"], formation.referentiel_competence
                 )
 
-        # Check unicité code module dans la formation
-        # ??? TODO
-        #
+        # Le check unicité code module dans la formation
+        # est fait par Module.from_dict
         do_module_edit(tf[2])
     # Modifie les parcours
     if form_parcours is not None and formation.referentiel_competence:
diff --git a/app/models/formsemestre.py b/app/models/formsemestre.py
index cfcc47567f16098d597ca2d8887f9972c025191e..b3f8da2abc31d4986f41f176b6b43dc6662a9bca 100644
--- a/app/models/formsemestre.py
+++ b/app/models/formsemestre.py
@@ -216,6 +216,7 @@ class FormSemestre(models.ScoDocModel):
             except (TypeError, ValueError):
                 if accept_none:
                     return None
+                log("get_formsemestre: formsemestre_id invalide")
                 abort(404, "formsemestre_id invalide")
 
         dept_id = (
diff --git a/app/models/modules.py b/app/models/modules.py
index 6adba8d5089f9ee407a2271be0ec7ac00c7be7e2..5a8752a55f76948bf0fe73efb05166a74046bcac 100644
--- a/app/models/modules.py
+++ b/app/models/modules.py
@@ -183,6 +183,7 @@ class Module(models.ScoDocModel):
 
             matiere = db.session.get(Matiere, new_matiere_id)
             if matiere is None or matiere.ue_id != self.ue_id:
+                log("Module.from_dict: invalid matiere")
                 raise ScoValueError("invalid matiere")
 
         modified = super().from_dict(
diff --git a/app/scodoc/sco_utils.py b/app/scodoc/sco_utils.py
index 84a3d77d9405d229a1b16371bdc0c1508c45b6f2..2a56cacfdac0432dafc91888a03bdb05c99af888 100644
--- a/app/scodoc/sco_utils.py
+++ b/app/scodoc/sco_utils.py
@@ -1694,11 +1694,13 @@ def confirm_dialog(
     if add_headers:
         formsemestre = (
             FormSemestre.get_formsemestre(parameters["formsemestre_id"])
-            if "formsemestre_id" in parameters
+            if parameters.get("formsemestre_id")
             else None
         )
         etud = (
-            Identite.get_etud(parameters["etudid"]) if "etudid" in parameters else None
+            Identite.get_etud(parameters["etudid"])
+            if parameters.get("etudid")
+            else None
         )
         return render_template(
             template,
diff --git a/tests/api/start_api_server.sh b/tests/api/start_api_server.sh
index ac4a1ce0a3c8517551e9178e69c5f406ab0445ee..0fdd447fbbbe894aee2005e46fc62c9011fd7bdd 100755
--- a/tests/api/start_api_server.sh
+++ b/tests/api/start_api_server.sh
@@ -27,7 +27,7 @@ done
 source "$SCRIPT_DIR"/.env
 
 export FLASK_ENV=test_api
-export FLASK_DEBUG=1 
+export FLASK_DEBUG=1
 tools/create_database.sh --drop SCODOC_TEST_API
 flask db upgrade
 flask sco-db-init --erase
@@ -39,7 +39,7 @@ flask edit-role LecteurAPI -a ScoView
 flask user-role "$API_USER" -a LecteurAPI
 
 if [ -z "$PORT" ]
-then 
+then
     flask run --host 0.0.0.0
 else
     flask run --host 0.0.0.0 -p "$PORT"
diff --git a/tests/api/test_api_formations.py b/tests/api/test_api_formations.py
index 8192961bcac00fd3fe48394291828caa01b16293..ec3a08883dd11f85112d27c7de0de5c201ede18b 100644
--- a/tests/api/test_api_formations.py
+++ b/tests/api/test_api_formations.py
@@ -388,14 +388,6 @@ def test_api_module_edit(api_admin_headers):
     assert module["heures_cours"] == 16
     assert module["semestre_id"] == 1
     assert module["abbrev"] == "ALLO"
-    # tente de changer l'UE: ne devrait rien faire:
-    ue_id = module["ue_id"]
-    module = POST(
-        f"/formation/module/{module_id}/edit",
-        {"ue_id": 666},
-        api_admin_headers,
-    )
-    assert module["ue_id"] == ue_id
     # tente de changer la formation: ne devrait rien faire:
     formation_id = module["formation_id"]
     module = POST(