Refactor routes Flask en Blueprints
This commit is contained in:
@@ -0,0 +1,476 @@
|
||||
from flask import (
|
||||
Blueprint,
|
||||
current_app,
|
||||
flash,
|
||||
jsonify,
|
||||
redirect,
|
||||
render_template,
|
||||
request,
|
||||
session,
|
||||
url_for,
|
||||
)
|
||||
from sqlalchemy import text
|
||||
|
||||
from app import csrf, db
|
||||
from app.decorators import login_required
|
||||
|
||||
ordres_bp = Blueprint("ordres", __name__)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Gestion des ordres
|
||||
# ---------------------------------------------------------------------------
|
||||
@ordres_bp.route("/gestion_ordres", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def gestion_ordres():
|
||||
selected_societe_id = (
|
||||
request.form.get("societe_id")
|
||||
or request.args.get("societe_id")
|
||||
or session.get("selected_societe_id")
|
||||
)
|
||||
|
||||
selected_annee = (
|
||||
request.form.get("annee")
|
||||
or request.args.get("annee")
|
||||
or session.get("selected_annee", "Tout")
|
||||
)
|
||||
session["selected_annee"] = selected_annee
|
||||
|
||||
societes = []
|
||||
selected_societe = ""
|
||||
groupes_dict = {}
|
||||
total_plus_value_globale = 0.0
|
||||
|
||||
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:
|
||||
try:
|
||||
selected_societe_id = int(selected_societe_id)
|
||||
except (TypeError, ValueError):
|
||||
selected_societe_id = societes[0]["id"]
|
||||
|
||||
session["selected_societe_id"] = selected_societe_id
|
||||
|
||||
for soc in societes:
|
||||
if soc["id"] == selected_societe_id:
|
||||
selected_societe = soc["nom"]
|
||||
break
|
||||
|
||||
# --- CALCUL DE LA PLUS-VALUE TOTALE ---
|
||||
annee_str = str(selected_annee).strip().lower()
|
||||
|
||||
if not annee_str or annee_str == "tout":
|
||||
query_pv = text(
|
||||
"SELECT SUM(plus_value) AS total_pv FROM ordre "
|
||||
"WHERE id_societe = :soc_id AND plus_value IS NOT NULL"
|
||||
)
|
||||
res_pv = connection.execute(
|
||||
query_pv, {"soc_id": selected_societe_id}
|
||||
).fetchone()
|
||||
else:
|
||||
query_pv = text(
|
||||
"SELECT SUM(plus_value) AS total_pv FROM ordre "
|
||||
"WHERE id_societe = :soc_id AND plus_value IS NOT NULL "
|
||||
"AND LEFT(CAST(date_plus_value AS CHAR), 4) = :annee"
|
||||
)
|
||||
res_pv = connection.execute(
|
||||
query_pv, {"soc_id": selected_societe_id, "annee": annee_str}
|
||||
).fetchone()
|
||||
|
||||
if res_pv and res_pv.total_pv is not None:
|
||||
total_plus_value_globale = float(res_pv.total_pv)
|
||||
|
||||
# --- CHARGEMENT DES ORDRES ---
|
||||
query = text(
|
||||
"SELECT o.id AS ordre_id, o.isin, o.plus_value, o.date_plus_value, "
|
||||
"op.id AS pied_id, op.date_op, op.quantite, op.prix_brut, "
|
||||
"op.frais_broker, op.frais_etat, op.frais_autre, "
|
||||
"op.sens, op.position, 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 o.id DESC, op.date_op DESC"
|
||||
)
|
||||
|
||||
result_ordres = connection.execute(
|
||||
query, {"soc_id": selected_societe_id}
|
||||
)
|
||||
|
||||
for row in result_ordres:
|
||||
r = dict(row._mapping)
|
||||
ordre_id = r["ordre_id"]
|
||||
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_broker = (
|
||||
float(r["frais_broker"]) if r["frais_broker"] 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_broker + frais_etat + frais_autre
|
||||
sens_ordre = str(r["sens"]).strip().lower()
|
||||
type_ordre = str(r["position"]).strip().lower() if r["position"] else "long"
|
||||
|
||||
# Prix Net unitaire
|
||||
if sens_ordre == "vente":
|
||||
prix_net = prix_brut - frais_total
|
||||
else:
|
||||
prix_net = prix_brut + frais_total
|
||||
|
||||
# Engagements ligne
|
||||
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_broker": frais_broker,
|
||||
"frais_etat": frais_etat,
|
||||
"frais_autre": frais_autre,
|
||||
"prix_net": prix_net,
|
||||
"eng_brut": eng_brut,
|
||||
"eng_net": eng_net,
|
||||
"ordre": r["sens"],
|
||||
"type_ordre": r["position"],
|
||||
"etat": r["etat"],
|
||||
}
|
||||
|
||||
if ordre_id not in groupes_dict:
|
||||
groupes_dict[ordre_id] = {
|
||||
"id": 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,
|
||||
"plus_value": r["plus_value"],
|
||||
"date_plusvalue": r["date_plus_value"],
|
||||
}
|
||||
|
||||
groupes_dict[ordre_id]["pieds"].append(pied_data)
|
||||
groupes_dict[ordre_id]["total_prix_brut"] += prix_brut
|
||||
groupes_dict[ordre_id]["total_frais"] += frais_total
|
||||
groupes_dict[ordre_id]["total_prix_net"] += prix_net
|
||||
|
||||
# Règles de totaux
|
||||
if sens_ordre == "achat":
|
||||
groupes_dict[ordre_id]["total_quantite"] += quantite
|
||||
groupes_dict[ordre_id]["total_eng_brut"] -= eng_brut
|
||||
groupes_dict[ordre_id]["total_eng_net"] -= eng_net
|
||||
elif sens_ordre == "vente":
|
||||
groupes_dict[ordre_id]["total_quantite"] -= quantite
|
||||
groupes_dict[ordre_id]["total_eng_brut"] += eng_brut
|
||||
groupes_dict[ordre_id]["total_eng_net"] += eng_net
|
||||
|
||||
# --- MISE À JOUR AUTO DE LA PLUS-VALUE (ordres soldés) ---
|
||||
try:
|
||||
with db.engine.begin() as conn:
|
||||
for ordre_id, groupe in groupes_dict.items():
|
||||
if abs(groupe["total_quantite"]) < 1e-9:
|
||||
total_eng_net = groupe["total_eng_net"]
|
||||
max_date = max(
|
||||
(p["date_op"] for p in groupe["pieds"] if p["date_op"] is not None),
|
||||
default=None,
|
||||
)
|
||||
|
||||
conn.execute(
|
||||
text(
|
||||
"UPDATE ordre_pied SET etat = :nouvel_etat "
|
||||
"WHERE id_entete = :ordre_id"
|
||||
),
|
||||
{"nouvel_etat": "soldé", "ordre_id": ordre_id},
|
||||
)
|
||||
conn.execute(
|
||||
text(
|
||||
"UPDATE ordre SET plus_value = :plus_value, "
|
||||
"date_plus_value = :date_pv WHERE id = :ordre_id"
|
||||
),
|
||||
{
|
||||
"plus_value": total_eng_net,
|
||||
"date_pv": max_date,
|
||||
"ordre_id": ordre_id,
|
||||
},
|
||||
)
|
||||
|
||||
groupe["plus_value"] = total_eng_net
|
||||
groupe["date_plusvalue"] = max_date
|
||||
for pied in groupe["pieds"]:
|
||||
pied["etat"] = "soldé"
|
||||
except Exception as e:
|
||||
current_app.logger.error("Erreur MAJ plus-value : %s", e, exc_info=True)
|
||||
|
||||
groupes_ordres = list(groupes_dict.values())
|
||||
annees_combo = ["Tout"] + [str(an) for an in range(2020, 2041)]
|
||||
|
||||
return render_template(
|
||||
"gestion_ordres.html",
|
||||
societes=societes,
|
||||
selected_societe_id=selected_societe_id,
|
||||
selected_societe=selected_societe,
|
||||
groupes_ordres=groupes_ordres,
|
||||
total_plus_value_globale=total_plus_value_globale,
|
||||
annees_combo=annees_combo,
|
||||
selected_annee=selected_annee,
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Création d'ordre (la saisie se fait via la modale de gestion_ordres ;
|
||||
# cette route est conservée pour compatibilité et redirige vers la liste)
|
||||
# ---------------------------------------------------------------------------
|
||||
@ordres_bp.route("/creer-ordre", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def creer_ordre():
|
||||
return redirect(url_for("ordres.gestion_ordres"))
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# API : vérifier l'existence d'un ISIN (JSON, exemptée de CSRF)
|
||||
# ---------------------------------------------------------------------------
|
||||
@ordres_bp.route("/api/verifier_isin", methods=["POST"])
|
||||
@csrf.exempt
|
||||
def verifier_isin():
|
||||
data = request.get_json(silent=True) or {}
|
||||
isin = (data.get("isin") or "").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,
|
||||
}
|
||||
)
|
||||
return jsonify({"exists": False})
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Traitement de la création d'un ordre (appelé par la modale)
|
||||
# ---------------------------------------------------------------------------
|
||||
@ordres_bp.route("/creer-ordre-traitement", methods=["POST"])
|
||||
@login_required
|
||||
def creer_ordre_traitement():
|
||||
isin = request.form.get("isin")
|
||||
date_op = request.form.get("date_op")
|
||||
selected_annee = request.form.get("annee", "tout")
|
||||
|
||||
try:
|
||||
quantite = float(request.form.get("quantite") or 0)
|
||||
prix_brut = float(request.form.get("prix_brut") or 0)
|
||||
frais_broker = float(request.form.get("frais_broker") or 0)
|
||||
frais_etat = float(request.form.get("frais_etat") or 0)
|
||||
frais_autre = float(request.form.get("frais_autre") or 0)
|
||||
except ValueError:
|
||||
flash(
|
||||
"Veuillez saisir des valeurs numériques valides pour les quantités, "
|
||||
"prix et frais.",
|
||||
"danger",
|
||||
)
|
||||
return redirect(url_for("ordres.gestion_ordres", annee=selected_annee))
|
||||
|
||||
sens = request.form.get("ordre")
|
||||
position = request.form.get("type_ordre")
|
||||
etat = request.form.get("etat")
|
||||
|
||||
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("ordres.gestion_ordres", annee=selected_annee))
|
||||
|
||||
try:
|
||||
query_ordre = text(
|
||||
"INSERT INTO ordre (id_societe, isin) VALUES (:id_societe, :isin)"
|
||||
)
|
||||
db.session.execute(query_ordre, {"id_societe": id_societe, "isin": isin})
|
||||
|
||||
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},
|
||||
).scalar()
|
||||
|
||||
query_pied = text(
|
||||
"INSERT INTO ordre_pied (id_entete, date_op, quantite, prix_brut, "
|
||||
"frais_broker, frais_etat, frais_autre, sens, position, etat) "
|
||||
"VALUES (:id_entete, :date_op, :quantite, :prix_brut, :frais_broker, "
|
||||
":frais_etat, :frais_autre, :sens, :position, :etat)"
|
||||
)
|
||||
db.session.execute(
|
||||
query_pied,
|
||||
{
|
||||
"id_entete": result_id,
|
||||
"date_op": date_op,
|
||||
"quantite": quantite,
|
||||
"prix_brut": prix_brut,
|
||||
"frais_broker": frais_broker,
|
||||
"frais_etat": frais_etat,
|
||||
"frais_autre": frais_autre,
|
||||
"sens": sens,
|
||||
"position": position,
|
||||
"etat": etat,
|
||||
},
|
||||
)
|
||||
|
||||
db.session.commit()
|
||||
flash("Ordre créé avec succès !", "success")
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
current_app.logger.error("Erreur création ordre : %s", e, exc_info=True)
|
||||
flash("Erreur lors de la création de l'ordre.", "danger")
|
||||
|
||||
return redirect(url_for("ordres.gestion_ordres", annee=selected_annee))
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Ajout d'une ligne à un ordre existant (POST depuis la modale)
|
||||
# ---------------------------------------------------------------------------
|
||||
@ordres_bp.route("/ordre/<int:ordre_id>/ajouter-ligne", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def ajouter_ligne_ordre(ordre_id):
|
||||
selected_annee = request.args.get("annee", "tout")
|
||||
|
||||
if request.method == "POST":
|
||||
selected_annee = request.form.get("annee", "tout")
|
||||
try:
|
||||
quantite = float(request.form.get("quantite") or 0)
|
||||
prix_brut = float(request.form.get("prix_brut") or 0)
|
||||
frais_broker = float(request.form.get("frais_broker") or 0)
|
||||
frais_etat = float(request.form.get("frais_etat") or 0)
|
||||
frais_autre = float(request.form.get("frais_autre") or 0)
|
||||
except ValueError:
|
||||
flash("Veuillez saisir des valeurs numériques valides.", "danger")
|
||||
return redirect(
|
||||
url_for(
|
||||
"ordres.ajouter_ligne_ordre",
|
||||
ordre_id=ordre_id,
|
||||
annee=selected_annee,
|
||||
)
|
||||
)
|
||||
|
||||
try:
|
||||
query_pied = text(
|
||||
"INSERT INTO ordre_pied (id_entete, date_op, quantite, prix_brut, "
|
||||
"frais_broker, frais_etat, frais_autre, sens, position, etat) "
|
||||
"VALUES (:id_entete, :date_op, :quantite, :prix_brut, :frais_broker, "
|
||||
":frais_etat, :frais_autre, :sens, :position, :etat)"
|
||||
)
|
||||
db.session.execute(
|
||||
query_pied,
|
||||
{
|
||||
"id_entete": ordre_id,
|
||||
"date_op": request.form.get("date_op"),
|
||||
"quantite": quantite,
|
||||
"prix_brut": prix_brut,
|
||||
"frais_broker": frais_broker,
|
||||
"frais_etat": frais_etat,
|
||||
"frais_autre": frais_autre,
|
||||
"sens": request.form.get("ordre"),
|
||||
"position": 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("ordres.gestion_ordres", annee=selected_annee))
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
current_app.logger.error("Erreur ajout ligne : %s", e, exc_info=True)
|
||||
flash("Erreur lors de l'ajout de la ligne.", "danger")
|
||||
|
||||
return redirect(url_for("ordres.gestion_ordres", annee=selected_annee))
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Modification d'une ligne d'ordre (POST depuis la modale)
|
||||
# ---------------------------------------------------------------------------
|
||||
@ordres_bp.route("/modifier-ligne-ordre/<int:pied_id>", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def modifier_ligne_ordre(pied_id):
|
||||
selected_annee = request.args.get("annee", "tout")
|
||||
|
||||
if request.method == "POST":
|
||||
selected_annee = request.form.get("annee", "tout")
|
||||
try:
|
||||
quantite = float(request.form.get("quantite") or 0)
|
||||
prix_brut = float(request.form.get("prix_brut") or 0)
|
||||
frais_broker = float(request.form.get("frais_broker") or 0)
|
||||
frais_etat = float(request.form.get("frais_etat") or 0)
|
||||
frais_autre = float(request.form.get("frais_autre") or 0)
|
||||
except ValueError:
|
||||
flash("Veuillez saisir des valeurs numériques valides.", "danger")
|
||||
return redirect(
|
||||
url_for(
|
||||
"ordres.modifier_ligne_ordre",
|
||||
pied_id=pied_id,
|
||||
annee=selected_annee,
|
||||
)
|
||||
)
|
||||
|
||||
try:
|
||||
query_update = text(
|
||||
"UPDATE ordre_pied SET date_op = :date_op, quantite = :quantite, "
|
||||
"prix_brut = :prix_brut, frais_broker = :frais_broker, "
|
||||
"frais_etat = :frais_etat, frais_autre = :frais_autre, "
|
||||
"sens = :sens, position = :position, etat = :etat "
|
||||
"WHERE id = :pied_id"
|
||||
)
|
||||
db.session.execute(
|
||||
query_update,
|
||||
{
|
||||
"date_op": request.form.get("date_op"),
|
||||
"quantite": quantite,
|
||||
"prix_brut": prix_brut,
|
||||
"frais_broker": frais_broker,
|
||||
"frais_etat": frais_etat,
|
||||
"frais_autre": frais_autre,
|
||||
"sens": request.form.get("ordre"),
|
||||
"position": 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("ordres.gestion_ordres", annee=selected_annee))
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
current_app.logger.error("Erreur modification ligne : %s", e, exc_info=True)
|
||||
flash("Erreur lors de la modification de la ligne.", "danger")
|
||||
|
||||
return redirect(url_for("ordres.gestion_ordres", annee=selected_annee))
|
||||
Reference in New Issue
Block a user