parent
8b0f29583a
commit
2a5327fc88
|
@ -4,29 +4,12 @@
|
|||
<option name="autoReloadType" value="SELECTIVE" />
|
||||
</component>
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="c6d0259a-16e1-410d-91a1-830590ee2a08" name="Changes" comment="31/05/2025 - 20h">
|
||||
<change afterPath="$PROJECT_DIR$/reference_pedagogique_mgt.py" afterDir="false" />
|
||||
<list default="true" id="c6d0259a-16e1-410d-91a1-830590ee2a08" name="Changes" comment="02/06/2025 - 20h">
|
||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/Activite.py" beforeDir="false" afterPath="$PROJECT_DIR$/Activite.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/Doc_tech/spec.txt" beforeDir="false" afterPath="$PROJECT_DIR$/Doc_tech/spec.txt" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/E_Sign_Document.py" beforeDir="false" afterPath="$PROJECT_DIR$/E_Sign_Document.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/Inscription_mgt.py" beforeDir="false" afterPath="$PROJECT_DIR$/Inscription_mgt.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/Job_Cron_Common.py" beforeDir="false" afterPath="$PROJECT_DIR$/Job_Cron_Common.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/Session_Formation.py" beforeDir="false" afterPath="$PROJECT_DIR$/Session_Formation.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/attached_file_mgt.py" beforeDir="false" afterPath="$PROJECT_DIR$/attached_file_mgt.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/base_class_calcul_note.py" beforeDir="false" afterPath="$PROJECT_DIR$/base_class_calcul_note.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/base_document_automatic_setup.py" beforeDir="false" afterPath="$PROJECT_DIR$/base_document_automatic_setup.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/class_mgt.py" beforeDir="false" afterPath="$PROJECT_DIR$/class_mgt.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/competence.py" beforeDir="false" afterPath="$PROJECT_DIR$/competence.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/crm_opportunite.py" beforeDir="false" afterPath="$PROJECT_DIR$/crm_opportunite.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/financial_caracteristique_mgt.py" beforeDir="false" afterPath="$PROJECT_DIR$/financial_caracteristique_mgt.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/formulaire_mgt.py" beforeDir="false" afterPath="$PROJECT_DIR$/formulaire_mgt.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/jury_mgt.py" beforeDir="false" afterPath="$PROJECT_DIR$/jury_mgt.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/lms_chamilo/mysy_lms.py" beforeDir="false" afterPath="$PROJECT_DIR$/lms_chamilo/mysy_lms.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/main.py" beforeDir="false" afterPath="$PROJECT_DIR$/main.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/note_evaluation_mgt.py" beforeDir="false" afterPath="$PROJECT_DIR$/note_evaluation_mgt.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/partner_client.py" beforeDir="false" afterPath="$PROJECT_DIR$/partner_client.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/prj_common.py" beforeDir="false" afterPath="$PROJECT_DIR$/prj_common.py" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/survey_mgt.py" beforeDir="false" afterPath="$PROJECT_DIR$/survey_mgt.py" afterDir="false" />
|
||||
</list>
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
|
@ -72,7 +55,7 @@
|
|||
"RunOnceActivity.OpenProjectViewOnStart": "true",
|
||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||
"git-widget-placeholder": "master__Elyos__FI",
|
||||
"last_opened_file_path": "C:/Users/Cherif/Documents/myclass.com/Siteweb/Elyos_Ftion_Initiale/Ela_back/Back_Office_FI",
|
||||
"last_opened_file_path": "C:/Users/Cherif/Documents/myclass.com/Siteweb/Elyos_Ftion_Continue/Ela_back/Back_Office",
|
||||
"settings.editor.selected.configurable": "project.propVCSSupport.CommitDialog"
|
||||
}
|
||||
}</component>
|
||||
|
@ -468,7 +451,7 @@
|
|||
<option name="project" value="LOCAL" />
|
||||
<updated>1747251650255</updated>
|
||||
</task>
|
||||
<option name="localTasksCounter" value="465" />
|
||||
<option name="localTasksCounter" value="466" />
|
||||
<servers />
|
||||
</component>
|
||||
<component name="Vcs.Log.Tabs.Properties">
|
||||
|
@ -510,7 +493,6 @@
|
|||
</option>
|
||||
</component>
|
||||
<component name="VcsManagerConfiguration">
|
||||
<MESSAGE value="10/02/205 - 21h" />
|
||||
<MESSAGE value="11/02/2025 - 15h30" />
|
||||
<MESSAGE value="11/02/2025 - 21h" />
|
||||
<MESSAGE value="18/02/2025 - 21h" />
|
||||
|
@ -535,6 +517,7 @@
|
|||
<MESSAGE value="Init - 29/05/2025" />
|
||||
<MESSAGE value="Init - 29/05/2025 - FI" />
|
||||
<MESSAGE value="31/05/2025 - 20h" />
|
||||
<option name="LAST_COMMIT_MESSAGE" value="31/05/2025 - 20h" />
|
||||
<MESSAGE value="02/06/2025 - 20h" />
|
||||
<option name="LAST_COMMIT_MESSAGE" value="02/06/2025 - 20h" />
|
||||
</component>
|
||||
</project>
|
|
@ -63,7 +63,7 @@ def Add_Update_SessionFormation(diction):
|
|||
'class_internal_url', 'session_status', 'date_debut_inscription', 'date_fin_inscription',
|
||||
'attestation_certif', "distantiel", "presentiel", "prix_session", 'contenu_ftion', 'lms_class_code',
|
||||
'session_ondemande', 'source', 'session_id', 'session_etape', 'pays', 'formateur_id',
|
||||
'titre', 'location_type', 'is_bpf', 'site_formation_id', 'price_by', 'mode_animation']
|
||||
'titre', 'location_type', 'is_bpf', 'site_formation_id', 'price_by', 'mode_animation', 'archive']
|
||||
|
||||
incom_keys = diction.keys()
|
||||
for val in incom_keys:
|
||||
|
@ -223,6 +223,12 @@ def Add_Update_SessionFormation(diction):
|
|||
mydata['session_ondemande'] = "0"
|
||||
|
||||
|
||||
if ("archive" in diction.keys()):
|
||||
mydata['archive'] = str(diction['archive'])
|
||||
else:
|
||||
mydata['archive'] = "0"
|
||||
|
||||
|
||||
if ("nb_participant" in diction.keys()):
|
||||
nb_particants = str(mycommon.tryInt(str(diction['nb_participant'])))
|
||||
if( nb_particants == "0" ):
|
||||
|
@ -880,6 +886,9 @@ def GetSessionFormation(diction):
|
|||
if ("automatic_traitement" not in user.keys() ):
|
||||
user['automatic_traitement'] = []
|
||||
|
||||
if ("archive" not in user.keys() ):
|
||||
user['archive'] = "0"
|
||||
|
||||
if ('myclass' in retval.keys() and len(retval['myclass']) > 0):
|
||||
|
||||
if ("_id" in retval['myclass'][0].keys()):
|
||||
|
@ -1079,6 +1088,9 @@ def Get_Given_SessionFormation_From_Id(diction):
|
|||
class_ispublished = "0"
|
||||
class_lms_class_code = ""
|
||||
|
||||
if( "archive" not in user.keys() ):
|
||||
user['user'] = "0"
|
||||
|
||||
if ('myclass' in retval.keys() and len(retval['myclass']) > 0):
|
||||
|
||||
if ("_id" in retval['myclass'][0].keys()):
|
||||
|
@ -1110,7 +1122,6 @@ def Get_Given_SessionFormation_From_Id(diction):
|
|||
|
||||
if ("invoiced_statut" in retval.keys() ):
|
||||
user['invoiced_statut'] = retval['invoiced_statut']
|
||||
|
||||
else:
|
||||
user['invoiced_statut'] = "0"
|
||||
|
||||
|
@ -1195,7 +1206,7 @@ Donc faire attention au controle de la connexion
|
|||
def GetActiveSessionFormation_List(diction):
|
||||
try:
|
||||
|
||||
field_list = ['token', 'class_internal_url']
|
||||
field_list = ['token', 'class_internal_url', 'archive']
|
||||
incom_keys = diction.keys()
|
||||
for val in incom_keys:
|
||||
if val not in field_list and val.startswith('my_') is False:
|
||||
|
@ -1219,6 +1230,12 @@ def GetActiveSessionFormation_List(diction):
|
|||
if (local_status is not True):
|
||||
return local_status, my_partner
|
||||
|
||||
filt_archive = {}
|
||||
if ("archive" in diction.keys() and (diction["archive"] == "0" or diction["archive"] == "")):
|
||||
filt_archive = {'archive': {'$ne': '1'}}
|
||||
|
||||
elif ("archive" in diction.keys() and diction["archive"] == "1"):
|
||||
filt_archive = {'archive': '1'}
|
||||
|
||||
# Recuperation du recid du partner
|
||||
mydata = {}
|
||||
|
@ -1240,9 +1257,13 @@ def GetActiveSessionFormation_List(diction):
|
|||
RetObject = []
|
||||
|
||||
print(" ### GetActiveSessionFormation_List myquery = ", myquery)
|
||||
for retval in coll_session.find(myquery):
|
||||
for retval in coll_session.find({"$and":[myquery,filt_archive]} ):
|
||||
|
||||
local_tmp = retval
|
||||
|
||||
if ("archive" not in local_tmp.keys()):
|
||||
local_tmp['user'] = "0"
|
||||
|
||||
## Verification des conditions supplementaires :
|
||||
# en realité c'est parce que c'est chaud de le faire avec pymongo
|
||||
tmp_debut_inscritp = str(local_tmp['date_debut_inscription']).strip().split(" ")
|
||||
|
@ -1320,14 +1341,18 @@ def Get_Partner_Session_Ftion_Reduice_Fields(diction):
|
|||
my_partner['scope']['class_id']) > 0):
|
||||
filt_scope_class_id['class_id'] = {'$in': my_partner['scope']['class_id']}
|
||||
|
||||
filt_archive = {}
|
||||
if ("archive" in diction.keys() and (diction["archive"] == "0" or diction["archive"] == "")):
|
||||
filt_archive = {'archive': {'$ne': '1'}}
|
||||
|
||||
RetObject = []
|
||||
elif ("archive" in diction.keys() and diction["archive"] == "1"):
|
||||
filt_archive = {'archive': '1'}
|
||||
|
||||
RetObject = []
|
||||
val_tmp = 0
|
||||
|
||||
for retval in MYSY_GV.dbname['session_formation'].find({ "$and" : [ {'partner_owner_recid':str(my_partner['recid']),
|
||||
'valide':'1',},filt_scope_class_id]},
|
||||
'valide':'1',},filt_scope_class_id, filt_archive]},
|
||||
{'_id':1, 'code_session':1, 'titre':1,
|
||||
'class_internal_url':1, 'date_debut':1,
|
||||
'date_fin':1}):
|
||||
|
@ -1460,7 +1485,7 @@ Qu'elles soient cloturées ou pas."""
|
|||
def GetAllValideSessionFormation_List(diction):
|
||||
try:
|
||||
|
||||
field_list = ['token', 'class_internal_url']
|
||||
field_list = ['token', 'class_internal_url', 'archive']
|
||||
incom_keys = diction.keys()
|
||||
for val in incom_keys:
|
||||
if val not in field_list and val.startswith('my_') is False:
|
||||
|
@ -1490,6 +1515,17 @@ def GetAllValideSessionFormation_List(diction):
|
|||
if diction['class_internal_url']:
|
||||
class_internal_url = diction['class_internal_url']
|
||||
|
||||
filt_archive = {}
|
||||
if ("archive" in diction.keys() and str(diction['archive']) == "1"):
|
||||
filt_archive = {'archive': '1'}
|
||||
|
||||
elif ("archive" in diction.keys() and (str(diction['archive']) == "0" or str(diction['archive']) == "")):
|
||||
filt_archive = {'archive': {'$ne': '1'}}
|
||||
|
||||
elif ("archive" not in diction.keys()):
|
||||
filt_archive = {'archive': {'$ne': '1'}}
|
||||
|
||||
|
||||
coll_session = MYSY_GV.dbname['session_formation']
|
||||
myquery = {}
|
||||
myquery['class_internal_url'] = class_internal_url
|
||||
|
@ -1499,7 +1535,7 @@ def GetAllValideSessionFormation_List(diction):
|
|||
print(" ##### myquery tt = "+str(myquery))
|
||||
RetObject = []
|
||||
|
||||
for retval in coll_session.find(myquery):
|
||||
for retval in coll_session.find({"$and": [myquery,filt_archive]}):
|
||||
#print(" ##### retval = " + str(retval))
|
||||
RetObject.append(mycommon.JSONEncoder().encode(retval))
|
||||
|
||||
|
@ -1518,7 +1554,7 @@ Cette fonction recupere toutes les sessions de formation d'un partenaire
|
|||
def GetAllValideSessionPartner_List(diction):
|
||||
try:
|
||||
|
||||
field_list = ['token']
|
||||
field_list = ['token', 'archive']
|
||||
incom_keys = diction.keys()
|
||||
for val in incom_keys:
|
||||
if val not in field_list and val.startswith('my_') is False:
|
||||
|
@ -1548,8 +1584,16 @@ def GetAllValideSessionPartner_List(diction):
|
|||
if (local_status is not True):
|
||||
return local_status, my_partner
|
||||
|
||||
filt_archive = {}
|
||||
if ("archive" in diction.keys() and (diction["archive"] == "0" or diction["archive"] == "")):
|
||||
filt_archive = {'archive': {'$ne': '1'}}
|
||||
|
||||
elif ("archive" in diction.keys() and diction["archive"] == "1"):
|
||||
filt_archive = {'archive': '1'}
|
||||
|
||||
|
||||
"""
|
||||
Filtre uniquement pour la Formation continue
|
||||
Filtre uniquement pour la Formation Initiale
|
||||
"""
|
||||
filt_scope_class_id = {}
|
||||
if ("scope" in my_partner.keys() and "class_id" in my_partner['scope'].keys() and len(
|
||||
|
@ -1560,7 +1604,7 @@ def GetAllValideSessionPartner_List(diction):
|
|||
|
||||
filt_class_partner_recid = {'partner_owner_recid': str(my_partner['recid'])}
|
||||
|
||||
query = [{'$match':{ "$and" : [{'partner_owner_recid':str(my_partner['recid'])},filt_scope_class_id]} },
|
||||
query = [{'$match':{ "$and" : [{'partner_owner_recid':str(my_partner['recid'])},filt_scope_class_id, filt_archive]} },
|
||||
{ '$sort': {'_id': -1}},
|
||||
{'$lookup':
|
||||
{
|
||||
|
@ -1615,6 +1659,11 @@ def GetAllValideSessionPartner_List(diction):
|
|||
else:
|
||||
val['is_bpf'] = ""
|
||||
|
||||
if ("archive" in retVal.keys()):
|
||||
val['archive'] = retVal['archive']
|
||||
else:
|
||||
val['archive'] = ""
|
||||
|
||||
|
||||
if ("session_status" in retVal.keys()):
|
||||
val['session_status'] = retVal['session_status']
|
||||
|
@ -1855,7 +1904,7 @@ def GetAllValideSessionPartner_List_filter_like(diction):
|
|||
|
||||
field_list = ['token','class_title', 'code_session', 'class_external_code',
|
||||
'session_start_date', 'session_end_date',
|
||||
'class_internal_url']
|
||||
'class_internal_url', 'archive']
|
||||
incom_keys = diction.keys()
|
||||
for val in incom_keys:
|
||||
if val not in field_list and val.startswith('my_') is False:
|
||||
|
@ -1926,8 +1975,18 @@ def GetAllValideSessionPartner_List_filter_like(diction):
|
|||
inspect.stack()[0][3]) + " Le filtre : 'date de fin de session' n'est pas au format jj/mm/aaaa.")
|
||||
return False, " Le filtre : 'date de fin de session' n'est pas au format jj/mm/aaaa."
|
||||
|
||||
filt_archive = {}
|
||||
if ("archive" in diction.keys() and str(diction['archive']) == "1"):
|
||||
filt_archive = {'archive': '1'}
|
||||
|
||||
elif ("archive" in diction.keys() and (str(diction['archive']) == "0" or str(diction['archive']) == "")):
|
||||
filt_archive = {'archive': {'$ne': '1'}}
|
||||
|
||||
elif ("archive" not in diction.keys()):
|
||||
filt_archive = {'archive': {'$ne': '1'}}
|
||||
|
||||
"""
|
||||
Filtre uniquement pour la Formation continue
|
||||
Filtre uniquement pour la Formation Initiale
|
||||
"""
|
||||
filt_scope_class_id = {}
|
||||
if ("scope" in my_partner.keys() and "class_id" in my_partner['scope'].keys() and len(
|
||||
|
@ -1937,7 +1996,7 @@ def GetAllValideSessionPartner_List_filter_like(diction):
|
|||
|
||||
coll_session = MYSY_GV.dbname['session_formation']
|
||||
|
||||
query = [ {'$match':{ '$and' : [ filt_code_session,{'partner_owner_recid':str(my_partner['recid'])}, filt_scope_class_id] }} ,
|
||||
query = [ {'$match':{ '$and' : [ filt_code_session,{'partner_owner_recid':str(my_partner['recid'])}, filt_scope_class_id, filt_archive] }} ,
|
||||
{'$sort': {'_id': -1}},
|
||||
{'$lookup':
|
||||
{
|
||||
|
@ -1984,6 +2043,10 @@ def GetAllValideSessionPartner_List_filter_like(diction):
|
|||
else:
|
||||
val['is_bpf'] = ""
|
||||
|
||||
if ("archive" in retVal.keys()):
|
||||
val['archive'] = retVal['archive']
|
||||
else:
|
||||
val['archive'] = ""
|
||||
|
||||
if( "session_etape" in retVal.keys()):
|
||||
val['session_etape'] = retVal['session_etape']
|
||||
|
@ -2260,7 +2323,7 @@ Recuperation de la liste sans filtre
|
|||
def GetAllValideSessionPartner_List_no_filter(diction):
|
||||
try:
|
||||
|
||||
field_list = ['token','class_title', 'code_session']
|
||||
field_list = ['token','class_title', 'code_session', 'archive']
|
||||
incom_keys = diction.keys()
|
||||
for val in incom_keys:
|
||||
if val not in field_list and val.startswith('my_') is False:
|
||||
|
@ -2290,10 +2353,19 @@ def GetAllValideSessionPartner_List_no_filter(diction):
|
|||
if (local_status is not True):
|
||||
return local_status, my_partner
|
||||
|
||||
filt_archive = {}
|
||||
if ("archive" in diction.keys() and str(diction['archive']) == "1"):
|
||||
filt_archive = {'archive': '1'}
|
||||
|
||||
elif ("archive" in diction.keys() and (str(diction['archive']) == "0" or str(diction['archive']) == "")):
|
||||
filt_archive = {'archive': {'$ne': '1'}}
|
||||
|
||||
elif ("archive" not in diction.keys()):
|
||||
filt_archive = {'archive': {'$ne': '1'}}
|
||||
|
||||
coll_session = MYSY_GV.dbname['session_formation']
|
||||
|
||||
query = [{'$match':{'partner_owner_recid': str(my_partner['recid'])} },
|
||||
query = [{'$match':{"$and": [{'partner_owner_recid': str(my_partner['recid'])}, filt_archive]} },
|
||||
{'$lookup':
|
||||
{
|
||||
'from': 'myclass',
|
||||
|
@ -2337,6 +2409,10 @@ def GetAllValideSessionPartner_List_no_filter(diction):
|
|||
else:
|
||||
val['is_bpf'] = ""
|
||||
|
||||
if ("archive" in retVal.keys()):
|
||||
val['archive'] = retVal['archive']
|
||||
else:
|
||||
val['archive'] = ""
|
||||
|
||||
if( "session_etape" in retVal.keys()):
|
||||
val['session_etape'] = retVal['session_etape']
|
||||
|
@ -10663,6 +10739,7 @@ def Prepare_and_Send_Facture_From_Session_By_Inscription_Id(tab_files, Folder, d
|
|||
if (local_status is not True):
|
||||
return local_status, my_partner, False
|
||||
|
||||
liste_client_client_rattachement_id = []
|
||||
my_inscription_ids = ""
|
||||
tab_my_inscription_ids = []
|
||||
tab_my_inscription_ids_Object = []
|
||||
|
@ -14910,3 +14987,198 @@ def Get_Given_SessionFormation_List_Automatic_Traitement_From(diction):
|
|||
return False, "Impossible de récupérer La promotion de formation"
|
||||
|
||||
|
||||
"""
|
||||
02/06/2025 : Archivage d'une session
|
||||
"""
|
||||
def archive_session(diction):
|
||||
try:
|
||||
|
||||
'''
|
||||
# Verification que les champs reçus dans l'API sont bien dans la liste des champs autorisés
|
||||
# Cela evite le cas ou une entité tierce ajouter les valeurs inconnu dans l'API
|
||||
# Ici on doit mettre tous les champs possible (obligatoire ou non) de la BDD dans la liste
|
||||
# field_list.
|
||||
'''
|
||||
field_list = ['tab_session_id', 'token']
|
||||
incom_keys = diction.keys()
|
||||
for val in incom_keys:
|
||||
if val not in field_list and val.startswith('my_') is False:
|
||||
mycommon.myprint(
|
||||
str(inspect.stack()[0][3])+" - Le champ '" + val + "' n'existe pas, Creation formation annulée")
|
||||
return False, " Impossible de mettre à jour la formation"
|
||||
|
||||
'''
|
||||
Une fois qu'on a controlé que toutes les clés mise dans l'API sont correcte. etape precedente,
|
||||
On controle que les champs obligatoires sont presents dans la liste
|
||||
'''
|
||||
field_list_obligatoire = ['tab_session_id', 'token']
|
||||
|
||||
for val in field_list_obligatoire:
|
||||
if val not in diction:
|
||||
mycommon.myprint(str(inspect.stack()[0][3])+" - La valeur '" + val + "' n'est pas presente dans la liste des arguments ")
|
||||
return False, " Impossible de mettre à jour la formation"
|
||||
|
||||
# recuperation des paramettre
|
||||
mydata = {}
|
||||
my_internal_url = ""
|
||||
|
||||
if ("token" in diction.keys()):
|
||||
if diction['token']:
|
||||
mydata['token'] = diction['token']
|
||||
|
||||
# Verification de la validité du token
|
||||
tab_session_ids = []
|
||||
tab_session_ids_ObjectId = []
|
||||
if ("tab_session_id" in diction.keys()):
|
||||
tab_session_ids = str(diction['tab_session_id']).split(",")
|
||||
|
||||
for tmp in tab_session_ids:
|
||||
if (tmp):
|
||||
tab_session_ids_ObjectId.append(ObjectId(tmp))
|
||||
|
||||
# Verification de la validité du token
|
||||
local_status, my_partner = mycommon.Check_Connexion_And_Return_Partner_Data(diction)
|
||||
if (local_status is not True):
|
||||
return local_status, my_partner
|
||||
|
||||
mydata['date_update'] = str(datetime.now())
|
||||
mydata['update_by'] = str(my_partner['_id'])
|
||||
mydata['archive'] = '1'
|
||||
|
||||
|
||||
coll_name = MYSY_GV.dbname['session_formation']
|
||||
|
||||
# seules les formations avec locked = 0 et valide=1 sont modifiables
|
||||
ret_val = coll_name.update_many(
|
||||
{'_id': {'$in': tab_session_ids_ObjectId}, 'valide': '1', 'locked': '0',
|
||||
'partner_owner_recid': str(my_partner['recid'])},
|
||||
{"$set": mydata},
|
||||
|
||||
)
|
||||
|
||||
for session_id in tab_session_ids:
|
||||
# pour la collection 'myclass'
|
||||
now = str(datetime.now().strftime("%d/%m/%Y %H:%M:%S"))
|
||||
# print(" ####### laaaa diction = ", diction)
|
||||
|
||||
history_event_dict = {}
|
||||
history_event_dict['token'] = str(diction['token'])
|
||||
history_event_dict['related_collection'] = "session_formation"
|
||||
history_event_dict['related_collection_recid'] = str(session_id)
|
||||
history_event_dict['action_date'] = str(now)
|
||||
|
||||
history_event_dict['action_description'] = "Archivage "
|
||||
local_status, local_retval = mycommon.Collection_Historique.Add_Historique_Event(history_event_dict)
|
||||
if (local_status is False):
|
||||
mycommon.myprint(
|
||||
" WARNING : Impossible de logguer l'historique pour l'évènement : " + str(history_event_dict))
|
||||
|
||||
return True, " Promotion(s) archivée(s) "
|
||||
|
||||
|
||||
except Exception as e:
|
||||
|
||||
exc_type, exc_obj, exc_tb = sys.exc_info()
|
||||
|
||||
mycommon.myprint(str(inspect.stack()[0][3]) + " -" + str(e) + " - Line : " + str(exc_tb.tb_lineno))
|
||||
|
||||
return False, " Impossible d'archiver les sessions "
|
||||
|
||||
|
||||
"""
|
||||
02/06/2025 : De-Archivage d'une session
|
||||
"""
|
||||
def unarchive_session(diction):
|
||||
try:
|
||||
|
||||
'''
|
||||
# Verification que les champs reçus dans l'API sont bien dans la liste des champs autorisés
|
||||
# Cela evite le cas ou une entité tierce ajouter les valeurs inconnu dans l'API
|
||||
# Ici on doit mettre tous les champs possible (obligatoire ou non) de la BDD dans la liste
|
||||
# field_list.
|
||||
'''
|
||||
field_list = ['tab_session_id', 'token']
|
||||
incom_keys = diction.keys()
|
||||
for val in incom_keys:
|
||||
if val not in field_list and val.startswith('my_') is False:
|
||||
mycommon.myprint(
|
||||
str(inspect.stack()[0][3])+" - Le champ '" + val + "' n'existe pas, Creation formation annulée")
|
||||
return False, " Impossible de mettre à jour la formation"
|
||||
|
||||
'''
|
||||
Une fois qu'on a controlé que toutes les clés mise dans l'API sont correcte. etape precedente,
|
||||
On controle que les champs obligatoires sont presents dans la liste
|
||||
'''
|
||||
field_list_obligatoire = ['tab_session_id', 'token']
|
||||
|
||||
for val in field_list_obligatoire:
|
||||
if val not in diction:
|
||||
mycommon.myprint(str(inspect.stack()[0][3])+" - La valeur '" + val + "' n'est pas presente dans la liste des arguments ")
|
||||
return False, " Impossible de mettre à jour la formation"
|
||||
|
||||
# recuperation des paramettre
|
||||
mydata = {}
|
||||
my_internal_url = ""
|
||||
|
||||
if ("token" in diction.keys()):
|
||||
if diction['token']:
|
||||
mydata['token'] = diction['token']
|
||||
|
||||
# Verification de la validité du token
|
||||
tab_session_ids = []
|
||||
tab_session_ids_ObjectId = []
|
||||
if ("tab_session_id" in diction.keys()):
|
||||
tab_session_ids = str(diction['tab_session_id']).split(",")
|
||||
|
||||
for tmp in tab_session_ids:
|
||||
if (tmp):
|
||||
tab_session_ids_ObjectId.append(ObjectId(tmp))
|
||||
|
||||
# Verification de la validité du token
|
||||
local_status, my_partner = mycommon.Check_Connexion_And_Return_Partner_Data(diction)
|
||||
if (local_status is not True):
|
||||
return local_status, my_partner
|
||||
|
||||
mydata['date_update'] = str(datetime.now())
|
||||
mydata['update_by'] = str(my_partner['_id'])
|
||||
mydata['archive'] = '0'
|
||||
|
||||
|
||||
coll_name = MYSY_GV.dbname['session_formation']
|
||||
|
||||
# seules les formations avec locked = 0 et valide=1 sont modifiables
|
||||
ret_val = coll_name.update_many(
|
||||
{'_id': {'$in': tab_session_ids_ObjectId}, 'valide': '1', 'locked': '0',
|
||||
'partner_owner_recid': str(my_partner['recid'])},
|
||||
{"$set": mydata},
|
||||
|
||||
)
|
||||
|
||||
for session_id in tab_session_ids:
|
||||
# pour la collection 'myclass'
|
||||
now = str(datetime.now().strftime("%d/%m/%Y %H:%M:%S"))
|
||||
# print(" ####### laaaa diction = ", diction)
|
||||
|
||||
history_event_dict = {}
|
||||
history_event_dict['token'] = str(diction['token'])
|
||||
history_event_dict['related_collection'] = "session_formation"
|
||||
history_event_dict['related_collection_recid'] = str(session_id)
|
||||
history_event_dict['action_date'] = str(now)
|
||||
|
||||
history_event_dict['action_description'] = "Desarchivage "
|
||||
local_status, local_retval = mycommon.Collection_Historique.Add_Historique_Event(history_event_dict)
|
||||
if (local_status is False):
|
||||
mycommon.myprint(
|
||||
" WARNING : Impossible de logguer l'historique pour l'évènement : " + str(history_event_dict))
|
||||
|
||||
return True, " Promotion(s) desarchivée(s) "
|
||||
|
||||
|
||||
except Exception as e:
|
||||
|
||||
exc_type, exc_obj, exc_tb = sys.exc_info()
|
||||
|
||||
mycommon.myprint(str(inspect.stack()[0][3]) + " -" + str(e) + " - Line : " + str(exc_tb.tb_lineno))
|
||||
|
||||
return False, " Impossible d'archiver les sessions "
|
||||
|
||||
|
|
|
@ -3771,6 +3771,9 @@ def find_partner_class_like(diction):
|
|||
elif ("archive" in diction.keys() and (str(diction['archive']) == "0" or str(diction['archive']) == "") ):
|
||||
filt_archive = {'archive':{'$ne':'1'}}
|
||||
|
||||
elif( "archive" not in diction.keys()):
|
||||
filt_archive = {'archive': {'$ne': '1'}}
|
||||
|
||||
|
||||
filt_internal_url = {}
|
||||
if ("internal_url" in diction.keys()):
|
||||
|
@ -3781,7 +3784,7 @@ def find_partner_class_like(diction):
|
|||
{'partner_owner_recid': user_recid},
|
||||
filt_external_code, filt_title, filt_internal_url, filt_archive]}
|
||||
|
||||
#print(" ### find_partner_class_like_local_qry = ", find_partner_class_like_local_qry)
|
||||
print(" ### find_partner_class_like_local_qry = ", find_partner_class_like_local_qry)
|
||||
|
||||
"""
|
||||
Recuperation du nombre d'heure par jour depuis la confif du partner
|
||||
|
|
|
@ -152,7 +152,7 @@ def Add_Update_Question_Formulaire(diction):
|
|||
Verification des input acceptés
|
||||
"""
|
||||
field_list = ['token', 'form_id', 'question_id', 'question', 'type', 'commentaire', 'list_choix', 'max_note',
|
||||
'list_qcm', 'list_case_cocher', 'is_statistic' ]
|
||||
'list_qcm', 'list_case_cocher', 'is_statistic', 'is_alert', 'alert_seuil' ]
|
||||
|
||||
incom_keys = diction.keys()
|
||||
for val in incom_keys:
|
||||
|
@ -274,6 +274,16 @@ def Add_Update_Question_Formulaire(diction):
|
|||
else:
|
||||
new_data['is_statistic'] = "0"
|
||||
|
||||
if ("is_alert" in diction.keys() and str(diction['is_alert']).strip() in ['0', '1'] ):
|
||||
new_data['is_alert'] = str(diction['is_alert']).strip()
|
||||
else:
|
||||
new_data['is_alert'] = "0"
|
||||
|
||||
if ("alert_seuil" in diction.keys()):
|
||||
new_data['alert_seuil'] = str(diction['alert_seuil'])
|
||||
else:
|
||||
new_data['alert_seuil'] = ""
|
||||
|
||||
|
||||
tab_list_choix = []
|
||||
if ("list_choix" in diction.keys() and str(diction['type']) == "qcm"):
|
||||
|
|
27
main.py
27
main.py
|
@ -2296,6 +2296,33 @@ def Get_Given_SessionFormation_List_Automatic_Traitement_From():
|
|||
return jsonify(status=localStatus, message=message )
|
||||
|
||||
|
||||
"""
|
||||
API qui permet d'archiver une liste de session de formation
|
||||
"""
|
||||
@app.route('/myclass/api/archive_session/', methods=['POST','GET'])
|
||||
@crossdomain(origin='*')
|
||||
def archive_session():
|
||||
# On recupere le corps (payload) de la requete
|
||||
payload = mycommon.strip_dictionary (request.form.to_dict())
|
||||
print(" ### archive_session : payload = ",str(payload))
|
||||
localStatus, message= SF.archive_session(payload)
|
||||
return jsonify(status=localStatus, message=message )
|
||||
|
||||
|
||||
|
||||
"""
|
||||
API qui permet de déarchiver une liste de session de formation
|
||||
"""
|
||||
@app.route('/myclass/api/unarchive_session/', methods=['POST','GET'])
|
||||
@crossdomain(origin='*')
|
||||
def unarchive_session():
|
||||
# On recupere le corps (payload) de la requete
|
||||
payload = mycommon.strip_dictionary (request.form.to_dict())
|
||||
print(" ### unarchive_session : payload = ",str(payload))
|
||||
localStatus, message= SF.unarchive_session(payload)
|
||||
return jsonify(status=localStatus, message=message )
|
||||
|
||||
|
||||
|
||||
"""
|
||||
API de recuperation de toutes les sessions de formation valides et actives
|
||||
|
|
246
survey_mgt.py
246
survey_mgt.py
|
@ -303,6 +303,10 @@ def Record_Survey_Data_No_Token(diction):
|
|||
"""
|
||||
question_is_statistic = ""
|
||||
question_type = ""
|
||||
|
||||
question_is_alert = ""
|
||||
question_alert_seuil = ""
|
||||
|
||||
for local_question in form_list_question :
|
||||
if( str(local_question['_id']) == str(question_id) and "is_statistic" in local_question.keys() ):
|
||||
question_is_statistic = local_question['is_statistic']
|
||||
|
@ -310,15 +314,21 @@ def Record_Survey_Data_No_Token(diction):
|
|||
if (str(local_question['_id']) == str(question_id) and "type" in local_question.keys()):
|
||||
question_type = local_question['type']
|
||||
|
||||
if (str(local_question['_id']) == str(question_id) and "is_alert" in local_question.keys()):
|
||||
question_is_alert = local_question['is_alert']
|
||||
|
||||
if (str(local_question['_id']) == str(question_id) and "alert_seuil" in local_question.keys()):
|
||||
question_alert_seuil = local_question['alert_seuil']
|
||||
|
||||
node['is_statistic'] = question_is_statistic
|
||||
node['type'] = question_type
|
||||
|
||||
|
||||
node['is_alert'] = question_is_alert
|
||||
node['alert_seuil'] = question_alert_seuil
|
||||
|
||||
global_response.append(node)
|
||||
|
||||
|
||||
|
||||
"""
|
||||
On va enregister les reponse et changer le statut
|
||||
"""
|
||||
|
@ -339,6 +349,15 @@ def Record_Survey_Data_No_Token(diction):
|
|||
" Impossible de mettre à jour le formulaire (2) ")
|
||||
return False, " Impossible de mettre à jour le formulaire (2) "
|
||||
|
||||
local_diction = {}
|
||||
local_diction['global_response'] = global_response
|
||||
local_diction['class_id'] = str(survey_data['class_id'])
|
||||
local_diction['session_id'] = str(survey_data['session_id'])
|
||||
local_diction['survey_type'] = str(survey_data['_id'])
|
||||
local_diction['formulaire_id'] = str(survey_data['formulaire_id'])
|
||||
local_diction['partner_owner_recid'] = str(survey_data['partner_owner_recid'])
|
||||
|
||||
retval_status, retval = Survey_Send_Notification_Sur_Seuil_Alert(local_diction)
|
||||
|
||||
return True, " Le formulaire a été correctement mis à jour"
|
||||
|
||||
|
@ -349,13 +368,232 @@ def Record_Survey_Data_No_Token(diction):
|
|||
return False, " Impossible d'enregister les informations "
|
||||
|
||||
|
||||
"""
|
||||
Cette fonction permet de declencer l'envoie d'un email de notification si
|
||||
un seuil d'alert est atteint dans un questionnaire
|
||||
"""
|
||||
def Survey_Send_Notification_Sur_Seuil_Alert(diction):
|
||||
try:
|
||||
field_list_obligatoire = ['global_response', 'class_id', 'session_id', 'survey_type', 'formulaire_id', 'partner_owner_recid']
|
||||
|
||||
for val in field_list_obligatoire:
|
||||
if val not in diction:
|
||||
mycommon.myprint(
|
||||
str(inspect.stack()[0][
|
||||
3]) + " La valeur '" + val + "' n'est pas presente dans la liste des arguments ")
|
||||
return False, " Les informations fournies sont incorrectes"
|
||||
|
||||
local_status, my_partner = mycommon.Get_Connected_User_Partner_Data_From_RecID(
|
||||
str(diction['partner_owner_recid']))
|
||||
if (local_status is not True):
|
||||
return local_status, my_partner
|
||||
|
||||
"""
|
||||
Recuperation des données du formulaire
|
||||
"""
|
||||
|
||||
form_data_qry = {'valide': '1', 'locked': '0', '_id': ObjectId(str(diction['formulaire_id'])),
|
||||
'partner_owner_recid': str(diction['partner_owner_recid'])}
|
||||
|
||||
form_data = MYSY_GV.dbname['formulaire'].find_one(form_data_qry)
|
||||
if (form_data is None):
|
||||
mycommon.myprint(
|
||||
str(inspect.stack()[0][3]) + " L'identifiant du formulaire est invalide ")
|
||||
return False, " L'identifiant du formulaire est invalide "
|
||||
|
||||
|
||||
"""
|
||||
Recuperer les données questionnaire
|
||||
"""
|
||||
session_data_qry = {'valide': '1', 'locked': '0', '_id': ObjectId(str(diction['session_id'])),
|
||||
'partner_owner_recid': str(diction['partner_owner_recid'])}
|
||||
|
||||
session_data = MYSY_GV.dbname['session_formation'].find_one(session_data_qry)
|
||||
if (form_data is None):
|
||||
mycommon.myprint(
|
||||
str(inspect.stack()[0][3]) + " L'identifiant de la session est invalide ")
|
||||
return False, " L'identifiant de la session est invalide "
|
||||
|
||||
# Creation du dictionnaire d'information à utiliser pour la creation du doc
|
||||
convention_dictionnary_data = {}
|
||||
new_diction = {}
|
||||
new_diction['token'] = str(my_partner['token'])
|
||||
new_diction['list_stagiaire_id'] = []
|
||||
new_diction['list_session_id'] = [ObjectId(str(diction['session_id']))]
|
||||
new_diction['list_class_id'] = [ObjectId(str(diction['class_id']))]
|
||||
new_diction['list_client_id'] = []
|
||||
new_diction['list_apprenant_id'] = []
|
||||
|
||||
local_status, local_retval = mycommon.Get_Dictionnary_data_For_Template(new_diction)
|
||||
|
||||
if (local_status is False):
|
||||
return local_status, local_retval
|
||||
|
||||
convention_dictionnary_data = local_retval
|
||||
|
||||
survey_type = ""
|
||||
if( diction['survey_type'] == "pos"):
|
||||
survey_type = "Questionnaire de positionnement"
|
||||
|
||||
elif (diction['survey_type'] == "cold_eval"):
|
||||
survey_type = "Questionnaire d'évaluation à froid"
|
||||
|
||||
elif (diction['survey_type'] == "hot_eval"):
|
||||
survey_type = "Questionnaire d'évaluation à chaud"
|
||||
|
||||
elif (diction['survey_type'] == "human_eval"):
|
||||
survey_type = "Questionnaire d'évaluation personnel"
|
||||
|
||||
convention_dictionnary_data['survey_type'] = survey_type
|
||||
convention_dictionnary_data['partner_owner_recid'] = str(diction['partner_owner_recid'])
|
||||
|
||||
diction_notif_mail = convention_dictionnary_data
|
||||
|
||||
for val in diction['global_response']:
|
||||
|
||||
if("is_alert" in val.keys() and val['is_alert'] == "1" ):
|
||||
print(" ## on fait un controle")
|
||||
|
||||
if( val['type'] == "bool" ):
|
||||
seuil = val['alert_seuil']
|
||||
valeur = ""
|
||||
if( val['response'] == "oui"):
|
||||
valeur = "1"
|
||||
else:
|
||||
valeur = "0"
|
||||
|
||||
if( seuil == valeur):
|
||||
print(" ### DECLENCHEMENT MESSAGE D'ALERT")
|
||||
convention_dictionnary_data['user_question_reponse'] = val
|
||||
notif_status, notif_retval = Send_Survey_Alert_Notif(convention_dictionnary_data)
|
||||
|
||||
elif( val['type'] == "note" ):
|
||||
seuil = mycommon.tryFloat(val['alert_seuil'])
|
||||
valeur = mycommon.tryFloat(val['response'])
|
||||
|
||||
if( valeur <= seuil):
|
||||
print(" ### DECLENCHEMENT MESSAGE D'ALERT")
|
||||
convention_dictionnary_data['user_question_reponse'] = val
|
||||
notif_status, notif_retval = Send_Survey_Alert_Notif(convention_dictionnary_data)
|
||||
|
||||
|
||||
return True, " Traitement Survey_Send_Notification_Sur_Seuil_Alert"
|
||||
|
||||
|
||||
except Exception as e:
|
||||
exc_type, exc_obj, exc_tb = sys.exc_info()
|
||||
mycommon.myprint(str(inspect.stack()[0][3]) + " -" + str(e) + " - Line : " + str(exc_tb.tb_lineno))
|
||||
return False, " Impossible de créer le formulaire "
|
||||
|
||||
|
||||
"""
|
||||
Cette fonction envoi l'email de notification en cas d'alert sur
|
||||
seuil de réponse
|
||||
"""
|
||||
def Send_Survey_Alert_Notif(diction):
|
||||
try:
|
||||
field_list_obligatoire = [ 'user_question_reponse', 'partner_owner_recid']
|
||||
|
||||
print(" ### Send_Survey_Alert_Notif diction = ", diction)
|
||||
|
||||
for val in field_list_obligatoire:
|
||||
if val not in diction:
|
||||
mycommon.myprint(
|
||||
str(inspect.stack()[0][
|
||||
3]) + " La valeur '" + val + "' n'est pas presente dans la liste des arguments ")
|
||||
return False, " Les informations fournies sont incorrectes"
|
||||
|
||||
body = {
|
||||
"params": diction,
|
||||
}
|
||||
|
||||
"""
|
||||
Recuperer le modèle de document
|
||||
"""
|
||||
local_diction = {}
|
||||
local_diction['ref_interne'] = "SURVEY_RESPONSE_NOTIF"
|
||||
local_diction['type_doc'] = "email"
|
||||
local_diction['partner_owner_recid'] = str(diction['partner_owner_recid'])
|
||||
|
||||
courrier_data_status, courrier_data_retval = mycommon.Get_Courrier_Template_Include_Default_Data(local_diction)
|
||||
if (courrier_data_status is False):
|
||||
return courrier_data_status, courrier_data_retval
|
||||
|
||||
|
||||
if ("corps_mail" not in courrier_data_retval.keys() or str(courrier_data_retval['corps_mail']) == ""):
|
||||
mycommon.myprint(
|
||||
str(inspect.stack()[0][
|
||||
3]) + " Le modèle de courrier 'SURVEY_RESPONSE_NOTIF' n'est pas correctement configuré - corps_mail ")
|
||||
return False, " Le modèle de courrier 'SURVEY_RESPONSE_NOTIF' n'est pas correctement configuré - corps_mail "
|
||||
|
||||
|
||||
# Recuperation des donnes smtp
|
||||
(local_stpm_status, partner_SMTP_COUNT_smtpsrv, partner_own_smtp_value, partner_SMTP_COUNT_password,
|
||||
partner_SMTP_COUNT_user, partner_SMTP_COUNT_From_User, partner_SMTP_COUNT_port) = mycommon.Get_Partner_SMTP_Param(str(diction['partner_owner_recid']))
|
||||
|
||||
if (local_stpm_status is False):
|
||||
return local_stpm_status, partner_own_smtp_value
|
||||
|
||||
if (str(partner_own_smtp_value) == "1"):
|
||||
smtpserver = smtplib.SMTP(partner_SMTP_COUNT_smtpsrv, partner_SMTP_COUNT_port)
|
||||
else:
|
||||
smtpserver = smtplib.SMTP(MYSY_GV.O365_SMTP_COUNT_smtpsrv, MYSY_GV.O365_SMTP_COUNT_port)
|
||||
|
||||
smtpserver.ehlo()
|
||||
smtpserver.starttls()
|
||||
smtpserver.login(MYSY_GV.O365_SMTP_COUNT_user, MYSY_GV.O365_SMTP_COUNT_password)
|
||||
|
||||
# Traitement du sujet du mail
|
||||
sujet_mail_Template = jinja2.Template(str(courrier_data_retval['sujet']))
|
||||
sujetHtml = sujet_mail_Template.render(params=body["params"])
|
||||
|
||||
# Traitement du corps du mail
|
||||
contenu_corp_mail_Template = jinja2.Template(str(courrier_data_retval['corps_mail']))
|
||||
sourceHtml_corps_mail = contenu_corp_mail_Template.render(params=body["params"])
|
||||
html_mime = MIMEText(sourceHtml_corps_mail, 'html')
|
||||
|
||||
email_to = ['cbalde@mysy-training.com']
|
||||
# Creation de l'email à enoyer
|
||||
msg = MIMEMultipart("alternative")
|
||||
if (str(partner_own_smtp_value) == "1"):
|
||||
msg.attach(html_mime)
|
||||
msg['From'] = partner_SMTP_COUNT_From_User
|
||||
msg['Bcc'] = 'contact@mysy-training.com'
|
||||
msg['Subject'] = sujetHtml
|
||||
|
||||
toaddrs = ",".join(email_to)
|
||||
msg['to'] = str(toaddrs)
|
||||
|
||||
val = smtpserver.send_message(msg)
|
||||
print(" Email de notification envoyé " + str(val))
|
||||
|
||||
else:
|
||||
msg.attach(html_mime)
|
||||
msg['From'] = MYSY_GV.O365_SMTP_COUNT_From_User
|
||||
msg['Bcc'] = 'contact@mysy-training.com'
|
||||
msg['Subject'] = sujetHtml
|
||||
|
||||
toaddrs = ",".join(email_to)
|
||||
msg['to'] = str(toaddrs)
|
||||
val = smtpserver.send_message(msg)
|
||||
print(" Email de notification envoyé " + str(val))
|
||||
|
||||
smtpserver.close()
|
||||
|
||||
|
||||
return True, " Send_Survey_Alert_Notif "
|
||||
|
||||
|
||||
except Exception as e:
|
||||
exc_type, exc_obj, exc_tb = sys.exc_info()
|
||||
mycommon.myprint(str(inspect.stack()[0][3]) + " -" + str(e) + " - Line : " + str(exc_tb.tb_lineno))
|
||||
return False, " Impossible de créer le formulaire "
|
||||
|
||||
|
||||
"""
|
||||
Survey for enduser : Cette fonction créer une enquete en prenant à partir d'une liste d'inscrit
|
||||
|
||||
/!\ : Cette fonction ne concerne uniquement et exclusibement les inscriptions validées (status = 1)
|
||||
|
||||
|
||||
"""
|
||||
|
||||
def Add_Survey_Tab_Inscrit(diction):
|
||||
|
|
Loading…
Reference in New Issue