Manu 1 2 4 5 7 ok
This commit is contained in:
+414
-18
@@ -1,7 +1,6 @@
|
||||
from flask import Blueprint, render_template, request, redirect, url_for, session, flash, Response, session
|
||||
from flask import Blueprint, render_template, request, redirect, url_for, session, flash, Response, jsonify
|
||||
import time
|
||||
import requests
|
||||
import mysql.connector
|
||||
import csv
|
||||
import io
|
||||
from werkzeug.security import check_password_hash, generate_password_hash
|
||||
@@ -129,7 +128,6 @@ def gestion_utilisateurs():
|
||||
|
||||
return redirect(url_for('main.gestion_utilisateurs'))
|
||||
|
||||
# Lecture exacte de la table login de votre base de données
|
||||
users_query = text("SELECT id, login, nom, prenom, mail, actif FROM login ORDER BY id ASC")
|
||||
result = db.session.execute(users_query)
|
||||
|
||||
@@ -146,7 +144,6 @@ def gestion_utilisateurs():
|
||||
|
||||
return render_template('gestion_utilisateurs.html', users=users)
|
||||
|
||||
|
||||
# Route pour la gestion des actions
|
||||
@main.route('/gestion-actions', methods=['GET', 'POST'])
|
||||
def gestion_actions():
|
||||
@@ -199,7 +196,6 @@ def gestion_actions():
|
||||
|
||||
return redirect(url_for('main.gestion_actions'))
|
||||
|
||||
# Récupération avec la nouvelle structure
|
||||
actions_query = text("SELECT id, isin, ticker, company_name, exchange, pays, currency, updated_at FROM actions ORDER BY company_name ASC")
|
||||
result = db.session.execute(actions_query)
|
||||
|
||||
@@ -218,7 +214,6 @@ def gestion_actions():
|
||||
|
||||
return render_template('gestion_actions.html', actions=actions_list)
|
||||
|
||||
|
||||
# Route pour la gestion des sociétés
|
||||
@main.route('/gestion-societes', methods=['GET', 'POST'])
|
||||
def gestion_societes():
|
||||
@@ -274,7 +269,6 @@ def gestion_societes():
|
||||
|
||||
return redirect(url_for('main.gestion_societes'))
|
||||
|
||||
# Lecture de la table societe
|
||||
societes_query = text("SELECT id, Nom, Forme, Capital, siren, siret, RCS, TVA, NAF, Tel, Web, Mail FROM societe ORDER BY id ASC")
|
||||
result = db.session.execute(societes_query)
|
||||
|
||||
@@ -297,8 +291,7 @@ def gestion_societes():
|
||||
|
||||
return render_template('gestion_societes.html', societes=societes)
|
||||
|
||||
|
||||
# Routes pour la gestion des imports exports des actions csv
|
||||
# Routes pour la gestion des imports/exports des actions CSV
|
||||
@main.route('/gestion-import-export-actions-csv', methods=['GET'])
|
||||
def gestion_import_export_actions_csv():
|
||||
if 'user_id' not in session:
|
||||
@@ -311,18 +304,14 @@ def export_actions_csv():
|
||||
return redirect(url_for('main.index'))
|
||||
|
||||
try:
|
||||
# Récupération de toutes les données de la table actions
|
||||
query = text("SELECT id, isin, ticker, company_name, exchange, pays, currency, updated_at FROM actions")
|
||||
result = db.session.execute(query)
|
||||
|
||||
# Création du fichier CSV en mémoire
|
||||
output = io.StringIO()
|
||||
writer = csv.writer(output, delimiter=';', quoting=csv.QUOTE_MINIMAL)
|
||||
|
||||
# Écriture de l'en-tête du CSV
|
||||
writer.writerow(['id', 'isin', 'ticker', 'company_name', 'exchange', 'pays', 'currency', 'updated_at'])
|
||||
|
||||
# Écriture des lignes
|
||||
for row in result:
|
||||
writer.writerow([
|
||||
row.id,
|
||||
@@ -337,7 +326,6 @@ def export_actions_csv():
|
||||
|
||||
output.seek(0)
|
||||
|
||||
# Envoi du fichier en téléchargement
|
||||
return Response(
|
||||
output.getvalue(),
|
||||
mimetype="text/csv",
|
||||
@@ -374,7 +362,6 @@ def import_actions_csv():
|
||||
|
||||
count = 0
|
||||
for row in csv_reader:
|
||||
# Vérifie qu'on a au moins les 6 colonnes de base
|
||||
if len(row) >= 6:
|
||||
isin = row[0].strip() if row[0] != '' else None
|
||||
ticker = row[1].strip() if row[1] != '' else None
|
||||
@@ -384,9 +371,8 @@ def import_actions_csv():
|
||||
currency = row[5].strip() if row[5] != '' else ''
|
||||
|
||||
if not isin or not ticker:
|
||||
continue # Ignore les lignes mal formatées
|
||||
continue
|
||||
|
||||
# Insertion ou mise à jour si le code ISIN existe déjà
|
||||
query = text("""
|
||||
INSERT INTO actions (isin, ticker, company_name, exchange, pays, currency, updated_at)
|
||||
VALUES (:isin, :ticker, :company_name, :exchange, :pays, :currency, NOW())
|
||||
@@ -409,4 +395,414 @@ def import_actions_csv():
|
||||
return redirect(url_for('main.gestion_import_export_actions_csv'))
|
||||
else:
|
||||
flash("Veuillez fournir un fichier au format .csv valide.", "warning")
|
||||
return redirect(url_for('main.gestion_import_export_actions_csv'))
|
||||
return redirect(url_for('main.gestion_import_export_actions_csv'))
|
||||
|
||||
|
||||
# Route pour la gestion des ordres
|
||||
@main.route("/gestion_ordres", methods=["GET", "POST"])
|
||||
def gestion_ordres():
|
||||
if "user_id" not in session:
|
||||
return redirect(url_for("main.index"))
|
||||
|
||||
selected_societe_id = (
|
||||
request.form.get("societe_id")
|
||||
or request.args.get("societe_id")
|
||||
or session.get("selected_societe_id")
|
||||
)
|
||||
|
||||
societes = []
|
||||
selected_societe = ""
|
||||
groupes_dict = {}
|
||||
|
||||
with db.engine.connect() as connection:
|
||||
result_soc = connection.execute(
|
||||
text("SELECT id, Nom FROM societe ORDER BY Nom ASC")
|
||||
)
|
||||
societes = [dict(row._mapping) for row in result_soc]
|
||||
|
||||
if societes:
|
||||
if not selected_societe_id:
|
||||
selected_societe_id = societes[0]["id"]
|
||||
else:
|
||||
selected_societe_id = int(selected_societe_id)
|
||||
|
||||
session["selected_societe_id"] = selected_societe_id
|
||||
|
||||
for soc in societes:
|
||||
if soc["id"] == selected_societe_id:
|
||||
selected_societe = soc["Nom"]
|
||||
break
|
||||
|
||||
query = text("""
|
||||
SELECT o.ID as ordre_id, o.isin, o.PlusValue, o.date_plusvalue,
|
||||
op.ID as pied_id, op.Date_op, op.Quantite, op.Prix_brut, op.Frais_brocker,
|
||||
op.Frais_etat, op.Frais_autre, op.Ordre, op.Type, op.Etat,
|
||||
a.ticker, a.company_name, a.currency
|
||||
FROM ordre o
|
||||
JOIN ordre_pied op ON o.ID = op.Id_entete
|
||||
JOIN actions a ON o.isin = a.isin
|
||||
WHERE o.id_societe = :soc_id
|
||||
ORDER BY op.Date_op DESC
|
||||
""")
|
||||
|
||||
result_ordres = connection.execute(
|
||||
query, {"soc_id": selected_societe_id}
|
||||
)
|
||||
|
||||
for row in result_ordres:
|
||||
r = dict(row._mapping)
|
||||
isin = r["isin"]
|
||||
|
||||
quantite = float(r["Quantite"]) if r["Quantite"] is not None else 0.0
|
||||
prix_brut = float(r["Prix_brut"]) if r["Prix_brut"] is not None else 0.0
|
||||
frais_brocker = (
|
||||
float(r["Frais_brocker"]) if r["Frais_brocker"] is not None else 0.0
|
||||
)
|
||||
frais_etat = (
|
||||
float(r["Frais_etat"]) if r["Frais_etat"] is not None else 0.0
|
||||
)
|
||||
frais_autre = (
|
||||
float(r["Frais_autre"]) if r["Frais_autre"] is not None else 0.0
|
||||
)
|
||||
|
||||
frais_total = frais_brocker + frais_etat + frais_autre
|
||||
sens_ordre = str(r["Ordre"]).strip().lower() # 'achat' ou 'vente'
|
||||
type_ordre = (
|
||||
str(r["Type"]).strip().lower()
|
||||
if r["Type"]
|
||||
else "long" # 'long' ou 'short'
|
||||
)
|
||||
|
||||
# Calcul Prix Net unitaire
|
||||
if sens_ordre == "vente":
|
||||
prix_net = prix_brut - frais_total
|
||||
else:
|
||||
prix_net = prix_brut + frais_total
|
||||
|
||||
# Calculs Ligne (Pied)
|
||||
eng_brut = quantite * prix_brut
|
||||
eng_net = quantite * prix_net
|
||||
|
||||
pied_data = {
|
||||
"id": r["pied_id"],
|
||||
"date_op": r["Date_op"],
|
||||
"quantite": quantite,
|
||||
"prix_brut": prix_brut,
|
||||
"frais": frais_total,
|
||||
"frais_brocker": frais_brocker,
|
||||
"frais_etat": frais_etat,
|
||||
"frais_autre": frais_autre,
|
||||
"prix_net": prix_net,
|
||||
"eng_brut": eng_brut,
|
||||
"eng_net": eng_net,
|
||||
"ordre": r["Ordre"],
|
||||
"type_ordre": r["Type"],
|
||||
"etat": r["Etat"],
|
||||
}
|
||||
|
||||
if isin not in groupes_dict:
|
||||
groupes_dict[isin] = {
|
||||
"id": r["ordre_id"],
|
||||
"isin": isin,
|
||||
"company_name": r["company_name"],
|
||||
"ticker": r["ticker"],
|
||||
"pieds": [],
|
||||
"total_quantite": 0.0,
|
||||
"total_prix_brut": 0.0,
|
||||
"total_frais": 0.0,
|
||||
"total_prix_net": 0.0,
|
||||
"total_eng_brut": 0.0,
|
||||
"total_eng_net": 0.0,
|
||||
}
|
||||
|
||||
groupes_dict[isin]["pieds"].append(pied_data)
|
||||
groupes_dict[isin]["total_prix_brut"] += prix_brut
|
||||
groupes_dict[isin]["total_frais"] += frais_total
|
||||
groupes_dict[isin]["total_prix_net"] += prix_net
|
||||
|
||||
# --- APPLICATION DE VOS RÈGLES POUR LES TOTAUX ---
|
||||
|
||||
# 1. QUANTITÉ
|
||||
if sens_ordre == "achat" and type_ordre == "long":
|
||||
groupes_dict[isin]["total_quantite"] += quantite
|
||||
elif sens_ordre == "vente" and type_ordre == "long":
|
||||
groupes_dict[isin]["total_quantite"] -= quantite
|
||||
elif sens_ordre == "achat" and type_ordre == "short":
|
||||
groupes_dict[isin]["total_quantite"] -= quantite
|
||||
elif sens_ordre == "vente" and type_ordre == "short":
|
||||
groupes_dict[isin]["total_quantite"] += quantite
|
||||
|
||||
# 2. ENGAGEMENT BRUT
|
||||
if sens_ordre == "achat" and type_ordre == "long":
|
||||
groupes_dict[isin]["total_eng_brut"] += eng_brut
|
||||
elif sens_ordre == "vente" and type_ordre == "long":
|
||||
groupes_dict[isin]["total_eng_brut"] -= eng_brut
|
||||
elif sens_ordre == "achat" and type_ordre == "short":
|
||||
groupes_dict[isin]["total_eng_brut"] -= eng_brut
|
||||
elif sens_ordre == "vente" and type_ordre == "short":
|
||||
groupes_dict[isin]["total_eng_brut"] += eng_brut
|
||||
|
||||
# 3. ENGAGEMENT NET
|
||||
if sens_ordre == "achat" and type_ordre == "long":
|
||||
groupes_dict[isin]["total_eng_net"] += eng_net
|
||||
elif sens_ordre == "vente" and type_ordre == "long":
|
||||
groupes_dict[isin]["total_eng_net"] -= eng_net
|
||||
elif sens_ordre == "achat" and type_ordre == "short":
|
||||
groupes_dict[isin]["total_eng_net"] -= eng_net
|
||||
elif sens_ordre == "vente" and type_ordre == "short":
|
||||
groupes_dict[isin]["total_eng_net"] += eng_net
|
||||
|
||||
# --- VÉRIFICATION ET MISE À JOUR AUTOMATIQUE (EN BASE ET EN MÉMOIRE) ---
|
||||
try:
|
||||
with db.engine.begin() as conn:
|
||||
for isin, groupe in groupes_dict.items():
|
||||
# Utilisation d'une tolérance de décimales (ex: 1e-9) pour éviter les erreurs d'arrondi flottant
|
||||
if abs(groupe["total_quantite"]) < 1e-9:
|
||||
ordre_id = groupe["id"]
|
||||
|
||||
# 1. Sauvegarde en base de données
|
||||
conn.execute(
|
||||
text(
|
||||
"UPDATE ordre_pied SET Etat = :nouvel_etat WHERE Id_entete ="
|
||||
" :ordre_id"
|
||||
),
|
||||
{"nouvel_etat": "soldé", "ordre_id": ordre_id},
|
||||
)
|
||||
|
||||
# 2. Mise à jour immédiate en mémoire pour l'affichage dans le template
|
||||
for pied in groupe["pieds"]:
|
||||
pied["etat"] = "soldé"
|
||||
|
||||
except Exception as e:
|
||||
# En cas d'erreur, SQLAlchemy gère le rollback automatique avec le bloc 'with connection.begin()'
|
||||
pass
|
||||
|
||||
groupes_ordres = list(groupes_dict.values())
|
||||
|
||||
return render_template(
|
||||
"gestion_ordres.html",
|
||||
societes=societes,
|
||||
selected_societe_id=selected_societe_id,
|
||||
selected_societe=selected_societe,
|
||||
groupes_ordres=groupes_ordres,
|
||||
)
|
||||
|
||||
|
||||
# Route pour la creation d'un ordre
|
||||
@main.route("/creer-ordre", methods=["GET", "POST"])
|
||||
def creer_ordre():
|
||||
if "user_id" not in session:
|
||||
return redirect(url_for("main.index"))
|
||||
return render_template("creer_ordre.html")
|
||||
|
||||
|
||||
# Route pour vérifier l'existence d'un ISIN
|
||||
@main.route("/api/verifier_isin", methods=["POST"])
|
||||
def verifier_isin():
|
||||
data = request.get_json()
|
||||
isin = data.get("isin", "").strip()
|
||||
|
||||
query = text(
|
||||
"SELECT company_name, ticker, currency FROM actions WHERE isin = :isin"
|
||||
)
|
||||
action = db.session.execute(query, {"isin": isin}).fetchone()
|
||||
|
||||
if action:
|
||||
return jsonify({
|
||||
"exists": True,
|
||||
"company_name": action.company_name,
|
||||
"ticker": action.ticker,
|
||||
"currency": action.currency,
|
||||
})
|
||||
else:
|
||||
return jsonify({"exists": False})
|
||||
|
||||
|
||||
# Route pour la création d'un ordre de traitement
|
||||
@main.route("/creer-ordre-traitement", methods=["POST"])
|
||||
def creer_ordre_traitement():
|
||||
if "user_id" not in session:
|
||||
return redirect(url_for("main.index"))
|
||||
|
||||
isin = request.form.get("isin")
|
||||
date_op = request.form.get("date_op")
|
||||
quantite = float(request.form.get("quantite", 0))
|
||||
prix_brut = float(request.form.get("prix_brut", 0))
|
||||
|
||||
# Récupération des trois types de frais avec les bons noms d'inputs
|
||||
frais_brocker = float(request.form.get("frais_brocker", 0))
|
||||
frais_etat = float(request.form.get("frais_etat", 0))
|
||||
frais_autre = float(request.form.get("frais_autre", 0))
|
||||
|
||||
ordre = request.form.get("ordre")
|
||||
type_ordre = request.form.get("type_ordre")
|
||||
etat = request.form.get("etat")
|
||||
|
||||
# Récupération de l'id_societe courant stocké dans la session
|
||||
id_societe = session.get("selected_societe_id")
|
||||
|
||||
if not id_societe:
|
||||
flash(
|
||||
"Veuillez sélectionner une société active avant de créer un ordre.",
|
||||
"danger",
|
||||
)
|
||||
return redirect(url_for("main.gestion_ordres"))
|
||||
|
||||
try:
|
||||
# 1. Insertion dans la table 'ordre' avec l'isin et l'id_societe
|
||||
query_ordre = text(
|
||||
"INSERT INTO ordre (id_societe, isin) VALUES (:id_societe, :isin)"
|
||||
)
|
||||
db.session.execute(query_ordre, {"id_societe": id_societe, "isin": isin})
|
||||
|
||||
# Récupérer l'ID généré pour l'en-tête (Id_entete)
|
||||
result_id = db.session.execute(
|
||||
text("SELECT LAST_INSERT_ID()")
|
||||
).scalar()
|
||||
if not result_id:
|
||||
result_id = db.session.execute(
|
||||
text(
|
||||
"SELECT ID FROM ordre WHERE isin = :isin ORDER BY ID DESC LIMIT"
|
||||
" 1"
|
||||
),
|
||||
{"isin": isin},
|
||||
).fetchone()[0]
|
||||
|
||||
# 2. Insertion dans la table 'ordre_pied' avec les 3 frais distincts
|
||||
query_pied = text("""
|
||||
INSERT INTO ordre_pied (Id_entete, Date_op, Quantite, Prix_brut, Frais_brocker, Frais_etat, Frais_autre, Ordre, Type, Etat)
|
||||
VALUES (:id_entete, :date_op, :quantite, :prix_brut, :frais_brocker, :frais_etat, :frais_autre, :ordre, :type_ordre, :etat)
|
||||
""")
|
||||
db.session.execute(
|
||||
query_pied,
|
||||
{
|
||||
"id_entete": result_id,
|
||||
"date_op": date_op,
|
||||
"quantite": quantite,
|
||||
"prix_brut": prix_brut,
|
||||
"frais_brocker": frais_brocker,
|
||||
"frais_etat": frais_etat,
|
||||
"frais_autre": frais_autre,
|
||||
"ordre": ordre,
|
||||
"type_ordre": type_ordre,
|
||||
"etat": etat,
|
||||
},
|
||||
)
|
||||
|
||||
db.session.commit()
|
||||
flash("Ordre créé avec succès !", "success")
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
flash(f"Erreur lors de la création de l'ordre : {e}", "danger")
|
||||
|
||||
return redirect(url_for("main.gestion_ordres"))
|
||||
|
||||
|
||||
# Route pour créer une nouvelle ligne rattachée à un ordre existant (depuis le bouton "Créer")
|
||||
@main.route("/ordre/<int:ordre_id>/ajouter-ligne", methods=["GET", "POST"])
|
||||
def ajouter_ligne_ordre(ordre_id):
|
||||
if "user_id" not in session:
|
||||
return redirect(url_for("main.index"))
|
||||
|
||||
# Récupérer l'en-tête de l'ordre, l'ISIN et le nom de la société associée
|
||||
query_info = text("""
|
||||
SELECT o.ID, o.isin, a.company_name
|
||||
FROM ordre o
|
||||
JOIN actions a ON o.isin = a.isin
|
||||
WHERE o.ID = :ordre_id
|
||||
""")
|
||||
ordre_info = db.session.execute(query_info, {"ordre_id": ordre_id}).fetchone()
|
||||
|
||||
if not ordre_info:
|
||||
flash("Ordre introuvable.", "danger")
|
||||
return redirect(url_for("main.gestion_ordres"))
|
||||
|
||||
if request.method == "POST":
|
||||
try:
|
||||
query_pied = text("""
|
||||
INSERT INTO ordre_pied (Id_entete, Date_op, Quantite, Prix_brut, Frais_brocker, Frais_etat, Frais_autre, Ordre, Type, Etat)
|
||||
VALUES (:id_entete, :date_op, :quantite, :prix_brut, :frais_brocker, :frais_etat, :frais_autre, :ordre, :type_ordre, :etat)
|
||||
""")
|
||||
db.session.execute(query_pied, {
|
||||
"id_entete": ordre_id,
|
||||
"date_op": request.form.get("date_op"),
|
||||
"quantite": float(request.form.get("quantite", 0)),
|
||||
"prix_brut": float(request.form.get("prix_brut", 0)),
|
||||
"frais_brocker": float(request.form.get("frais_brocker", 0)),
|
||||
"frais_etat": float(request.form.get("frais_etat", 0)),
|
||||
"frais_autre": float(request.form.get("frais_autre", 0)),
|
||||
"ordre": request.form.get("ordre"),
|
||||
"type_ordre": request.form.get("type_ordre"),
|
||||
"etat": request.form.get("etat"),
|
||||
})
|
||||
db.session.commit()
|
||||
flash("Ligne ajoutée avec succès à l'ordre !", "success")
|
||||
return redirect(url_for("main.gestion_ordres"))
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
flash(f"Erreur lors de l'ajout : {e}", "danger")
|
||||
|
||||
contexte_form = {
|
||||
"mode": "creer_ligne",
|
||||
"isin": ordre_info.isin,
|
||||
"company_name": ordre_info.company_name,
|
||||
"action_url": url_for("main.ajouter_ligne_ordre", ordre_id=ordre_id)
|
||||
}
|
||||
return render_template("form_ordre_ligne.html", data=contexte_form, ligne=None)
|
||||
|
||||
|
||||
# Route pour modifier une ligne spécifique (depuis le bouton "Modifier" de la ligne)
|
||||
@main.route("/modifier-ligne-ordre/<int:pied_id>", methods=["GET", "POST"])
|
||||
def modifier_ligne_ordre(pied_id):
|
||||
if "user_id" not in session:
|
||||
return redirect(url_for("main.index"))
|
||||
|
||||
# Récupérer la ligne du pied, l'ISIN et le nom de l'entreprise
|
||||
query_get = text("""
|
||||
SELECT op.*, o.isin, a.company_name
|
||||
FROM ordre_pied op
|
||||
JOIN ordre o ON op.Id_entete = o.ID
|
||||
JOIN actions a ON o.isin = a.isin
|
||||
WHERE op.ID = :pied_id
|
||||
""")
|
||||
ligne = db.session.execute(query_get, {"pied_id": pied_id}).fetchone()
|
||||
|
||||
if not ligne:
|
||||
flash("Ligne d'ordre introuvable.", "danger")
|
||||
return redirect(url_for("main.gestion_ordres"))
|
||||
|
||||
if request.method == "POST":
|
||||
try:
|
||||
query_update = text("""
|
||||
UPDATE ordre_pied
|
||||
SET Date_op = :date_op, Quantite = :quantite, Prix_brut = :prix_brut,
|
||||
Frais_brocker = :frais_brocker, Frais_etat = :frais_etat, Frais_autre = :frais_autre,
|
||||
Ordre = :ordre, Type = :type_ordre, Etat = :etat
|
||||
WHERE ID = :pied_id
|
||||
""")
|
||||
db.session.execute(query_update, {
|
||||
"date_op": request.form.get("date_op"),
|
||||
"quantite": float(request.form.get("quantite", 0)),
|
||||
"prix_brut": float(request.form.get("prix_brut", 0)),
|
||||
"frais_brocker": float(request.form.get("frais_brocker", 0)),
|
||||
"frais_etat": float(request.form.get("frais_etat", 0)),
|
||||
"frais_autre": float(request.form.get("frais_autre", 0)),
|
||||
"ordre": request.form.get("ordre"),
|
||||
"type_ordre": request.form.get("type_ordre"),
|
||||
"etat": request.form.get("etat"),
|
||||
"pied_id": pied_id
|
||||
})
|
||||
db.session.commit()
|
||||
flash("Ligne modifiée avec succès !", "success")
|
||||
return redirect(url_for("main.gestion_ordres"))
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
flash(f"Erreur lors de la modification : {e}", "danger")
|
||||
|
||||
contexte_form = {
|
||||
"mode": "modifier_ligne",
|
||||
"isin": ligne.isin,
|
||||
"company_name": ligne.company_name,
|
||||
"action_url": url_for("main.modifier_ligne_ordre", pied_id=pied_id)
|
||||
}
|
||||
return render_template("form_ordre_ligne.html", data=contexte_form, ligne=ligne)
|
||||
Reference in New Issue
Block a user