diff --git a/app/api/formations.py b/app/api/formations.py
index 7c6bd4930c3c0417df4a7653fe57c491f8896f26..ac5c10e2ac138a7736240dde9b153ac9182045c7 100644
--- a/app/api/formations.py
+++ b/app/api/formations.py
@@ -368,6 +368,8 @@ def get_module(module_id: int):
     return module.to_dict(convert_objects=True)
 
 
+@bp.route("/ue/set_code_apogee", methods=["POST"])
+@api_web_bp.route("/ue/set_code_apogee", methods=["POST"])
 @bp.route("/ue/<int:ue_id>/set_code_apogee/<string:code_apogee>", methods=["POST"])
 @api_web_bp.route(
     "/ue/<int:ue_id>/set_code_apogee/<string:code_apogee>", methods=["POST"]
@@ -381,17 +383,22 @@ def get_module(module_id: int):
 @login_required
 @scodoc
 @permission_required(Permission.EditFormation)
-def ue_set_code_apogee(ue_id: int, code_apogee: str = ""):
+def ue_set_code_apogee(ue_id: int | None = None, code_apogee: str = ""):
     """Change le code Apogée de l'UE.
     Le code est une chaîne, avec éventuellement plusieurs valeurs séparées
     par des virgules.
     (Ce changement peut être fait sur formation verrouillée)
 
+    Si ue_id n'est pas spécifié, utilise l'argument oid du POST.
     Si code_apogee n'est pas spécifié ou vide,
-    utilise l'argument value du POST (utilisé par jinplace.js)
+    utilise l'argument value du POST
 
     Le retour est une chaîne (le code enregistré), pas json.
     """
+    if ue_id is None:
+        ue_id = request.form.get("oid")
+        if ue_id is None:
+            return json_error(404, "argument oid manquant")
     if not code_apogee:
         code_apogee = request.form.get("value", "")
     query = UniteEns.query.filter_by(id=ue_id)
@@ -454,6 +461,8 @@ def ue_set_code_apogee_rcue(ue_id: int, code_apogee: str = ""):
     return code_apogee or ""
 
 
+@bp.route("/module/set_code_apogee", methods=["POST"])
+@api_web_bp.route("/module/set_code_apogee", methods=["POST"])
 @bp.route(
     "/module/<int:module_id>/set_code_apogee/<string:code_apogee>",
     methods=["POST"],
@@ -475,17 +484,22 @@ def ue_set_code_apogee_rcue(ue_id: int, code_apogee: str = ""):
 @login_required
 @scodoc
 @permission_required(Permission.EditFormation)
-def module_set_code_apogee(module_id: int, code_apogee: str = ""):
+def module_set_code_apogee(module_id: int | None = None, code_apogee: str = ""):
     """Change le code Apogée du module.
     Le code est une chaîne, avec éventuellement plusieurs valeurs séparées
     par des virgules.
     (Ce changement peut être fait sur formation verrouillée)
 
+    Si module_id n'est pas spécifié, utilise l'argument oid du POST.
     Si code_apogee n'est pas spécifié ou vide,
     utilise l'argument value du POST (utilisé par jinplace.js)
 
     Le retour est une chaîne (le code enregistré), pas json.
     """
+    if module_id is None:
+        module_id = request.form.get("oid")
+        if module_id is None:
+            return json_error(404, "argument oid manquant")
     if not code_apogee:
         code_apogee = request.form.get("value", "")
     query = Module.query.filter_by(id=module_id)
diff --git a/app/api/formsemestres.py b/app/api/formsemestres.py
index 7d91516dd92cc9ce57b3a3da7667fef944f02a58..6239c127836a9a8ae6cc4a5e12884981cb70a75c 100644
--- a/app/api/formsemestres.py
+++ b/app/api/formsemestres.py
@@ -14,7 +14,7 @@ from flask_json import as_json
 from flask_login import current_user, login_required
 import sqlalchemy as sa
 import app
-from app import db
+from app import db, log
 from app.api import api_bp as bp, api_web_bp, API_CLIENT_ERROR
 from app.decorators import scodoc, permission_required
 from app.scodoc.sco_utils import json_error
@@ -64,6 +64,7 @@ def formsemestre_infos(formsemestre_id: int):
           "date_fin": "31/08/2022",
           "dept_id": 1,
           "elt_annee_apo": null,
+          "elt_passage_apo" : null,
           "elt_sem_apo": null,
           "ens_can_edit_eval": false,
           "etat": true,
@@ -220,6 +221,127 @@ def formsemestre_edit(formsemestre_id: int):
     return formsemestre.to_dict_api()
 
 
+@bp.route("/formsemestre/apo/set_etapes", methods=["POST"])
+@api_web_bp.route("/formsemestre/apo/set_etapes", methods=["POST"])
+@scodoc
+@permission_required(Permission.EditApogee)
+def formsemestre_set_apo_etapes():
+    """Change les codes étapes du semestre indiqué.
+    Le code est une chaîne, avec éventuellement plusieurs valeurs séparées
+    par des virgules.
+    (Ce changement peut être fait sur un semestre verrouillé)
+
+    Args:
+       oid=int, le formsemestre_id
+       value=chaine "V1RT, V1RT2", codes séparés par des virgules
+    """
+    formsemestre_id = int(request.form.get("oid"))
+    etapes_apo_str = request.form.get("value")
+    formsemestre = FormSemestre.get_formsemestre(formsemestre_id)
+
+    current_etapes = {e.etape_apo for e in formsemestre.etapes}
+    new_etapes = {s.strip() for s in etapes_apo_str.split(",")}
+
+    if new_etapes != current_etapes:
+        formsemestre.etapes = []
+        for etape_apo in new_etapes:
+            etape = FormSemestreEtape(
+                formsemestre_id=formsemestre_id, etape_apo=etape_apo
+            )
+            formsemestre.etapes.append(etape)
+        db.session.add(formsemestre)
+        db.session.commit()
+        log(
+            f"""API formsemestre_set_apo_etapes: formsemestre_id={
+                formsemestre.id} code_apogee={etapes_apo_str}"""
+        )
+    return ("", 204)
+
+
+@bp.route("/formsemestre/apo/set_elt_sem", methods=["POST"])
+@api_web_bp.route("/formsemestre/apo/set_elt_sem", methods=["POST"])
+@scodoc
+@permission_required(Permission.EditApogee)
+def formsemestre_set_elt_sem_apo():
+    """Change les codes étapes du semestre indiqué.
+    Le code est une chaîne, avec éventuellement plusieurs valeurs séparées
+    par des virgules.
+    (Ce changement peut être fait sur un semestre verrouillé)
+
+    Args:
+       oid=int, le formsemestre_id
+       value=chaine "V3ONM, V3ONM1, V3ONM2", codes séparés par des virgules
+    """
+    oid = int(request.form.get("oid"))
+    value = (request.form.get("value") or "").strip()
+    formsemestre = FormSemestre.get_formsemestre(oid)
+    if value != formsemestre.elt_sem_apo:
+        formsemestre.elt_sem_apo = value
+        db.session.add(formsemestre)
+        db.session.commit()
+        log(
+            f"""API formsemestre_set_elt_sem_apo: formsemestre_id={
+                formsemestre.id} code_apogee={value}"""
+        )
+    return ("", 204)
+
+
+@bp.route("/formsemestre/apo/set_elt_annee", methods=["POST"])
+@api_web_bp.route("/formsemestre/apo/set_elt_annee", methods=["POST"])
+@scodoc
+@permission_required(Permission.EditApogee)
+def formsemestre_set_elt_annee_apo():
+    """Change les codes étapes du semestre indiqué (par le champ oid).
+    Le code est une chaîne, avec éventuellement plusieurs valeurs séparées
+    par des virgules.
+    (Ce changement peut être fait sur un semestre verrouillé)
+
+    Args:
+       oid=int, le formsemestre_id
+       value=chaine "V3ONM, V3ONM1, V3ONM2", codes séparés par des virgules
+    """
+    oid = int(request.form.get("oid"))
+    value = (request.form.get("value") or "").strip()
+    formsemestre = FormSemestre.get_formsemestre(oid)
+    if value != formsemestre.elt_annee_apo:
+        formsemestre.elt_annee_apo = value
+        db.session.add(formsemestre)
+        db.session.commit()
+        log(
+            f"""API formsemestre_set_elt_annee_apo: formsemestre_id={
+                formsemestre.id} code_apogee={value}"""
+        )
+    return ("", 204)
+
+
+@bp.route("/formsemestre/apo/set_elt_passage", methods=["POST"])
+@api_web_bp.route("/formsemestre/apo/set_elt_passage", methods=["POST"])
+@scodoc
+@permission_required(Permission.EditApogee)
+def formsemestre_set_elt_passage_apo():
+    """Change les codes apogée de passage du semestre indiqué (par le champ oid).
+    Le code est une chaîne, avec éventuellement plusieurs valeurs séparées
+    par des virgules.
+    (Ce changement peut être fait sur un semestre verrouillé)
+
+    Args:
+       oid=int, le formsemestre_id
+       value=chaine "V3ONM, V3ONM1, V3ONM2", codes séparés par des virgules
+    """
+    oid = int(request.form.get("oid"))
+    value = (request.form.get("value") or "").strip()
+    formsemestre = FormSemestre.get_formsemestre(oid)
+    if value != formsemestre.elt_annee_apo:
+        formsemestre.elt_passage_apo = value
+        db.session.add(formsemestre)
+        db.session.commit()
+        log(
+            f"""API formsemestre_set_elt_passage_apo: formsemestre_id={
+                formsemestre.id} code_apogee={value}"""
+        )
+    return ("", 204)
+
+
 @bp.route("/formsemestre/<int:formsemestre_id>/bulletins")
 @bp.route("/formsemestre/<int:formsemestre_id>/bulletins/<string:version>")
 @api_web_bp.route("/formsemestre/<int:formsemestre_id>/bulletins")
diff --git a/app/models/formsemestre.py b/app/models/formsemestre.py
index eb10f57b7444b6879e785d3fe84f75e9b8dcac99..b0a77139cc2fa0d55b1338623cb4c2e1ed5f626a 100644
--- a/app/models/formsemestre.py
+++ b/app/models/formsemestre.py
@@ -123,9 +123,11 @@ class FormSemestre(models.ScoDocModel):
     )
     "autorise les enseignants à créer des évals dans leurs modimpls"
     elt_sem_apo = db.Column(db.Text())  # peut être fort long !
-    "code element semestre Apogee, eg 'VRTW1' ou 'V2INCS4,V2INLS4,...'"
+    "code element semestre Apogée, eg 'VRTW1' ou 'V2INCS4,V2INLS4,...'"
     elt_annee_apo = db.Column(db.Text())
     "code element annee Apogee, eg 'VRT1A' ou 'V2INLA,V2INCA,...'"
+    elt_passage_apo = db.Column(db.Text())
+    "code element passage Apogée"
 
     # Data pour groups_auto_assignment
     # (ce champ est utilisé uniquement via l'API par le front js)
@@ -993,7 +995,12 @@ class FormSemestre(models.ScoDocModel):
 
     def get_codes_apogee(self, category=None) -> set[str]:
         """Les codes Apogée (codés en base comme "VRT1,VRT2")
-        category: None: tous, "etapes": étapes associées, "sem: code semestre", "annee": code annuel
+        category:
+            None: tous,
+            "etapes": étapes associées,
+            "sem: code semestre"
+            "annee": code annuel
+            "passage": code passage
         """
         codes = set()
         if category is None or category == "etapes":
@@ -1002,6 +1009,8 @@ class FormSemestre(models.ScoDocModel):
             codes |= {x.strip() for x in self.elt_sem_apo.split(",") if x}
         if (category is None or category == "annee") and self.elt_annee_apo:
             codes |= {x.strip() for x in self.elt_annee_apo.split(",") if x}
+        if (category is None or category == "passage") and self.elt_passage_apo:
+            codes |= {x.strip() for x in self.elt_passage_apo.split(",") if x}
         return codes
 
     def get_inscrits(self, include_demdef=False, order=False) -> list[Identite]:
diff --git a/app/scodoc/sco_dept.py b/app/scodoc/sco_dept.py
index 6a0ee01460e3e2ff142a5efccf6ebfb8466eb3fe..e033a89a54f60144e1a375ccd1012dd27cbd010f 100644
--- a/app/scodoc/sco_dept.py
+++ b/app/scodoc/sco_dept.py
@@ -137,6 +137,7 @@ def _convert_formsemestres_to_dicts(
             "bul_hide_xml": formsemestre.bul_hide_xml,
             "dateord": formsemestre.date_debut,
             "elt_annee_apo": formsemestre.elt_annee_apo,
+            "elt_passage_apo": formsemestre.elt_passage_apo,
             "elt_sem_apo": formsemestre.elt_sem_apo,
             "etapes_apo_str": formsemestre.etapes_apo_str(),
             "formation": f"{formation.acronyme} v{formation.version}",
@@ -189,6 +190,7 @@ def _sem_table_gt(formsemestres: Query, showcodes=False, fmt="html") -> GenTable
         "formation",
         "etapes_apo_str",
         "elt_annee_apo",
+        "elt_passage_apo",
         "elt_sem_apo",
     ]
     if showcodes:
@@ -203,9 +205,18 @@ def _sem_table_gt(formsemestres: Query, showcodes=False, fmt="html") -> GenTable
         html_class=html_class,
         html_sortable=True,
         html_table_attrs=f"""
-            data-apo_save_url="{url_for('notes.formsemestre_set_apo_etapes', scodoc_dept=g.scodoc_dept)}"
-            data-elt_annee_apo_save_url="{url_for('notes.formsemestre_set_elt_annee_apo', scodoc_dept=g.scodoc_dept)}"
-            data-elt_sem_apo_save_url="{url_for('notes.formsemestre_set_elt_sem_apo', scodoc_dept=g.scodoc_dept)}"
+            data-apo_save_url="{
+                url_for('apiweb.formsemestre_set_apo_etapes', scodoc_dept=g.scodoc_dept)
+            }"
+            data-elt_annee_apo_save_url="{
+                url_for('apiweb.formsemestre_set_elt_annee_apo', scodoc_dept=g.scodoc_dept)
+            }"
+            data-elt_sem_apo_save_url="{
+                url_for('apiweb.formsemestre_set_elt_sem_apo', scodoc_dept=g.scodoc_dept)
+            }"
+            data-elt_passage_apo_save_url="{
+                url_for('apiweb.formsemestre_set_elt_passage_apo', scodoc_dept=g.scodoc_dept)
+            }"
         """,
         html_with_td_classes=True,
         preferences=sco_preferences.SemPreferences(),
@@ -221,6 +232,7 @@ def _sem_table_gt(formsemestres: Query, showcodes=False, fmt="html") -> GenTable
             "etapes_apo_str": "Étape Apo.",
             "elt_annee_apo": "Elt. année Apo.",
             "elt_sem_apo": "Elt. sem. Apo.",
+            "elt_passage_apo": "Elt. pass. Apo.",
             "formation": "Formation",
         },
         table_id="semlist",
@@ -282,6 +294,9 @@ def _style_sems(sems: list[dict], fmt="html") -> list[dict]:
         sem["_elt_sem_apo_td_attrs"] = (
             f""" data-oid="{sem['formsemestre_id']}" data-value="{sem['elt_sem_apo']}" """
         )
+        sem["_elt_passage_apo_td_attrs"] = (
+            f""" data-oid="{sem['formsemestre_id']}" data-value="{sem['elt_passage_apo']}" """
+        )
     return sems
 
 
diff --git a/app/scodoc/sco_formation_recap.py b/app/scodoc/sco_formation_recap.py
index 3b5a923e9ee78ed28e33df858b319b1dc77317e3..f0074e4eb1cab69d8afe16b8eb2dd72c6fd7c84e 100644
--- a/app/scodoc/sco_formation_recap.py
+++ b/app/scodoc/sco_formation_recap.py
@@ -60,13 +60,15 @@ def formation_table_recap(formation_id, fmt="html") -> Response:
                 "_sem_order": f"{li:04d}",
                 "code": ue.acronyme,
                 "titre": ue.titre or "",
-                "_titre_target": url_for(
-                    "notes.ue_edit",
-                    scodoc_dept=g.scodoc_dept,
-                    ue_id=ue.id,
-                )
-                if can_edit
-                else None,
+                "_titre_target": (
+                    url_for(
+                        "notes.ue_edit",
+                        scodoc_dept=g.scodoc_dept,
+                        ue_id=ue.id,
+                    )
+                    if can_edit
+                    else None
+                ),
                 "apo": ue.code_apogee or "",
                 "_apo_td_attrs": f""" data-oid="{ue.id}" data-value="{ue.code_apogee or ''}" """,
                 "coef": ue.coefficient or "",
@@ -83,19 +85,23 @@ def formation_table_recap(formation_id, fmt="html") -> Response:
                 # le module (ou ressource ou sae)
                 T.append(
                     {
-                        "sem": f"S{mod.semestre_id}"
-                        if mod.semestre_id is not None
-                        else "-",
+                        "sem": (
+                            f"S{mod.semestre_id}"
+                            if mod.semestre_id is not None
+                            else "-"
+                        ),
                         "_sem_order": f"{li:04d}",
                         "code": mod.code,
                         "titre": mod.abbrev or mod.titre,
-                        "_titre_target": url_for(
-                            "notes.module_edit",
-                            scodoc_dept=g.scodoc_dept,
-                            module_id=mod.id,
-                        )
-                        if can_edit
-                        else None,
+                        "_titre_target": (
+                            url_for(
+                                "notes.module_edit",
+                                scodoc_dept=g.scodoc_dept,
+                                module_id=mod.id,
+                            )
+                            if can_edit
+                            else None
+                        ),
                         "apo": mod.code_apogee,
                         "_apo_td_attrs": f""" data-oid="{mod.id}" data-value="{mod.code_apogee or ''}" """,
                         "coef": mod.coefficient,
@@ -154,8 +160,12 @@ def formation_table_recap(formation_id, fmt="html") -> Response:
         html_class=html_class,
         html_class_ignore_default=True,
         html_table_attrs=f"""
-        data-apo_ue_save_url="{url_for('notes.ue_set_apo', scodoc_dept=g.scodoc_dept)}"
-        data-apo_mod_save_url="{url_for('notes.module_set_apo', scodoc_dept=g.scodoc_dept)}"
+        data-apo_ue_save_url="{
+            url_for('apiweb.ue_set_code_apogee', scodoc_dept=g.scodoc_dept)
+        }"
+        data-apo_mod_save_url="{
+            url_for('apiweb.module_set_code_apogee', scodoc_dept=g.scodoc_dept)
+        }"
         """,
         html_with_td_classes=True,
         base_url=f"{request.base_url}?formation_id={formation_id}",
diff --git a/app/scodoc/sco_formsemestre.py b/app/scodoc/sco_formsemestre.py
index 16daef1a7bd1ace2a05602d85db666f2393efc6d..00ea831e374c4ffc40867ec3cf32a38d8ed421a5 100644
--- a/app/scodoc/sco_formsemestre.py
+++ b/app/scodoc/sco_formsemestre.py
@@ -39,7 +39,7 @@ import app.scodoc.sco_utils as scu
 from app import log
 from app.models import Departement
 from app.models import Formation, FormSemestre
-from app.scodoc import sco_cache, codes_cursus, sco_formations, sco_preferences
+from app.scodoc import sco_cache, codes_cursus, sco_preferences
 from app.scodoc.gen_tables import GenTable
 from app.scodoc.codes_cursus import NO_SEMESTRE_ID
 from app.scodoc.sco_exceptions import ScoInvalidIdType, ScoValueError
@@ -68,6 +68,7 @@ _formsemestreEditor = ndb.EditableTable(
         "ens_can_edit_eval",
         "elt_sem_apo",
         "elt_annee_apo",
+        "elt_passage_apo",
         "edt_id",
     ),
     filter_dept=True,
diff --git a/app/scodoc/sco_formsemestre_edit.py b/app/scodoc/sco_formsemestre_edit.py
index 70237d20bcc622c60a5d47624c8d4cab450d1473..d7e6349a7a676b21bcc77a05f3f638c5b50038d2 100644
--- a/app/scodoc/sco_formsemestre_edit.py
+++ b/app/scodoc/sco_formsemestre_edit.py
@@ -464,6 +464,17 @@ def do_formsemestre_createwithmodules(edit=False, formsemestre: FormSemestre = N
             },
         )
     )
+    modform.append(
+        (
+            "elt_passage_apo",
+            {
+                "size": 32,
+                "title": "Element(s) Apogée passage:",
+                "explanation": "associé(s) au passage. Séparés par des virgules.",
+                "allow_null": True,  # toujours optionnel car rarement utilisé
+            },
+        )
+    )
     if ScoDocSiteConfig.get("edt_ics_path"):
         modform.append(
             (
diff --git a/app/static/js/scolar_index.js b/app/static/js/scolar_index.js
index 9b75eb0df0b5b173e6d0845c7f43e886f426d9a0..c2d09670bc8ab08896be020172db233d173e0d34 100644
--- a/app/static/js/scolar_index.js
+++ b/app/static/js/scolar_index.js
@@ -34,5 +34,9 @@ $(document).ready(function () {
     save_url = document.querySelector("table#semlist.apo_editable").dataset
       .elt_sem_apo_save_url;
     elt_sem_apo_editor = new ScoFieldEditor(".elt_sem_apo", save_url, false);
+
+    save_url = document.querySelector("table#semlist.apo_editable").dataset
+      .elt_passage_apo_save_url;
+    elt_passage_apo_editor = new ScoFieldEditor(".elt_passage_apo", save_url, false);
   }
 });
diff --git a/app/views/notes.py b/app/views/notes.py
index da115d62dd3e27b222752cef906d557d2bca1312..d82f0de38fac596b9a5d7be33f3b15875c49f582 100644
--- a/app/views/notes.py
+++ b/app/views/notes.py
@@ -3123,123 +3123,6 @@ sco_publish(
 )
 
 
-@bp.route("/formsemestre_set_apo_etapes", methods=["POST"])
-@scodoc
-@permission_required(Permission.EditApogee)
-def formsemestre_set_apo_etapes():
-    """Change les codes étapes du semestre indiqué.
-    Args: oid=formsemestre_id, value=chaine "V1RT, V1RT2", codes séparés par des virgules
-    """
-    formsemestre_id = int(request.form.get("oid"))
-    etapes_apo_str = request.form.get("value")
-    formsemestre: FormSemestre = FormSemestre.query.get_or_404(formsemestre_id)
-    current_etapes = {e.etape_apo for e in formsemestre.etapes}
-    new_etapes = {s.strip() for s in etapes_apo_str.split(",")}
-
-    if new_etapes != current_etapes:
-        formsemestre.etapes = []
-        for etape_apo in new_etapes:
-            etape = models.FormSemestreEtape(
-                formsemestre_id=formsemestre_id, etape_apo=etape_apo
-            )
-            formsemestre.etapes.append(etape)
-        db.session.add(formsemestre)
-        db.session.commit()
-        ScolarNews.add(
-            typ=ScolarNews.NEWS_APO,
-            text=f"Modification code Apogée du semestre {formsemestre.titre_annee()})",
-        )
-    return ("", 204)
-
-
-@bp.route("/formsemestre_set_elt_annee_apo", methods=["POST"])
-@scodoc
-@permission_required(Permission.EditApogee)
-def formsemestre_set_elt_annee_apo():
-    """Change les codes étapes du semestre indiqué.
-    Args: oid=formsemestre_id, value=chaine "V3ONM, V3ONM1, V3ONM2", codes séparés par des virgules
-    """
-    oid = int(request.form.get("oid"))
-    value = (request.form.get("value") or "").strip()
-    formsemestre: FormSemestre = FormSemestre.query.get_or_404(oid)
-    if value != formsemestre.elt_annee_apo:
-        formsemestre.elt_annee_apo = value
-        db.session.add(formsemestre)
-        db.session.commit()
-        ScolarNews.add(
-            typ=ScolarNews.NEWS_APO,
-            text=f"Modification code Apogée du semestre {formsemestre.titre_annee()})",
-        )
-    return ("", 204)
-
-
-@bp.route("/formsemestre_set_elt_sem_apo", methods=["POST"])
-@scodoc
-@permission_required(Permission.EditApogee)
-def formsemestre_set_elt_sem_apo():
-    """Change les codes étapes du semestre indiqué.
-    Args: oid=formsemestre_id, value=chaine "V3ONM, V3ONM1, V3ONM2", codes séparés par des virgules
-    """
-    try:
-        oid = int(request.form.get("oid"))
-    except (TypeError, ValueError) as exc:
-        raise ScoValueError("paramètre invalide") from exc
-    value = (request.form.get("value") or "").strip()
-    formsemestre: FormSemestre = FormSemestre.query.get_or_404(oid)
-    if value != formsemestre.elt_sem_apo:
-        formsemestre.elt_sem_apo = value
-        db.session.add(formsemestre)
-        db.session.commit()
-        ScolarNews.add(
-            typ=ScolarNews.NEWS_APO,
-            text=f"Modification code Apogée du semestre {formsemestre.titre_annee()})",
-        )
-    return ("", 204)
-
-
-@bp.route("/ue_set_apo", methods=["POST"])
-@scodoc
-@permission_required(Permission.EditApogee)
-def ue_set_apo():
-    """Change le code APO de l'UE
-    Args: oid=ue_id, value=chaine "VRTU12" (1 seul code / UE)
-    """
-    ue_id = int(request.form.get("oid"))
-    code_apo = (request.form.get("value") or "").strip()
-    ue = UniteEns.query.get_or_404(ue_id)
-    if code_apo != ue.code_apogee:
-        ue.code_apogee = code_apo
-        db.session.add(ue)
-        db.session.commit()
-        ScolarNews.add(
-            typ=ScolarNews.NEWS_FORM,
-            text=f"Modification code Apogée d'UE dans la formation {ue.formation.titre} ({ue.formation.acronyme})",
-        )
-    return ("", 204)
-
-
-@bp.route("/module_set_apo", methods=["POST"])
-@scodoc
-@permission_required(Permission.EditApogee)
-def module_set_apo():
-    """Change le code APO du module
-    Args: oid=ue_id, value=chaine "VRTU12" (1 seul code / UE)
-    """
-    oid = int(request.form.get("oid"))
-    code_apo = (request.form.get("value") or "").strip()
-    mod = Module.query.get_or_404(oid)
-    if code_apo != mod.code_apogee:
-        mod.code_apogee = code_apo
-        db.session.add(mod)
-        db.session.commit()
-        ScolarNews.add(
-            typ=ScolarNews.NEWS_FORM,
-            text=f"""Modification code Apogée d'UE dans la formation {
-                mod.formation.titre} ({mod.formation.acronyme})""",
-        )
-    return ("", 204)
-
-
 # sco_semset
 sco_publish("/semset_page", sco_semset.semset_page, Permission.EditApogee)
 sco_publish(
diff --git a/migrations/versions/07f37334727b_code_passage_apo.py b/migrations/versions/07f37334727b_code_passage_apo.py
new file mode 100644
index 0000000000000000000000000000000000000000..7f7a4147d533451c4d25929ec90e851a55aaf4f2
--- /dev/null
+++ b/migrations/versions/07f37334727b_code_passage_apo.py
@@ -0,0 +1,27 @@
+"""code_passage_apo
+
+Revision ID: 07f37334727b
+Revises: 809faa9d89ec
+Create Date: 2024-06-24 02:15:54.019156
+
+"""
+
+from alembic import op
+import sqlalchemy as sa
+
+
+# revision identifiers, used by Alembic.
+revision = "07f37334727b"
+down_revision = "809faa9d89ec"
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+    with op.batch_alter_table("notes_formsemestre", schema=None) as batch_op:
+        batch_op.add_column(sa.Column("elt_passage_apo", sa.Text(), nullable=True))
+
+
+def downgrade():
+    with op.batch_alter_table("notes_formsemestre", schema=None) as batch_op:
+        batch_op.drop_column("elt_passage_apo")