diff --git a/commandes/banque.py b/commandes/banque.py index 1a89ce6..0cc97aa 100644 --- a/commandes/banque.py +++ b/commandes/banque.py @@ -240,6 +240,56 @@ class Banque(commands.Cog): except ValueError as e: await interaction.response.send_message(f"❌ {e}", ephemeral=True) + @banque_group.command(name="set-plafond", description="Définir le plafond négatif autorisé pour les comptes clients") + @app_commands.describe(plafond="Plafond négatif (ex: 1000 = les clients peuvent descendre à -1000€)") + async def set_plafond(self, interaction: discord.Interaction, plafond: float): + bank = await self._get_user_bank(interaction) + if not bank: + await interaction.response.send_message( + "❌ Vous n'avez pas de rôle de banquier sur ce serveur.", ephemeral=True + ) + return + + if plafond < 0 or plafond > 10000: + await interaction.response.send_message( + "❌ Le plafond doit être entre 0 et 10000€.", ephemeral=True + ) + return + + ok = await bank_manager.set_negative_floor(interaction.guild.id, bank["id"], plafond) + if ok: + await interaction.response.send_message( + f"✅ Plafond négatif de **{bank['name']}** défini à **-{plafond:.2f}€**.", + ephemeral=True, + ) + else: + await interaction.response.send_message("❌ Erreur.", ephemeral=True) + + @banque_group.command(name="set-reserve", description="Définir le montant de la réserve bancaire (vault)") + @app_commands.describe(montant="Montant de la réserve") + async def set_reserve(self, interaction: discord.Interaction, montant: float): + bank = await self._get_user_bank(interaction) + if not bank: + await interaction.response.send_message( + "❌ Vous n'avez pas de rôle de banquier sur ce serveur.", ephemeral=True + ) + return + + if montant < 0: + await interaction.response.send_message( + "❌ La réserve ne peut pas être négative.", ephemeral=True + ) + return + + ok = await bank_manager.set_vault_balance(interaction.guild.id, bank["id"], montant) + if ok: + await interaction.response.send_message( + f"✅ Réserve de **{bank['name']}** définie à **{montant:.2f}€**.", + ephemeral=True, + ) + else: + await interaction.response.send_message("❌ Erreur.", ephemeral=True) + async def setup(bot): await bot.add_cog(Banque(bot)) diff --git a/commandes/panel.py b/commandes/panel.py index a84641e..c3f0ef5 100644 --- a/commandes/panel.py +++ b/commandes/panel.py @@ -1,796 +1,236 @@ -""" -Panel centralise — Container V2 (discord.py 2.7) -Menus adaptatifs selon les roles de l'utilisateur. -Banquiers : UserSelect pour choisir l'utilisateur cible. -""" +import os +import json +import random import discord -from discord.ext import commands from discord import app_commands -import json -import math -import random -import string -import os -import asyncio -from datetime import datetime, timedelta -from utils.guild_config import ( - has_role, get_guild_role, get_guild_channel, get_guild_category, - load_guild_config, save_guild_config, set_config_value, - ROLE_LABELS, CHANNEL_LABELS, CATEGORY_LABELS, get_role_id -) -from utils.data_manager import DataManager +from discord.ext import commands -C_MAIN = 0x2F3136 -C_BANK = 0xF1C40F -C_GOV = 0x1ABC9C +from utils.guild_config import ( + has_role, + get_role_id, + load_guild_config, + save_guild_config, + set_config_value, + ROLE_LABELS, + CHANNEL_LABELS, + CATEGORY_LABELS, +) +from utils.bank_manager import BankManager +from utils.rib import decoder_rib + +bank_manager = BankManager() + +base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +C_MAIN = 0x2C2F33 +C_BANK = 0x1ABC9C +C_BANQ_OP = 0x2ECC71 +C_GOV = 0xD4AF37 C_ID = 0x3498DB C_TICKET = 0xE74C3C C_SESSION = 0x9B59B6 C_CONFIG = 0x95A5A6 C_FUN = 0xE67E22 -C_BANQ_OP = 0x2ECC71 - -BANQUES = ["Victoria's Credit Bank", "NGC Bank"] -def _load_json(path, default=None): - if default is None: - default = {} - if not os.path.exists(path): - return default - try: +def _load_json(name): + path = os.path.join(base_dir, "data", name) + if os.path.exists(path): with open(path, "r", encoding="utf-8") as f: return json.load(f) - except (json.JSONDecodeError, IOError): - return default + return {} -def _save_json(path, data): - with open(path, "w", encoding="utf-8") as f: - json.dump(data, f, indent=4, ensure_ascii=False) +def _btn(label, style, cid, cb): + b = discord.ui.Button(label=label, style=style, custom_id=cid) + b.callback = cb + return b -def _find_account_by_rib(rib, comptes, entreprises, banque_nom=None): - if banque_nom: - if banque_nom in comptes: - for uid, data in comptes[banque_nom].items(): - if data.get("rib") == rib: - return comptes, banque_nom, uid, False - if banque_nom in entreprises: - for nom, data in entreprises[banque_nom].items(): - if data.get("rib") == rib: - return entreprises, banque_nom, nom, True - for bq, users in comptes.items(): - for uid, data in users.items(): - if data.get("rib") == rib: - return comptes, bq, uid, False - for bq, ents in entreprises.items(): - for nom, data in ents.items(): - if data.get("rib") == rib: - return entreprises, bq, nom, True - return None, None, None, None - - -def _get_user_accounts(uid_str, comptes): - accounts = [] - for banque, users in comptes.items(): - if uid_str in users: - c = users[uid_str] - accounts.append((banque, c)) - return accounts +def _back_btn(cid, cb): + return _btn("◀ Retour", discord.ButtonStyle.secondary, cid, cb) # ═══════════════════════════════════════════════════════════════════ -# USER SELECT (Banquiers uniquement) +# USER SELECT (pour banquiers) # ═══════════════════════════════════════════════════════════════════ class TargetUserSelect(discord.ui.UserSelect): - def __init__(self, custom_id="pnl_usel", placeholder="Choisir un utilisateur (defaut: vous)"): - super().__init__(placeholder=placeholder, custom_id=custom_id, min_values=1, max_values=1) + def __init__(self, custom_id="bp_usel"): + super().__init__(placeholder="Sélectionner un membre...", custom_id=custom_id) + self.selected_user = None async def callback(self, interaction: discord.Interaction): - self.view.selected_user = self.values[0] + self.selected_user = self.values[0] await interaction.response.defer() # ═══════════════════════════════════════════════════════════════════ -# MODALS — COMPTES (tout le monde) +# MODALS — COMPTES (Mes Comptes) # ═══════════════════════════════════════════════════════════════════ -class CompteModal(discord.ui.Modal, title="💰 Voir un Compte"): - def __init__(self, target_user=None): - super().__init__() - self.target_user = target_user - - async def on_submit(self, interaction: discord.Interaction): - if self.target_user: - uid_str = str(self.target_user.id) - member = self.target_user - else: - uid_str = str(interaction.user.id) - member = interaction.user - comptes = _load_json("comptes_bancaires.json") - accounts = _get_user_accounts(uid_str, comptes) - if not accounts: - await interaction.response.send_message( - f"❌ Aucun compte trouvé pour **{member.display_name}**.", ephemeral=True - ) - return - embed = discord.Embed( - title=f"💰 Comptes de {member.display_name}", - color=C_BANK, - ) - for banque, c in accounts: - txs = c.get("transactions", []) - tx_text = "" - for t in txs[-3:]: - tx_text += ( - f"> 📅 {t.get('date', 'N/A')} — **{t.get('type', 'N/A')}** — " - f"{t.get('montant', 0)}€\n" - ) - if not tx_text: - tx_text = "> Aucune transaction récente.\n" - embed.add_field( - name=f"🏦 {banque}", - value=( - f"**RIB** : `{c.get('rib', 'N/A')}`\n" - f"**Solde** : **{c.get('solde', 0):.2f}€**\n" - f"**Dernières transactions** :\n{tx_text}" - ), - inline=False, - ) - await interaction.response.send_message(embed=embed, ephemeral=True) - - -class VirementModal(discord.ui.Modal, title="📤 Effectuer un Virement"): +class VirementModal(discord.ui.Modal, title="💸 Virement"): rib_dest = discord.ui.TextInput( - label="RIB destinataire", required=True, max_length=40 + label="RIB du destinataire", required=True, max_length=30 ) - montant = discord.ui.TextInput(label="Montant", required=True, max_length=10) - banque = discord.ui.TextInput( - label="Banque (laisser vide = auto)", required=False, max_length=50 + montant = discord.ui.TextInput( + label="Montant (€)", required=True, max_length=15 ) + def __init__(self, source_rib=None): + super().__init__() + self.source_rib = source_rib + async def on_submit(self, interaction: discord.Interaction): try: - montant_val = float(self.montant.value.strip()) + montant = float(self.montant.value.replace(",", ".").strip()) except ValueError: - await interaction.response.send_message( - "❌ Montant invalide.", ephemeral=True - ) + await interaction.response.send_message("❌ Montant invalide.", ephemeral=True) return - if montant_val <= 0: - await interaction.response.send_message( - "❌ Le montant doit être supérieur à 0.", ephemeral=True + rib_dest = self.rib_dest.value.strip().replace(" ", "") + try: + result = await bank_manager.virement( + guild_id=interaction.guild.id, + rib_source=self.source_rib, + rib_dest=rib_dest, + montant=montant, + emetteur_id=interaction.user.id, ) - return - rib_dest_val = self.rib_dest.value.strip() - banque_nom = self.banque.value.strip() if self.banque.value else None - comptes = _load_json("comptes_bancaires.json") - entreprises = _load_json("comptes_entreprises.json") - uid_str = str(interaction.user.id) - source = None - source_bq = None - for bq, users in comptes.items(): - if uid_str in users: - source = users[uid_str] - source_bq = bq - break - if not source: - await interaction.response.send_message( - "❌ Vous n'avez pas de compte bancaire.", ephemeral=True - ) - return - if source["solde"] < montant_val: - await interaction.response.send_message( - "❌ Solde insuffisant.", ephemeral=True - ) - return - dest_store, dest_bq, dest_id, dest_ent = _find_account_by_rib( - rib_dest_val, comptes, entreprises, banque_nom - ) - if not dest_store: - await interaction.response.send_message( - "❌ RIB destinataire introuvable.", ephemeral=True - ) - return - now = datetime.now().strftime("%d/%m/%Y %H:%M:%S") - source["solde"] -= montant_val - source.setdefault("transactions", []).append( - { - "date": now, - "type": "Virement", - "source": f"Vers {rib_dest_val}", - "montant": -montant_val, - } - ) - dest_store[dest_bq][dest_id]["solde"] += montant_val - dest_store[dest_bq][dest_id].setdefault("transactions", []).append( - { - "date": now, - "type": "Virement reçu", - "source": interaction.user.name, - "montant": montant_val, - } - ) - _save_json("comptes_bancaires.json", comptes) - _save_json("comptes_entreprises.json", entreprises) - await interaction.response.send_message( - f"✅ Virement de **{montant_val:.2f}€** effectué vers `{rib_dest_val}` dans **{dest_bq}**.", - ephemeral=False, - ) + msg = f"✅ Virement de **{montant:.2f}€** envoyé." + if result["tva_prelevee"] > 0: + msg += f"\n💡 TVA prélevée : **{result['tva_prelevee']:.2f}€**" + await interaction.response.send_message(msg, ephemeral=True) + except ValueError as e: + await interaction.response.send_message(f"❌ {e}", ephemeral=True) -class AbonnementModal(discord.ui.Modal, title="🔄 Créer un Abonnement"): - nom = discord.ui.TextInput( - label="Nom de l'abonnement", required=True, max_length=50 - ) - rib_emetteur = discord.ui.TextInput( - label="RIB émetteur", required=True, max_length=40 - ) - montant = discord.ui.TextInput(label="Montant", required=True, max_length=10) - rib_receveur = discord.ui.TextInput( - label="RIB receveur", required=True, max_length=40 - ) - banque = discord.ui.TextInput(label="Banque", required=True, max_length=50) - jour = discord.ui.TextInput( - label="Jour (lundi, mardi...)", required=True, max_length=10 +class DepotRetraitModal(discord.ui.Modal): + montant = discord.ui.TextInput( + label="Montant (€)", required=True, max_length=15 ) + def __init__(self, rib: str, operation: str): + super().__init__(title=f"{'➕ Dépôt' if operation == 'depot' else '➖ Retrait'}") + self.rib = rib + self.operation = operation + async def on_submit(self, interaction: discord.Interaction): - JOURS = [ - "lundi", "mardi", "mercredi", "jeudi", - "vendredi", "samedi", "dimanche", - ] - jour_val = self.jour.value.strip().lower() - if jour_val not in JOURS: - await interaction.response.send_message( - f"❌ Jour invalide. {', '.join(JOURS)}", ephemeral=True - ) + try: + montant = float(self.montant.value.replace(",", ".").strip()) + except ValueError: + await interaction.response.send_message("❌ Montant invalide.", ephemeral=True) return try: - montant_val = float(self.montant.value.strip()) - except ValueError: + if self.operation == "depot": + result = await bank_manager.depot_banquier( + interaction.guild.id, self.rib, montant, interaction.user.id + ) + else: + result = await bank_manager.retrait_banquier( + interaction.guild.id, self.rib, montant, interaction.user.id + ) await interaction.response.send_message( - "❌ Montant invalide.", ephemeral=True + f"✅ {self.operation.title()} de **{montant:.2f}€**. Nouveau solde : **{result['nouveau_solde']:.2f}€**", + ephemeral=True, ) - return - abos = _load_json("abonnements.json", []) - abos.append( - { - "nom": self.nom.value.strip(), - "rib_emetteur": self.rib_emetteur.value.strip(), - "rib_receveur": self.rib_receveur.value.strip(), - "montant": montant_val, - "banque": self.banque.value.strip(), - "jour": jour_val, - } - ) - _save_json("abonnements.json", abos) - await interaction.response.send_message( - f"✅ Abonnement **{self.nom.value.strip()}** enregistré. " - f"Virement automatique tous les **{jour_val}**.", - ephemeral=True, - ) + except ValueError as e: + await interaction.response.send_message(f"❌ {e}", ephemeral=True) # ═══════════════════════════════════════════════════════════════════ -# MODALS — OPERATIONS BANQUIER +# MODALS — BANQUIER # ═══════════════════════════════════════════════════════════════════ class OuvrirCompteModal(discord.ui.Modal, title="🏦 Ouvrir un Compte"): - def __init__(self, target_user=None): - super().__init__() - self.target_user = target_user - - async def on_submit(self, interaction: discord.Interaction): - if not has_role(interaction.user, "banquier"): - await interaction.response.send_message( - "❌ Réservé aux banquiers.", ephemeral=True - ) - return - member = self.target_user or interaction.user - uid = member.id - embed = discord.Embed( - title="🏦 Ouvrir un Compte", - description=f"Compte pour : **{member.display_name}**", - color=C_BANQ_OP, - ) - await interaction.response.send_message(embed=embed, ephemeral=True) - - async def _actual_open(self, interaction, banque_nom, uid, member): - comptes = _load_json("comptes_bancaires.json") - if banque_nom not in comptes: - comptes[banque_nom] = {} - uid_str = str(uid) - if uid_str in comptes[banque_nom]: - return False, f"{member.mention} a déjà un compte dans **{banque_nom}**." - rib = ( - f"{random.randint(10000, 99999)} {random.randint(10000, 99999)} " - f"{random.randint(10000000000, 99999999999)} {random.randint(10, 99)}" - ) - comptes[banque_nom][uid_str] = { - "rib": rib, - "solde": 0.0, - "transactions": [], - } - _save_json("comptes_bancaires.json", comptes) - try: - await member.send( - f"🏦 Un compte **{banque_nom}** a été créé pour vous.\nRIB : `{rib}`" - ) - except discord.Forbidden: - pass - return True, f"✅ Compte créé pour {member.mention} dans **{banque_nom}**. RIB: `{rib}`" - - -class OuvrirCompteSimpleModal(discord.ui.Modal, title="🏦 Ouvrir Compte Personnel"): - banque = discord.ui.TextInput( - label="Banque (V'Toria's Credit Bank / NGC Bank)", - required=True, - max_length=50, + membre_id = discord.ui.TextInput( + label="ID du membre", required=True, max_length=20 + ) + type_compte = discord.ui.TextInput( + label="Type (particulier / entreprise)", required=True, max_length=15 + ) + taux_tva = discord.ui.TextInput( + label="TVA % (entreprise, 0 sinon)", required=False, max_length=6 ) - def __init__(self, target_user=None): - super().__init__() - self.target_user = target_user - async def on_submit(self, interaction: discord.Interaction): - if not has_role(interaction.user, "banquier"): - await interaction.response.send_message( - "❌ Réservé aux banquiers.", ephemeral=True - ) - return - banque_nom = self.banque.value.strip() - if banque_nom not in BANQUES: - await interaction.response.send_message( - f"❌ Banque invalide. Choisissez : {', '.join(BANQUES)}", - ephemeral=True, - ) - return - member = self.target_user or interaction.user - uid = member.id - uid_str = str(uid) - comptes = _load_json("comptes_bancaires.json") - if banque_nom not in comptes: - comptes[banque_nom] = {} - if uid_str in comptes[banque_nom]: - await interaction.response.send_message( - f"❌ {member.mention} a déjà un compte dans **{banque_nom}**.", - ephemeral=True, - ) - return - rib = ( - f"{random.randint(10000, 99999)} {random.randint(10000, 99999)} " - f"{random.randint(10000000000, 99999999999)} {random.randint(10, 99)}" - ) - comptes[banque_nom][uid_str] = { - "rib": rib, - "solde": 0.0, - "transactions": [], - } - _save_json("comptes_bancaires.json", comptes) try: - await member.send( - f"🏦 Un compte **{banque_nom}** a été créé pour vous.\nRIB : `{rib}`" - ) - except discord.Forbidden: - pass - await interaction.response.send_message( - f"✅ Compte créé pour {member.mention} dans **{banque_nom}**. RIB: `{rib}`", - ephemeral=False, - ) - - -class OuvrirCompteEntModal(discord.ui.Modal, title="🏢 Ouvrir Compte Entreprise"): - entreprise = discord.ui.TextInput( - label="Nom de l'entreprise", required=True, max_length=50 - ) - banque = discord.ui.TextInput( - label="Banque (V'Toria's Credit Bank / NGC Bank)", - required=True, - max_length=50, - ) - tva = discord.ui.TextInput(label="TVA (2, 5, 10 ou 20)", required=True, max_length=3) - - async def on_submit(self, interaction: discord.Interaction): - if not has_role(interaction.user, "banquier"): - await interaction.response.send_message( - "❌ Réservé aux banquiers.", ephemeral=True - ) - return - banque_nom = self.banque.value.strip() - if banque_nom not in BANQUES: - await interaction.response.send_message( - f"❌ Banque invalide. Choisissez : {', '.join(BANQUES)}", - ephemeral=True, - ) - return - try: - tva_val = int(self.tva.value.strip()) - if tva_val not in (2, 5, 10, 20): - raise ValueError + membre_id = int(self.membre_id.value.strip()) except ValueError: - await interaction.response.send_message( - "❌ TVA invalide (2, 5, 10 ou 20).", ephemeral=True - ) + await interaction.response.send_message("❌ ID invalide.", ephemeral=True) return - nom_ent = self.entreprise.value.strip() - entreprises = _load_json("comptes_entreprises.json") - if banque_nom not in entreprises: - entreprises[banque_nom] = {} - if nom_ent in entreprises[banque_nom]: - await interaction.response.send_message( - f"❌ Un compte existe déjà pour **{nom_ent}** dans **{banque_nom}**.", - ephemeral=True, - ) + member = interaction.guild.get_member(membre_id) + if not member: + await interaction.response.send_message("❌ Membre introuvable.", ephemeral=True) return - rib = " ".join( - ["".join(random.choices(string.digits, k=5)) for _ in range(4)] - ) - mdp = "".join(random.choices(string.ascii_letters + string.digits, k=8)) - entreprises[banque_nom][nom_ent] = { - "rib": rib, - "solde": 0, - "mdp": mdp, - "tva": tva_val, - "transactions": [], - } - _save_json("comptes_entreprises.json", entreprises) - try: - await interaction.user.send( - f"🏦 Compte entreprise **{nom_ent}** ouvert dans **{banque_nom}** !\n" - f"💳 RIB : `{rib}`\n🔐 Mot de passe : `{mdp}`\n💼 TVA : `{tva_val}%`" - ) - except discord.Forbidden: - pass - await interaction.response.send_message( - f"✅ Compte entreprise **{nom_ent}** créé dans **{banque_nom}**.", - ephemeral=True, - ) - - -class VirementBanquierModal(discord.ui.Modal, title="🏦 Virement Banquier"): - rib_emetteur = discord.ui.TextInput( - label="RIB émetteur", required=True, max_length=40 - ) - rib_destinataire = discord.ui.TextInput( - label="RIB destinataire", required=True, max_length=40 - ) - montant = discord.ui.TextInput(label="Montant", required=True, max_length=10) - banque = discord.ui.TextInput( - label="Banque (V'Toria's Credit Bank / NGC Bank)", - required=True, - max_length=50, - ) - - async def on_submit(self, interaction: discord.Interaction): - if not has_role(interaction.user, "banquier"): - await interaction.response.send_message( - "❌ Réservé aux banquiers.", ephemeral=True - ) + type_val = self.type_compte.value.strip().lower() + if type_val not in ("particulier", "entreprise"): + await interaction.response.send_message("❌ Type invalide.", ephemeral=True) return - try: - montant_val = float(self.montant.value.strip()) - except ValueError: - await interaction.response.send_message( - "❌ Montant invalide.", ephemeral=True - ) - return - banque_nom = self.banque.value.strip() - comptes = _load_json("comptes_bancaires.json") - if banque_nom not in comptes: - await interaction.response.send_message( - "❌ Banque introuvable.", ephemeral=True - ) - return - emetteur_id = None - destinataire_id = None - for uid, data in comptes[banque_nom].items(): - if data.get("rib") == self.rib_emetteur.value.strip(): - emetteur_id = uid - if data.get("rib") == self.rib_destinataire.value.strip(): - destinataire_id = uid - if not emetteur_id: - await interaction.response.send_message( - "❌ RIB émetteur introuvable.", ephemeral=True - ) - return - if not destinataire_id: - await interaction.response.send_message( - "❌ RIB destinataire introuvable.", ephemeral=True - ) - return - if comptes[banque_nom][emetteur_id]["solde"] < montant_val: - await interaction.response.send_message( - "❌ Solde insuffisant.", ephemeral=True - ) - return - now = datetime.now().strftime("%d/%m/%Y %H:%M:%S") - comptes[banque_nom][emetteur_id]["solde"] -= montant_val - comptes[banque_nom][emetteur_id]["transactions"].append( - { - "date": now, - "type": "Virement banquier", - "source": f"Vers {self.rib_destinataire.value}", - "montant": -montant_val, - } - ) - comptes[banque_nom][destinataire_id]["solde"] += montant_val - comptes[banque_nom][destinataire_id]["transactions"].append( - { - "date": now, - "type": "Virement reçu (banquier)", - "source": f"De {self.rib_emetteur.value}", - "montant": montant_val, - } - ) - _save_json("comptes_bancaires.json", comptes) - await interaction.response.send_message( - f"✅ Virement banquier de **{montant_val:.2f}€** de " - f"`{self.rib_emetteur.value}` vers `{self.rib_destinataire.value}` " - f"dans **{banque_nom}**.", - ephemeral=True, - ) - - -class VirementBanquierEntModal(discord.ui.Modal, title="🏢 Vir. Banquier → Entreprise"): - banque = discord.ui.TextInput( - label="Banque (V'Toria's Credit Bank / NGC Bank)", - required=True, - max_length=50, - ) - entreprise = discord.ui.TextInput( - label="Nom entreprise", required=True, max_length=50 - ) - mdp = discord.ui.TextInput( - label="Mot de passe entreprise", required=True, max_length=20 - ) - rib_dest = discord.ui.TextInput( - label="RIB destinataire", required=True, max_length=40 - ) - montant = discord.ui.TextInput(label="Montant", required=True, max_length=10) - - async def on_submit(self, interaction: discord.Interaction): - if not has_role(interaction.user, "banquier"): - await interaction.response.send_message( - "❌ Réservé aux banquiers.", ephemeral=True - ) - return - try: - montant_val = float(self.montant.value.strip()) - except ValueError: - await interaction.response.send_message( - "❌ Montant invalide.", ephemeral=True - ) - return - banque_nom = self.banque.value.strip() - entreprises = _load_json("comptes_entreprises.json") - comptes = _load_json("comptes_bancaires.json") - if ( - banque_nom not in entreprises - or self.entreprise.value.strip() not in entreprises[banque_nom] - ): - await interaction.response.send_message( - "❌ Compte entreprise introuvable.", ephemeral=True - ) - return - compte_ent = entreprises[banque_nom][self.entreprise.value.strip()] - if compte_ent.get("mdp") != self.mdp.value.strip(): - await interaction.response.send_message( - "❌ Mot de passe incorrect.", ephemeral=True - ) - return - if compte_ent["solde"] < montant_val: - await interaction.response.send_message( - "❌ Solde insuffisant.", ephemeral=True - ) - return - dest_store, dest_bq, dest_id, _ = _find_account_by_rib( - self.rib_dest.value.strip(), comptes, entreprises - ) - if not dest_store: - await interaction.response.send_message( - "❌ RIB destinataire introuvable.", ephemeral=True - ) - return - now = datetime.now().strftime("%d/%m/%Y %H:%M:%S") - compte_ent["solde"] -= montant_val - compte_ent.setdefault("transactions", []).append( - { - "date": now, - "type": "Virement", - "source": f"Vers {self.rib_dest.value}", - "montant": -montant_val, - } - ) - dest_store[dest_bq][dest_id]["solde"] += montant_val - dest_store[dest_bq][dest_id].setdefault("transactions", []).append( - { - "date": now, - "type": "Virement reçu", - "source": self.entreprise.value.strip(), - "montant": montant_val, - } - ) - _save_json("comptes_entreprises.json", entreprises) - _save_json("comptes_bancaires.json", comptes) - await interaction.response.send_message( - f"✅ Virement de **{montant_val:.2f}€** depuis " - f"**{self.entreprise.value.strip()}** vers `{self.rib_dest.value.strip()}`.", - ephemeral=True, - ) - - -class AjouterArgentModal(discord.ui.Modal, title="➕ Ajouter de l'Argent"): - rib = discord.ui.TextInput(label="RIB du compte", required=True, max_length=40) - montant = discord.ui.TextInput(label="Montant", required=True, max_length=10) - banque = discord.ui.TextInput( - label="Banque (optionnel)", required=False, max_length=50 - ) - - async def on_submit(self, interaction: discord.Interaction): - if not has_role(interaction.user, "banquier"): - await interaction.response.send_message( - "❌ Réservé aux banquiers.", ephemeral=True - ) - return - try: - montant_val = float(self.montant.value.strip()) - except ValueError: - await interaction.response.send_message( - "❌ Montant invalide.", ephemeral=True - ) - return - if montant_val <= 0: - await interaction.response.send_message( - "❌ Montant doit être > 0.", ephemeral=True - ) - return - banque_nom = self.banque.value.strip() if self.banque.value else None - comptes = _load_json("comptes_bancaires.json") - entreprises = _load_json("comptes_entreprises.json") - store, bq, uid, is_ent = _find_account_by_rib( - self.rib.value.strip(), comptes, entreprises, banque_nom - ) - if not store: - await interaction.response.send_message( - "❌ Aucun compte trouvé avec ce RIB.", ephemeral=True - ) - return - now = datetime.now().strftime("%d/%m/%Y %H:%M:%S") - store[bq][uid]["solde"] += montant_val - store[bq][uid].setdefault("transactions", []).append( - { - "date": now, - "type": "Dépôt", - "source": "Banque", - "montant": montant_val, - "emetteur": str(interaction.user.id), - "destinataire": uid, - } - ) - if is_ent: - _save_json("comptes_entreprises.json", entreprises) - else: - _save_json("comptes_bancaires.json", comptes) + tva = 0.0 + if type_val == "entreprise" and self.taux_tva.value: try: - user = await interaction.client.fetch_user(int(uid)) - await user.send( - f"💰 Dépôt de **{montant_val:.2f}€** sur votre compte **{bq}**." - ) - except Exception: + tva = float(self.taux_tva.value.replace(",", ".").strip()) + except ValueError: pass - await interaction.response.send_message( - f"✅ **{montant_val:.2f}€** ajoutés au compte `{self.rib.value.strip()}` dans **{bq}**.", - ephemeral=True, - ) + + banques = await bank_manager.lister_banques(interaction.guild.id) + bank = None + for b in banques: + role = interaction.guild.get_role(b["role_id"]) + if role and role in interaction.user.roles: + bank = b + break + if not bank: + await interaction.response.send_message("❌ Aucune banque associée à vos rôles.", ephemeral=True) + return + + try: + result = await bank_manager.ouvrir_compte( + interaction.guild.id, bank["id"], member.id, type_val, tva + ) + embed = discord.Embed( + title="💳 Compte ouvert", + description=( + f"**Banque :** {bank['name']}\n" + f"**Membre :** {member.mention}\n" + f"**Type :** {type_val.title()}\n" + f"**RIB :** `{result['rib']}`" + ), + color=discord.Color.green(), + ) + await interaction.response.send_message(embed=embed, ephemeral=True) + try: + await member.send( + f"💳 Votre compte **{type_val}** chez **{bank['name']}** a été ouvert.\n" + f"**RIB :** `{result['rib']}`" + ) + except discord.Forbidden: + pass + except ValueError as e: + await interaction.response.send_message(f"❌ {e}", ephemeral=True) -class RetirerArgentModal(discord.ui.Modal, title="➖ Retirer de l'Argent"): - rib = discord.ui.TextInput(label="RIB du compte", required=True, max_length=40) - montant = discord.ui.TextInput(label="Montant", required=True, max_length=10) - banque = discord.ui.TextInput( - label="Banque (optionnel)", required=False, max_length=50 +class TransactionsModal(discord.ui.Modal, title="📜 Transactions"): + rib = discord.ui.TextInput( + label="RIB du compte", required=True, max_length=30 ) async def on_submit(self, interaction: discord.Interaction): - if not has_role(interaction.user, "banquier"): - await interaction.response.send_message( - "❌ Réservé aux banquiers.", ephemeral=True - ) + rib = self.rib.value.strip().replace(" ", "") + txs = await bank_manager.get_transactions(interaction.guild.id, rib, limit=10) + if not txs: + await interaction.response.send_message("Aucune transaction.", ephemeral=True) return - try: - montant_val = float(self.montant.value.strip()) - except ValueError: - await interaction.response.send_message( - "❌ Montant invalide.", ephemeral=True + lines = [] + for tx in txs: + lines.append( + f"`{tx['date']}` — **{tx['type']}** — {tx['montant']:.2f}€" ) - return - banque_nom = self.banque.value.strip() if self.banque.value else None - comptes = _load_json("comptes_bancaires.json") - entreprises = _load_json("comptes_entreprises.json") - store, bq, uid, is_ent = _find_account_by_rib( - self.rib.value.strip(), comptes, entreprises, banque_nom - ) - if not store: - await interaction.response.send_message( - "❌ Aucun compte trouvé avec ce RIB.", ephemeral=True - ) - return - if store[bq][uid]["solde"] < montant_val: - await interaction.response.send_message( - "❌ Solde insuffisant.", ephemeral=True - ) - return - now = datetime.now().strftime("%d/%m/%Y %H:%M:%S") - store[bq][uid]["solde"] -= montant_val - store[bq][uid].setdefault("transactions", []).append( - { - "date": now, - "type": "Retrait", - "source": "Banque", - "montant": -montant_val, - "emetteur": str(interaction.user.id), - "destinataire": uid, - } - ) - if is_ent: - _save_json("comptes_entreprises.json", entreprises) - else: - _save_json("comptes_bancaires.json", comptes) embed = discord.Embed( - title="💸 Retrait effectué", - description=( - f"💼 Banque : **{bq}**\n" - f"RIB : `{self.rib.value.strip()}`\n" - f"Montant : **{montant_val:.2f}€**" - ), - color=discord.Color.red(), + title=f"📜 Transactions ({rib})", + description="\n".join(lines), + color=discord.Color.blue(), ) - await interaction.response.send_message(embed=embed, ephemeral=False) - - -class TransactionsModal(discord.ui.Modal, title="📜 Voir Transactions"): - def __init__(self, target_user=None): - super().__init__() - self.target_user = target_user - - async def on_submit(self, interaction: discord.Interaction): - if not has_role(interaction.user, "banquier"): - await interaction.response.send_message( - "❌ Réservé aux banquiers.", ephemeral=True - ) - return - member = self.target_user or interaction.user - uid_str = str(member.id) - comptes = _load_json("comptes_bancaires.json") - all_txs = [] - for banque, users in comptes.items(): - if uid_str in users: - for t in users[uid_str].get("transactions", []): - all_txs.append(t) - if not all_txs: - await interaction.response.send_message( - f"📜 {member.mention} n'a aucune transaction.", ephemeral=True - ) - return - embed = discord.Embed( - title=f"📜 Transactions de {member.display_name}", - color=discord.Color.gold(), - ) - for t in all_txs[-10:]: - embed.add_field( - name=f"📅 {t.get('date', 'N/A')}", - value=( - f"**Type**: {t.get('type', 'N/A')}\n" - f"💰 **Montant**: {t.get('montant', 0)}€\n" - f"📤 **Source**: {t.get('source', 'N/A')}" - ), - inline=False, - ) - embed.set_footer(text="10 dernières transactions") await interaction.response.send_message(embed=embed, ephemeral=True) @@ -799,491 +239,183 @@ class TransactionsModal(discord.ui.Modal, title="📜 Voir Transactions"): # ═══════════════════════════════════════════════════════════════════ class GouvVirerModal(discord.ui.Modal, title="💸 Virement Gouvernement"): - rib = discord.ui.TextInput( - label="RIB destinataire", required=True, max_length=40 + rib_dest = discord.ui.TextInput( + label="RIB du destinataire", required=True, max_length=30 ) - montant = discord.ui.TextInput(label="Montant", required=True, max_length=10) - banque = discord.ui.TextInput( - label="Banque (optionnel)", required=False, max_length=50 + montant = discord.ui.TextInput( + label="Montant (€)", required=True, max_length=15 ) async def on_submit(self, interaction: discord.Interaction): if not has_role(interaction.user, "gouvernement"): - await interaction.response.send_message( - "❌ Réservé au gouvernement.", ephemeral=True - ) + await interaction.response.send_message("❌ Réservé au gouvernement.", ephemeral=True) return try: - montant_val = float(self.montant.value.strip()) + montant = float(self.montant.value.replace(",", ".").strip()) except ValueError: - await interaction.response.send_message( - "❌ Montant invalide.", ephemeral=True - ) + await interaction.response.send_message("❌ Montant invalide.", ephemeral=True) return - if montant_val <= 0: - await interaction.response.send_message( - "❌ Montant doit être > 0.", ephemeral=True + rib_dest = self.rib_dest.value.strip().replace(" ", "") + try: + fisc = await bank_manager.get_fisc_config(interaction.guild.id) + if not fisc: + await interaction.response.send_message("❌ Aucun fisc configuré.", ephemeral=True) + return + source_compte = await bank_manager.get_compte_by_rib(interaction.guild.id, fisc.get("rib", "")) + if not source_compte: + await interaction.response.send_message("❌ Compte fisc introuvable.", ephemeral=True) + return + result = await bank_manager.virement( + guild_id=interaction.guild.id, + rib_source=source_compte["rib"], + rib_dest=rib_dest, + montant=montant, + emetteur_id=interaction.user.id, ) - return - banque_nom = self.banque.value.strip() if self.banque.value else None - comptes = _load_json("comptes_bancaires.json") - entreprises = _load_json("comptes_entreprises.json") - gouv = entreprises.get("Gouvernement", {}).get("GOUV") - if not gouv: await interaction.response.send_message( - "❌ Compte Gouvernement introuvable.", ephemeral=True + f"✅ Virement de **{montant:.2f}€** effectué depuis le Fisc.", + ephemeral=True, ) - return - if gouv["solde"] < montant_val: - await interaction.response.send_message( - "❌ Fonds insuffisants.", ephemeral=True - ) - return - dest_store, dest_bq, dest_id, _ = _find_account_by_rib( - self.rib.value.strip(), comptes, entreprises, banque_nom - ) - if not dest_store: - await interaction.response.send_message( - "❌ RIB destinataire introuvable.", ephemeral=True - ) - return - now = datetime.now().strftime("%d/%m/%Y %H:%M:%S") - gouv["solde"] -= montant_val - gouv.setdefault("transactions", []).append( - { - "date": now, - "type": "Virement", - "source": "Gouvernement", - "montant": -montant_val, - "destinataire": self.rib.value.strip(), - } - ) - dest_store[dest_bq][dest_id]["solde"] += montant_val - dest_store[dest_bq][dest_id].setdefault("transactions", []).append( - { - "date": now, - "type": "Virement reçu", - "source": "Gouvernement", - "montant": montant_val, - } - ) - _save_json("comptes_entreprises.json", entreprises) - _save_json("comptes_bancaires.json", comptes) - embed = discord.Embed( - title="🏛️ Virement Gouvernemental effectué", - description=( - f"RIB : `{self.rib.value.strip()}`\n" - f"Montant : **{montant_val:.2f}€**\n" - f"Banque : **{dest_bq}**" - ), - color=discord.Color.gold(), - ) - await interaction.response.send_message(embed=embed) + except ValueError as e: + await interaction.response.send_message(f"❌ {e}", ephemeral=True) -class RetraitGouvModal(discord.ui.Modal, title="🏦 Retrait Gouvernement"): - montant = discord.ui.TextInput(label="Montant", required=True, max_length=10) +class ConfigFiscModal(discord.ui.Modal, title="🏛️ Configurer le Fisc"): + institution_id = discord.ui.TextInput( + label="ID de l'institution fisc", required=True, max_length=10 + ) async def on_submit(self, interaction: discord.Interaction): if not has_role(interaction.user, "gouvernement"): - await interaction.response.send_message( - "❌ Réservé au gouvernement.", ephemeral=True - ) + await interaction.response.send_message("❌ Réservé au gouvernement.", ephemeral=True) return try: - montant_val = float(self.montant.value.strip()) + iid = int(self.institution_id.value.strip()) except ValueError: - await interaction.response.send_message( - "❌ Montant invalide.", ephemeral=True - ) + await interaction.response.send_message("❌ ID invalide.", ephemeral=True) return - entreprises = _load_json("comptes_entreprises.json") - gouv = entreprises.get("Gouvernement", {}).get("GOUV") - if not gouv: - await interaction.response.send_message( - "❌ Compte Gouvernement introuvable.", ephemeral=True - ) - return - if gouv["solde"] < montant_val: - await interaction.response.send_message( - "❌ Fonds insuffisants.", ephemeral=True - ) - return - gouv["solde"] -= montant_val - now = datetime.now().strftime("%d/%m/%Y %H:%M:%S") - gouv.setdefault("transactions", []).append( - { - "date": now, - "type": "Retrait", - "source": "Gouvernement", - "montant": -montant_val, - } - ) - _save_json("comptes_entreprises.json", entreprises) - embed = discord.Embed( - title="🏦 Retrait Gouvernement", - description=( - f"Montant : **{montant_val:.2f}€**\n" - f"Solde restant : **{gouv['solde']:.2f}€**" - ), - color=discord.Color.gold(), - ) - await interaction.response.send_message(embed=embed) + ok = await bank_manager.configurer_fisc(interaction.guild.id, iid) + if ok: + await interaction.response.send_message("✅ Fisc configuré.", ephemeral=True) + else: + await interaction.response.send_message("❌ Institution introuvable.", ephemeral=True) -class VirementFBMModal(discord.ui.Modal, title="📤 Virement vers FBM"): - montant = discord.ui.TextInput(label="Montant", required=True, max_length=10) - banque = discord.ui.TextInput( - label="Banque (V'Toria's Credit Bank / NGC Bank)", - required=True, - max_length=50, +class CreerBanqueModal(discord.ui.Modal, title="🏦 Créer une Banque"): + nom = discord.ui.TextInput( + label="Nom de la banque", required=True, max_length=50 + ) + role_id = discord.ui.TextInput( + label="ID du rôle banquier", required=True, max_length=20 ) async def on_submit(self, interaction: discord.Interaction): + if not has_role(interaction.user, "gouvernement"): + await interaction.response.send_message("❌ Réservé au gouvernement.", ephemeral=True) + return try: - montant_val = float(self.montant.value.strip()) + rid = int(self.role_id.value.strip()) except ValueError: - await interaction.response.send_message( - "❌ Montant invalide.", ephemeral=True - ) + await interaction.response.send_message("❌ ID invalide.", ephemeral=True) return - if montant_val <= 0: - await interaction.response.send_message( - "❌ Montant doit être > 0.", ephemeral=True - ) + role = interaction.guild.get_role(rid) + if not role: + await interaction.response.send_message("❌ Rôle introuvable.", ephemeral=True) return - banque_nom = self.banque.value.strip() - uid_str = str(interaction.user.id) - comptes = _load_json("comptes_bancaires.json") - entreprises = _load_json("comptes_entreprises.json") - if banque_nom not in comptes or uid_str not in comptes[banque_nom]: - await interaction.response.send_message( - "❌ Vous n'avez pas de compte dans cette banque.", ephemeral=True + try: + result = await bank_manager.creer_banque(interaction.guild.id, self.nom.value.strip(), rid) + embed = discord.Embed( + title="🏦 Banque créée", + description=( + f"**Nom :** {result['name']}\n" + f"**Rôle :** {role.mention}\n" + f"**Code établissement :** `{result['establishment_code']}`" + ), + color=discord.Color.green(), ) - return - compte = comptes[banque_nom][uid_str] - if compte["solde"] < montant_val: - await interaction.response.send_message( - "❌ Solde insuffisant.", ephemeral=True - ) - return - if "fbm" not in entreprises: - entreprises["fbm"] = {} - if "FBM" not in entreprises["fbm"]: - entreprises["fbm"]["FBM"] = { - "rib": "00000 00000 00000000000 03", - "solde": 0.0, - "transactions": [], - } - now = datetime.now().strftime("%d/%m/%Y %H:%M:%S") - compte["solde"] -= montant_val - compte.setdefault("transactions", []).append( - { - "date": now, - "type": "Virement", - "source": "Vers FBM", - "montant": -montant_val, - } - ) - entreprises["fbm"]["FBM"]["solde"] += montant_val - entreprises["fbm"]["FBM"].setdefault("transactions", []).append( - { - "date": now, - "type": "Virement reçu", - "source": interaction.user.name, - "montant": montant_val, - } - ) - _save_json("comptes_bancaires.json", comptes) - _save_json("comptes_entreprises.json", entreprises) - await interaction.response.send_message( - f"✅ Virement de **{montant_val:.2f}€** envoyé à **FBM** depuis **{banque_nom}**.", - ephemeral=True, - ) + await interaction.response.send_message(embed=embed, ephemeral=True) + except ValueError as e: + await interaction.response.send_message(f"❌ {e}", ephemeral=True) -class VirementURSAFFModal(discord.ui.Modal, title="📤 Virement vers URSAFF"): - montant = discord.ui.TextInput(label="Montant", required=True, max_length=10) - banque = discord.ui.TextInput( - label="Banque (V'Toria's Credit Bank / NGC Bank)", - required=True, - max_length=50, +class CreerInstitutionModal(discord.ui.Modal, title="🏛️ Créer une Institution"): + nom = discord.ui.TextInput( + label="Nom de l'institution", required=True, max_length=50 + ) + role_id = discord.ui.TextInput( + label="ID du rôle (optionnel)", required=False, max_length=20 + ) + audit_all = discord.ui.TextInput( + label="Audit global ? (oui/non)", required=False, max_length=3 ) async def on_submit(self, interaction: discord.Interaction): + if not has_role(interaction.user, "gouvernement"): + await interaction.response.send_message("❌ Réservé au gouvernement.", ephemeral=True) + return + rid = None + if self.role_id.value: + try: + rid = int(self.role_id.value.strip()) + except ValueError: + pass + can_audit = self.audit_all.value.strip().lower() in ("oui", "yes", "true", "1") try: - montant_val = float(self.montant.value.strip()) - except ValueError: - await interaction.response.send_message( - "❌ Montant invalide.", ephemeral=True + result = await bank_manager.creer_institution( + interaction.guild.id, self.nom.value.strip(), rid, can_audit ) - return - if montant_val <= 0: await interaction.response.send_message( - "❌ Montant doit être > 0.", ephemeral=True + f"✅ Institution **{result['name']}** créée (ID: `{result['id']}`).", + ephemeral=True, ) - return - banque_nom = self.banque.value.strip() - uid_str = str(interaction.user.id) - comptes = _load_json("comptes_bancaires.json") - entreprises = _load_json("comptes_entreprises.json") - if banque_nom not in comptes or uid_str not in comptes[banque_nom]: - await interaction.response.send_message( - "❌ Vous n'avez pas de compte dans cette banque.", ephemeral=True - ) - return - compte = comptes[banque_nom][uid_str] - if compte["solde"] < montant_val: - await interaction.response.send_message( - "❌ Solde insuffisant.", ephemeral=True - ) - return - if "ursaff" not in entreprises: - entreprises["ursaff"] = {} - if "URSAFF" not in entreprises["ursaff"]: - entreprises["ursaff"]["URSAFF"] = { - "rib": "00000 00000 00000000000 02", - "solde": 0.0, - "transactions": [], - } - now = datetime.now().strftime("%d/%m/%Y %H:%M:%S") - compte["solde"] -= montant_val - compte.setdefault("transactions", []).append( - { - "date": now, - "type": "Virement", - "source": "Vers URSAFF", - "montant": -montant_val, - } - ) - entreprises["ursaff"]["URSAFF"]["solde"] += montant_val - entreprises["ursaff"]["URSAFF"].setdefault("transactions", []).append( - { - "date": now, - "type": "Virement reçu", - "source": interaction.user.name, - "montant": montant_val, - } - ) - _save_json("comptes_bancaires.json", comptes) - _save_json("comptes_entreprises.json", entreprises) - await interaction.response.send_message( - f"✅ Virement de **{montant_val:.2f}€** envoyé à **URSAFF** depuis **{banque_nom}**.", - ephemeral=True, - ) + except Exception as e: + await interaction.response.send_message(f"❌ {e}", ephemeral=True) # ═══════════════════════════════════════════════════════════════════ # MODALS — IDENTITE # ═══════════════════════════════════════════════════════════════════ -class CreerCarteModal(discord.ui.Modal, title="🪪 Créer une Carte d'Identité"): - utilisateur = discord.ui.TextInput( - label="ID Utilisateur", required=True, max_length=20 - ) - nom_complet = discord.ui.TextInput( - label="Nom complet (Ex: Dupont Jean)", required=True, max_length=100 - ) - date_naissance = discord.ui.TextInput( - label="Date de naissance", required=True, max_length=20 - ) - lieu_naissance = discord.ui.TextInput( - label="Lieu de naissance", required=True, max_length=50 - ) - sexe = discord.ui.TextInput( - label="Sexe (Homme/Femme/Autre)", required=True, max_length=6 - ) +class CreerCarteModal(discord.ui.Modal, title="🪪 Créer une Carte"): + prenom = discord.ui.TextInput(label="Prénom", required=True, max_length=30) + nom_famille = discord.ui.TextInput(label="Nom de famille", required=True, max_length=30) + date_naissance = discord.ui.TextInput(label="Date de naissance", required=True, max_length=20) + sexe = discord.ui.TextInput(label="Sexe (M/F)", required=True, max_length=1) + nationalite = discord.ui.TextInput(label="Nationalité", required=True, max_length=30) async def on_submit(self, interaction: discord.Interaction): if not has_role(interaction.user, "gouvernement"): - await interaction.response.send_message( - "❌ Réservé au gouvernement.", ephemeral=True - ) + await interaction.response.send_message("❌ Réservé au gouvernement.", ephemeral=True) return - uid = self.utilisateur.value.strip() - parts = self.nom_complet.value.strip().split(maxsplit=1) - nom_val = parts[0].upper() if parts else "" - prenom_val = parts[1].capitalize() if len(parts) > 1 else "" + from utils.data_manager import DataManager dm = DataManager() - date_creation = datetime.now() - date_exp = (date_creation + timedelta(days=730)).strftime("%d/%m/%Y") - dm.identities[uid] = { - "nom": nom_val, - "prenom": prenom_val, - "date_naissance": self.date_naissance.value.strip(), - "lieu_naissance": self.lieu_naissance.value.strip(), - "sexe": self.sexe.value.strip().capitalize(), - "date_expiration": date_exp, - } - dm.save_data() - await interaction.response.send_message( - f"✅ Carte d'identité créée pour <@{uid}>.", ephemeral=False + dm.save_identity( + str(interaction.user.id), + { + "prenom": self.prenom.value.strip(), + "nom": self.nom_famille.value.strip(), + "date_naissance": self.date_naissance.value.strip(), + "sexe": self.sexe.value.strip().upper(), + "nationalite": self.nationalite.value.strip(), + }, ) + await interaction.response.send_message("✅ Carte d'identité créée.", ephemeral=True) -class VoirCarteModal(discord.ui.Modal, title="🪪 Voir une Carte d'Identité"): - def __init__(self, target_user=None): - super().__init__() - self.target_user = target_user +class PermisModal(discord.ui.Modal, title="🚗 Permis de Conduire"): + membre_id = discord.ui.TextInput(label="ID du membre", required=True, max_length=20) + categorie = discord.ui.TextInput(label="Catégorie (A/B/C)", required=True, max_length=2) async def on_submit(self, interaction: discord.Interaction): - if self.target_user: - uid = str(self.target_user.id) - member = self.target_user - else: - uid = str(interaction.user.id) - member = interaction.user - dm = DataManager() - identity = dm.identities.get(uid) - if not identity: - await interaction.response.send_message( - "❌ Aucune carte trouvée.", ephemeral=True - ) - return - try: - from utils.card_generator import CardGenerator - - cg = CardGenerator() - card_buffer = await cg.create_identity_card(member, identity) - file = discord.File(card_buffer, filename="carte_identite.png") - embed = discord.Embed(title="🪪 Carte d'identité", color=C_ID) - embed.set_image(url="attachment://carte_identite.png") - await interaction.response.send_message(file=file, embed=embed) - except Exception as e: - await interaction.response.send_message( - f"❌ Erreur : {e}", ephemeral=True - ) + await interaction.response.send_message("✅ Permis enregistré (module à venir).", ephemeral=True) -class PermisModal(discord.ui.Modal, title="🚗 Ajouter un Permis"): - membre = discord.ui.TextInput(label="ID du membre", required=True, max_length=20) +class VisiteMedModal(discord.ui.Modal, title="🏥 Visite Médicale"): + membre_id = discord.ui.TextInput(label="ID du membre", required=True, max_length=20) + resultat = discord.ui.TextInput(label="Résultat (Apte/Inapte)", required=True, max_length=10) async def on_submit(self, interaction: discord.Interaction): - if not has_role(interaction.user, "auto_ecole"): - await interaction.response.send_message( - "❌ Réservé à l'auto-école.", ephemeral=True - ) - return - try: - member = await interaction.guild.fetch_member( - int(self.membre.value.strip()) - ) - role = get_guild_role(interaction.guild, "permis") - if not role: - await interaction.response.send_message( - "❌ Rôle permis introuvable.", ephemeral=True - ) - return - await member.add_roles(role) - await interaction.response.send_message( - f"✅ Permis attribué à {member.mention}.", ephemeral=True - ) - except (ValueError, discord.NotFound): - await interaction.response.send_message( - "❌ Membre introuvable.", ephemeral=True - ) - - -class VisiteMedModal(discord.ui.Modal, title="🏥 Valider Visite Médicale"): - membre = discord.ui.TextInput(label="ID du membre", required=True, max_length=20) - - async def on_submit(self, interaction: discord.Interaction): - if not has_role(interaction.user, "ems"): - await interaction.response.send_message( - "❌ Réservé aux EMS.", ephemeral=True - ) - return - try: - member = await interaction.guild.fetch_member( - int(self.membre.value.strip()) - ) - role = get_guild_role(interaction.guild, "visite_medicale") - if not role: - await interaction.response.send_message( - "❌ Rôle visite médicale introuvable.", ephemeral=True - ) - return - await member.add_roles(role) - fichier = "visite_médicale.json" - data = _load_json(fichier) - expiration = (datetime.now() + timedelta(days=30)).strftime( - "%Y-%m-%d %H:%M:%S" - ) - data[str(member.id)] = {"role_id": role.id, "expiration": expiration} - _save_json(fichier, data) - await interaction.response.send_message( - f"✅ Visite médicale validée pour {member.mention} jusqu'au {expiration}.", - ephemeral=True, - ) - except (ValueError, discord.NotFound): - await interaction.response.send_message( - "❌ Membre introuvable.", ephemeral=True - ) - - -# ═══════════════════════════════════════════════════════════════════ -# MODALS — TICKETS & MESSAGES -# ═══════════════════════════════════════════════════════════════════ - -class MessageModal(discord.ui.Modal, title="✉️ Envoyer un MP"): - membre = discord.ui.TextInput( - label="ID du membre", required=True, max_length=20 - ) - message = discord.ui.TextInput( - label="Message", - style=discord.TextStyle.paragraph, - required=True, - max_length=2000, - ) - - async def on_submit(self, interaction: discord.Interaction): - if not interaction.user.guild_permissions.administrator: - await interaction.response.send_message( - "❌ Réservé aux administrateurs.", ephemeral=True - ) - return - try: - user = await interaction.client.fetch_user(int(self.membre.value.strip())) - await user.send(self.message.value) - await interaction.response.send_message( - f"✅ MP envoyé à {user.name}.", ephemeral=True - ) - except (ValueError, discord.NotFound): - await interaction.response.send_message( - "❌ Membre introuvable.", ephemeral=True - ) - except discord.Forbidden: - await interaction.response.send_message( - "❌ Impossible d'envoyer le MP.", ephemeral=True - ) - - -class MessageTousModal(discord.ui.Modal, title="📢 MP à Tous"): - message = discord.ui.TextInput( - label="Message", - style=discord.TextStyle.paragraph, - required=True, - max_length=2000, - ) - - async def on_submit(self, interaction: discord.Interaction): - if not interaction.user.guild_permissions.administrator: - await interaction.response.send_message( - "❌ Réservé aux administrateurs.", ephemeral=True - ) - return - await interaction.response.defer(ephemeral=True) - sent, failed = 0, 0 - for member in interaction.guild.members: - if not member.bot: - try: - await member.send(self.message.value) - sent += 1 - except Exception: - failed += 1 - await interaction.followup.send( - f"✅ MP envoyés : {sent} | Échecs : {failed}", ephemeral=True - ) + await interaction.response.send_message("✅ Visite médicale enregistrée (module à venir).", ephemeral=True) # ═══════════════════════════════════════════════════════════════════ @@ -1292,19 +424,13 @@ class MessageTousModal(discord.ui.Modal, title="📢 MP à Tous"): class AnnonceSessionModal(discord.ui.Modal, title="📢 Annoncer une Session"): date = discord.ui.TextInput( - label="Date (ex: Vendredi 20 Juin à 20h45)", - required=True, - max_length=100, + label="Date (ex: Vendredi 20 Juin à 20h45)", required=True, max_length=100, ) reminder1 = discord.ui.TextInput( - label="1er rappel (ex: Jeudi 19 Juin à 19h)", - required=True, - max_length=100, + label="1er rappel (ex: Jeudi 19 Juin à 19h)", required=True, max_length=100, ) reminder2 = discord.ui.TextInput( - label="2e rappel (ex: Vendredi 20 Juin à 15h)", - required=True, - max_length=100, + label="2e rappel (ex: Vendredi 20 Juin à 15h)", required=True, max_length=100, ) async def on_submit(self, interaction: discord.Interaction): @@ -1312,37 +438,20 @@ class AnnonceSessionModal(discord.ui.Modal, title="📢 Annoncer une Session"): from datetime import timezone session_date = dateparser.parse( - self.date.value, - languages=["fr"], - settings={ - "TIMEZONE": "Europe/Paris", - "RETURN_AS_TIMEZONE_AWARE": True, - }, + self.date.value, languages=["fr"], + settings={"TIMEZONE": "Europe/Paris", "RETURN_AS_TIMEZONE_AWARE": True}, ) r1 = dateparser.parse( - self.reminder1.value, - languages=["fr"], - settings={ - "TIMEZONE": "Europe/Paris", - "RETURN_AS_TIMEZONE_AWARE": True, - }, + self.reminder1.value, languages=["fr"], + settings={"TIMEZONE": "Europe/Paris", "RETURN_AS_TIMEZONE_AWARE": True}, ) r2 = dateparser.parse( - self.reminder2.value, - languages=["fr"], - settings={ - "TIMEZONE": "Europe/Paris", - "RETURN_AS_TIMEZONE_AWARE": True, - }, + self.reminder2.value, languages=["fr"], + settings={"TIMEZONE": "Europe/Paris", "RETURN_AS_TIMEZONE_AWARE": True}, ) if not session_date or not r1 or not r2: - await interaction.response.send_message( - "❌ Une des dates n'a pas été comprise.", ephemeral=True - ) + await interaction.response.send_message("❌ Une des dates n'a pas été comprise.", ephemeral=True) return - session_date = session_date.astimezone(timezone.utc) - r1 = r1.astimezone(timezone.utc) - r2 = r2.astimezone(timezone.utc) ping_role_id = get_role_id(interaction.guild.id, "citoyen") message = ( "# <:VV:1339237574593609780> - **SESSION VICTORIA'S VICE**\n\n" @@ -1366,7 +475,6 @@ class AnnonceSessionModal(discord.ui.Modal, title="📢 Annoncer une Session"): await interaction.response.send_message(content=message) msg = await interaction.original_response() from commandes.annonces_sessions import PresenceView - await msg.edit(view=PresenceView(msg.id)) @@ -1377,26 +485,17 @@ class ListePresenceModal(discord.ui.Modal, title="📋 Liste des Présences"): async def on_submit(self, interaction: discord.Interaction): from commandes.annonces_sessions import presence_data - try: mid = int(self.message_id.value.strip()) except ValueError: - await interaction.response.send_message( - "❌ ID invalide.", ephemeral=True - ) + await interaction.response.send_message("❌ ID invalide.", ephemeral=True) return if mid not in presence_data: - await interaction.response.send_message( - "❌ Aucune donnée de présence pour ce message.", ephemeral=True - ) + await interaction.response.send_message("❌ Aucune donnée pour ce message.", ephemeral=True) return data = presence_data[mid] - def mentions(ids): - return ( - ", ".join(f"<@{uid}>" for uid in ids) if ids else "Aucun" - ) - + return ", ".join(f"<@{uid}>" for uid in ids) if ids else "Aucun" await interaction.response.send_message( f"**Présents ({len(data['present'])})** : {mentions(data['present'])}\n" f"**Absents ({len(data['absent'])})** : {mentions(data['absent'])}\n" @@ -1410,123 +509,80 @@ class ListePresenceModal(discord.ui.Modal, title="📋 Liste des Présences"): # ═══════════════════════════════════════════════════════════════════ class ConfigRoleModal(discord.ui.Modal, title="🎭 Définir un Rôle"): - cle = discord.ui.TextInput( - label="Clé (banquier, citoyen, ems...)", - required=True, - max_length=30, - ) - role_id = discord.ui.TextInput( - label="ID du rôle Discord", required=True, max_length=20 - ) + cle = discord.ui.TextInput(label="Clé (banquier, citoyen, ems...)", required=True, max_length=30) + role_id = discord.ui.TextInput(label="ID du rôle Discord", required=True, max_length=20) async def on_submit(self, interaction: discord.Interaction): if not interaction.user.guild_permissions.manage_guild: - await interaction.response.send_message( - "❌ Permission requise.", ephemeral=True - ) + await interaction.response.send_message("❌ Permission requise.", ephemeral=True) return cle = self.cle.value.strip().lower() try: rid = int(self.role_id.value.strip()) except ValueError: - await interaction.response.send_message( - "❌ ID invalide.", ephemeral=True - ) + await interaction.response.send_message("❌ ID invalide.", ephemeral=True) return role = interaction.guild.get_role(rid) if not role: - await interaction.response.send_message( - "❌ Rôle introuvable.", ephemeral=True - ) + await interaction.response.send_message("❌ Rôle introuvable.", ephemeral=True) return set_config_value(interaction.guild.id, "roles", cle, rid) await interaction.response.send_message( - f"✅ Rôle **{ROLE_LABELS.get(cle, cle)}** → {role.mention}", - ephemeral=True, + f"✅ Rôle **{ROLE_LABELS.get(cle, cle)}** → {role.mention}", ephemeral=True, ) class ConfigChannelModal(discord.ui.Modal, title="📢 Définir un Salon"): - cle = discord.ui.TextInput( - label="Clé (welcome, candidature...)", - required=True, - max_length=30, - ) - channel_id = discord.ui.TextInput( - label="ID du salon Discord", required=True, max_length=20 - ) + cle = discord.ui.TextInput(label="Clé (welcome, candidature...)", required=True, max_length=30) + channel_id = discord.ui.TextInput(label="ID du salon Discord", required=True, max_length=20) async def on_submit(self, interaction: discord.Interaction): if not interaction.user.guild_permissions.manage_guild: - await interaction.response.send_message( - "❌ Permission requise.", ephemeral=True - ) + await interaction.response.send_message("❌ Permission requise.", ephemeral=True) return cle = self.cle.value.strip().lower() try: cid = int(self.channel_id.value.strip()) except ValueError: - await interaction.response.send_message( - "❌ ID invalide.", ephemeral=True - ) + await interaction.response.send_message("❌ ID invalide.", ephemeral=True) return ch = interaction.guild.get_channel(cid) if not ch: - await interaction.response.send_message( - "❌ Salon introuvable.", ephemeral=True - ) + await interaction.response.send_message("❌ Salon introuvable.", ephemeral=True) return set_config_value(interaction.guild.id, "channels", cle, cid) await interaction.response.send_message( - f"✅ Salon **{CHANNEL_LABELS.get(cle, cle)}** → {ch.mention}", - ephemeral=True, + f"✅ Salon **{CHANNEL_LABELS.get(cle, cle)}** → {ch.mention}", ephemeral=True, ) class ConfigCategoryModal(discord.ui.Modal, title="📁 Définir une Catégorie"): cle = discord.ui.TextInput(label="Clé (ticket)", required=True, max_length=30) - category_id = discord.ui.TextInput( - label="ID de la catégorie Discord", required=True, max_length=20 - ) + category_id = discord.ui.TextInput(label="ID de la catégorie Discord", required=True, max_length=20) async def on_submit(self, interaction: discord.Interaction): if not interaction.user.guild_permissions.manage_guild: - await interaction.response.send_message( - "❌ Permission requise.", ephemeral=True - ) + await interaction.response.send_message("❌ Permission requise.", ephemeral=True) return cle = self.cle.value.strip().lower() try: cid = int(self.category_id.value.strip()) except ValueError: - await interaction.response.send_message( - "❌ ID invalide.", ephemeral=True - ) + await interaction.response.send_message("❌ ID invalide.", ephemeral=True) return cat = interaction.guild.get_channel(cid) if not cat: - await interaction.response.send_message( - "❌ Catégorie introuvable.", ephemeral=True - ) + await interaction.response.send_message("❌ Catégorie introuvable.", ephemeral=True) return set_config_value(interaction.guild.id, "categories", cle, cid) await interaction.response.send_message( - f"✅ Catégorie **{CATEGORY_LABELS.get(cle, cle)}** → **{cat.name}**", - ephemeral=True, + f"✅ Catégorie **{CATEGORY_LABELS.get(cle, cle)}** → **{cat.name}**", ephemeral=True, ) -# ═══════════════════════════════════════════════════════════════════ -# MODALS — UTILITAIRES -# ═══════════════════════════════════════════════════════════════════ - class RollModal(discord.ui.Modal, title="🎲 Lancer un Dé"): - minimum = discord.ui.TextInput( - label="Valeur minimale", required=True, max_length=5 - ) - maximum = discord.ui.TextInput( - label="Valeur maximale", required=True, max_length=5 - ) + minimum = discord.ui.TextInput(label="Valeur minimale", required=True, max_length=5) + maximum = discord.ui.TextInput(label="Valeur maximale", required=True, max_length=5) raison = discord.ui.TextInput(label="Raison", required=True, max_length=100) async def on_submit(self, interaction: discord.Interaction): @@ -1534,118 +590,206 @@ class RollModal(discord.ui.Modal, title="🎲 Lancer un Dé"): min_val = int(self.minimum.value.strip()) max_val = int(self.maximum.value.strip()) except ValueError: - await interaction.response.send_message( - "❌ Valeurs invalides.", ephemeral=True - ) + await interaction.response.send_message("❌ Valeurs invalides.", ephemeral=True) return if min_val > max_val: - await interaction.response.send_message( - "❌ Min > Max.", ephemeral=True - ) + await interaction.response.send_message("❌ Min > Max.", ephemeral=True) return result = random.randint(min_val, max_val) embed = discord.Embed( title="🎲 Résultat du dé", - description=( - f"**{result}** (entre {min_val} et {max_val})\n" - f"*Raison : {self.raison.value.strip()}*" - ), + description=f"**{result}** (entre {min_val} et {max_val})\n*Raison : {self.raison.value.strip()}*", color=C_FUN, ) await interaction.response.send_message(embed=embed) +class MessageModal(discord.ui.Modal, title="✉️ Envoyer un MP"): + user_id = discord.ui.TextInput(label="ID de l'utilisateur", required=True, max_length=20) + message = discord.ui.TextInput(label="Message", required=True, style=discord.TextStyle.paragraph, max_length=2000) + + async def on_submit(self, interaction: discord.Interaction): + try: + uid = int(self.user_id.value.strip()) + except ValueError: + await interaction.response.send_message("❌ ID invalide.", ephemeral=True) + return + user = interaction.bot.get_user(uid) + if not user: + await interaction.response.send_message("❌ Utilisateur introuvable.", ephemeral=True) + return + try: + await user.send(self.message.value) + await interaction.response.send_message(f"✅ MP envoyé à {user.mention}.", ephemeral=True) + except discord.Forbidden: + await interaction.response.send_message("❌ Impossible d'envoyer un MP.", ephemeral=True) + + +class MessageTousModal(discord.ui.Modal, title="📢 MP à Tous"): + message = discord.ui.TextInput(label="Message", required=True, style=discord.TextStyle.paragraph, max_length=2000) + + async def on_submit(self, interaction: discord.Interaction): + if not interaction.user.guild_permissions.administrator: + await interaction.response.send_message("❌ Admin uniquement.", ephemeral=True) + return + await interaction.response.send_message("📨 Envoi en cours...", ephemeral=True) + count = 0 + for member in interaction.guild.members: + if not member.bot: + try: + await member.send(self.message.value) + count += 1 + except discord.Forbidden: + pass + await interaction.followup.send(f"✅ MP envoyé à **{count}** membres.", ephemeral=True) + + # ═══════════════════════════════════════════════════════════════════ -# PANELS — VUES +# VIEWS — MES COMPTES (tout le monde) # ═══════════════════════════════════════════════════════════════════ -def _btn(label, style, cid, cb): - b = discord.ui.Button(label=label, style=style, custom_id=cid) - b.callback = cb - return b +class CompteDetailView(discord.ui.LayoutView): + def __init__(self, author, bot, compte_data, bank_data): + super().__init__(timeout=300) + self.author = author + self.bot_ref = bot + self.compte = compte_data + self.bank = bank_data + rib = compte_data["rib"] + solde = compte_data["solde"] + acc_type = compte_data["account_type"] + tva = compte_data.get("tva_rate", 0) + bank_name = bank_data["name"] -def _back_btn(cid, cb): - return _btn("◀ Retour", discord.ButtonStyle.secondary, cid, cb) + solde_color = discord.Color.green() if solde >= 0 else discord.Color.red() + items = [ + discord.ui.TextDisplay(f"## 💳 {bank_name} — {acc_type.title()}"), + discord.ui.Separator(), + discord.ui.TextDisplay(f"**RIB :** `{rib}`"), + discord.ui.TextDisplay(f"**Solde :** `{solde:,.2f}€`".replace(",", " ")), + ] + if acc_type == "entreprise" and tva > 0: + items.append(discord.ui.TextDisplay(f"**TVA :** `{tva}%`")) + + items.extend([ + discord.ui.Separator(), + discord.ui.ActionRow( + _btn("💳 Carte", discord.ButtonStyle.primary, f"cd_card_{rib[-8:]}", self._card), + _btn("📤 Virement", discord.ButtonStyle.primary, f"cd_vir_{rib[-8:]}", self._vir), + _btn("➕ Dépôt", discord.ButtonStyle.success, f"cd_dep_{rib[-8:]}", self._depot), + _btn("➖ Retrait", discord.ButtonStyle.danger, f"cd_ret_{rib[-8:]}", self._retrait), + ), + discord.ui.Separator(), + discord.ui.ActionRow(_back_btn("back_cd", self._back)), + ]) + + self.add_item(discord.ui.Container(*items, accent_colour=solde_color)) + + async def _card(self, interaction: discord.Interaction): + from utils.bank_card import BankCardGenerator + gen = BankCardGenerator() + buf = gen.generate( + bank_name=self.bank["name"], + holder_name=interaction.user.display_name, + rib=self.compte["rib"], + solde=self.compte["solde"], + account_type=self.compte["account_type"], + tva_rate=self.compte.get("tva_rate", 0), + ) + file = discord.File(buf, filename="carte_bancaire.png") + embed = discord.Embed(color=C_BANK) + embed.set_image(url="attachment://carte_bancaire.png") + await interaction.response.send_message(file=file, embed=embed, ephemeral=True) + + async def _vir(self, interaction: discord.Interaction): + await interaction.response.send_modal( + VirementModal(source_rib=self.compte["rib"]) + ) + + async def _depot(self, interaction: discord.Interaction): + await interaction.response.send_modal( + DepotRetraitModal(rib=self.compte["rib"], operation="depot") + ) + + async def _retrait(self, interaction: discord.Interaction): + await interaction.response.send_modal( + DepotRetraitModal(rib=self.compte["rib"], operation="retrait") + ) + + async def _back(self, interaction: discord.Interaction): + await interaction.response.edit_message( + view=ComptePanelView(interaction.user, self.bot_ref) + ) -# ─── MON COMPTE (tout le monde, banquiers ont UserSelect) ─────── class ComptePanelView(discord.ui.LayoutView): def __init__(self, author, bot): super().__init__(timeout=300) self.author = author self.bot_ref = bot - self.selected_user = None - - is_banquier = has_role(author, "banquier") - target_label = "👤 **Vous-même**" items = [ discord.ui.TextDisplay("## 💰 Mes Comptes"), - discord.ui.TextDisplay( - "Consultez vos comptes, faites des virements et gérez vos abonnements." - ), + discord.ui.TextDisplay("Cliquez sur un compte pour voir les détails et opérer."), discord.ui.Separator(), ] - if is_banquier: - items.append(discord.ui.ActionRow(TargetUserSelect())) - items.append( - discord.ui.TextDisplay("* Sélectionnez un utilisateur ci-dessus pour opérer sur son compte." - )) - items.append(discord.ui.Separator()) + self._account_buttons = [] + + async def _load_accounts(self, interaction: discord.Interaction): + comptes = await bank_manager.get_comptes_user(interaction.guild.id, interaction.user.id) + if not comptes: + items = [ + discord.ui.TextDisplay("## 💰 Mes Comptes"), + discord.ui.TextDisplay("Aucun compte bancaire. contactez un banquier pour en ouvrir un."), + discord.ui.Separator(), + discord.ui.ActionRow(_back_btn("back_cp", self._back)), + ] + self.add_item(discord.ui.Container(*items, accent_colour=discord.Colour(C_BANK))) + return + + items = [ + discord.ui.TextDisplay("## 💰 Mes Comptes"), + discord.ui.TextDisplay(f"**{len(comptes)}** compte(s) trouvé(s). Cliquez pour details."), + discord.ui.Separator(), + ] + + for i, c in enumerate(comptes): + solde_str = f"{c['solde']:,.2f}€".replace(",", " ") + label = f"💳 {c['bank_name']} — {c['account_type'].title()} — {solde_str}" + btn = _btn( + label[:80], + discord.ButtonStyle.primary, + f"cp_acc_{i}", + self._make_acc_cb(c), + ) + items.append(discord.ui.ActionRow(btn)) items.extend([ - discord.ui.ActionRow( - _btn("💰 Voir Compte", discord.ButtonStyle.primary, "cp_voir", self._voir), - _btn("📤 Virement", discord.ButtonStyle.primary, "cp_vir", self._vir), - _btn("🔄 Abonnement", discord.ButtonStyle.primary, "cp_abo", self._abo), - ), - discord.ui.ActionRow( - _btn("🏛️ Vir. Gouvernement", discord.ButtonStyle.primary, "cp_vg", self._vg), - _btn("🏢 Vir. FBM", discord.ButtonStyle.primary, "cp_vfbm", self._vfbm), - _btn("🏢 Vir. URSAFF", discord.ButtonStyle.primary, "cp_vurs", self._vurs), - ), discord.ui.Separator(), discord.ui.ActionRow(_back_btn("back_cp", self._back)), ]) + self.add_item(discord.ui.Container(*items, accent_colour=discord.Colour(C_BANK))) - self.add_item(discord.ui.Container( - *items, - accent_colour=discord.Colour(C_BANK), - )) + def _make_acc_cb(self, compte_data): + async def cb(interaction): + bank = await bank_manager.get_banque(interaction.guild.id, compte_data["bank_id"]) + await interaction.response.edit_message( + view=CompteDetailView(interaction.user, self.bot_ref, compte_data, bank) + ) + return cb - def _target(self, interaction): - return self.selected_user or interaction.user - - async def _back(self, interaction): + async def _back(self, interaction: discord.Interaction): await interaction.response.edit_message( view=MainPanelView(interaction.user, self.bot_ref) ) - async def _voir(self, interaction): - await interaction.response.send_modal( - CompteModal(target_user=self._target(interaction)) - ) - async def _vir(self, interaction): - await interaction.response.send_modal(VirementModal()) - - async def _abo(self, interaction): - await interaction.response.send_modal(AbonnementModal()) - - async def _vg(self, interaction): - await interaction.response.send_modal(VirementModal()) - - async def _vfbm(self, interaction): - await interaction.response.send_modal(VirementFBMModal()) - - async def _vurs(self, interaction): - await interaction.response.send_modal(VirementURSAFFModal()) - - -# ─── OPERATIONS BANQUIER (banquiers uniquement) ────────────────── +# ═══════════════════════════════════════════════════════════════════ +# VIEWS — OPERATIONS BANQUIER +# ═══════════════════════════════════════════════════════════════════ class BanquierPanelView(discord.ui.LayoutView): def __init__(self, author, bot): @@ -1653,41 +797,26 @@ class BanquierPanelView(discord.ui.LayoutView): self.author = author self.bot_ref = bot self.selected_user = None + self.user_select = TargetUserSelect(custom_id="bp_usel") items = [ discord.ui.TextDisplay("## 🏦 Opérations Banquier"), - discord.ui.TextDisplay( - "Ouvrez des comptes, ajoutez/retirez de l'argent et consultez les transactions." - ), + discord.ui.TextDisplay("Sélectionnez un membre puis choisissez une opération."), discord.ui.Separator(), - discord.ui.ActionRow(TargetUserSelect(custom_id="bp_usel")), + discord.ui.ActionRow(self.user_select), discord.ui.Separator(), discord.ui.ActionRow( _btn("🏦 Ouvrir Compte", discord.ButtonStyle.success, "bp_ouvrir", self._ouvrir), - _btn("🏢 Ouvrir Compte ENT", discord.ButtonStyle.success, "bp_ouvrir_ent", self._ouvrir_ent), - ), - discord.ui.ActionRow( - _btn("➕ Ajouter Argent", discord.ButtonStyle.primary, "bp_ajouter", self._ajouter), - _btn("➖ Retirer Argent", discord.ButtonStyle.danger, "bp_retirer", self._retirer), - ), - discord.ui.ActionRow( - _btn("🏦 Vir. Banquier", discord.ButtonStyle.primary, "bp_vir_bq", self._vir_bq), - _btn("🏢 Vir. Banquier ENT", discord.ButtonStyle.primary, "bp_vir_bq_ent", self._vir_bq_ent), - ), - discord.ui.ActionRow( _btn("📜 Transactions", discord.ButtonStyle.secondary, "bp_tx", self._tx), ), discord.ui.Separator(), discord.ui.ActionRow(_back_btn("back_bp", self._back)), ] - self.add_item(discord.ui.Container( - *items, - accent_colour=discord.Colour(C_BANQ_OP), - )) + self.add_item(discord.ui.Container(*items, accent_colour=discord.Colour(C_BANQ_OP))) def _target(self, interaction): - return self.selected_user or interaction.user + return self.user_select.selected_user or interaction.user async def _back(self, interaction): await interaction.response.edit_message( @@ -1695,32 +824,15 @@ class BanquierPanelView(discord.ui.LayoutView): ) async def _ouvrir(self, interaction): - await interaction.response.send_modal( - OuvrirCompteSimpleModal(target_user=self._target(interaction)) - ) - - async def _ouvrir_ent(self, interaction): - await interaction.response.send_modal(OuvrirCompteEntModal()) - - async def _ajouter(self, interaction): - await interaction.response.send_modal(AjouterArgentModal()) - - async def _retirer(self, interaction): - await interaction.response.send_modal(RetirerArgentModal()) - - async def _vir_bq(self, interaction): - await interaction.response.send_modal(VirementBanquierModal()) - - async def _vir_bq_ent(self, interaction): - await interaction.response.send_modal(VirementBanquierEntModal()) + await interaction.response.send_modal(OuvrirCompteModal()) async def _tx(self, interaction): - await interaction.response.send_modal( - TransactionsModal(target_user=self._target(interaction)) - ) + await interaction.response.send_modal(TransactionsModal()) -# ─── GOUVERNEMENT (gouvernement uniquement) ───────────────────── +# ═══════════════════════════════════════════════════════════════════ +# VIEWS — GOUVERNEMENT +# ═══════════════════════════════════════════════════════════════════ class GouvernementPanelView(discord.ui.LayoutView): def __init__(self, author, bot): @@ -1730,153 +842,104 @@ class GouvernementPanelView(discord.ui.LayoutView): items = [ discord.ui.TextDisplay("## 🏛️ Gouvernement"), - discord.ui.TextDisplay( - "Comptes étatiques, FBM, URSAFF, Banque Nationale." - ), + discord.ui.TextDisplay("Institutions, fisc, banques nationales."), discord.ui.Separator(), discord.ui.ActionRow( - _btn("🏛️ Compte Gouv", discord.ButtonStyle.primary, "gp_cg", self._cg), - _btn("💸 Virement Gouv", discord.ButtonStyle.primary, "gp_vg", self._vg), - _btn("🏦 Retrait Gouv", discord.ButtonStyle.danger, "gp_rg", self._rg), + _btn("🏦 Créer Banque", discord.ButtonStyle.success, "gp_cbanque", self._cbanque), + _btn("🏛️ Créer Institution", discord.ButtonStyle.success, "gp_cinst", self._cinst), ), discord.ui.ActionRow( - _btn("📄 Compte FBM", discord.ButtonStyle.primary, "gp_cfbm", self._cfbm), - _btn("📤 Virement FBM", discord.ButtonStyle.primary, "gp_vfbm", self._vfbm), + _btn("⚙️ Config Fisc", discord.ButtonStyle.primary, "gp_fisc", self._fisc), + _btn("💸 Virement Fisc", discord.ButtonStyle.primary, "gp_vfisc", self._vfisc), ), discord.ui.ActionRow( - _btn("📄 Compte URSAFF", discord.ButtonStyle.primary, "gp_curs", self._curs), - _btn("📤 Virement URSAFF", discord.ButtonStyle.primary, "gp_vurs", self._vurs), - ), - discord.ui.ActionRow( - _btn("🏦 Banque Nationale", discord.ButtonStyle.primary, "gp_bn", self._bn), + _btn("📋 Lister Banques", discord.ButtonStyle.secondary, "gp_lbanques", self._lbanques), + _btn("📋 Lister Institutions", discord.ButtonStyle.secondary, "gp_linst", self._linst), ), discord.ui.Separator(), discord.ui.ActionRow(_back_btn("back_gp", self._back)), ] - self.add_item(discord.ui.Container( - *items, - accent_colour=discord.Colour(C_GOV), - )) + self.add_item(discord.ui.Container(*items, accent_colour=discord.Colour(C_GOV))) async def _back(self, interaction): await interaction.response.edit_message( view=MainPanelView(interaction.user, self.bot_ref) ) - async def _cg(self, interaction): + async def _cbanque(self, interaction): if not has_role(interaction.user, "gouvernement"): - await interaction.response.send_message( - "❌ Réservé au gouvernement.", ephemeral=True - ) + await interaction.response.send_message("❌ Réservé au gouvernement.", ephemeral=True) return - entreprises = _load_json("comptes_entreprises.json") - gouv = entreprises.get("Gouvernement", {}).get("GOUV") - if not gouv: - await interaction.response.send_message( - "❌ Compte Gouvernement introuvable.", ephemeral=True - ) + await interaction.response.send_modal(CreerBanqueModal()) + + async def _cinst(self, interaction): + if not has_role(interaction.user, "gouvernement"): + await interaction.response.send_message("❌ Réservé au gouvernement.", ephemeral=True) return - solde = gouv.get("solde", 0) - txs = gouv.get("transactions", []) - embed = discord.Embed( - title="🏛️ Compte Gouvernement", - color=discord.Color.dark_gold(), - ) - embed.add_field(name="💰 Solde", value=f"{solde:.2f}€", inline=False) - for tx in txs[-5:]: + await interaction.response.send_modal(CreerInstitutionModal()) + + async def _fisc(self, interaction): + if not has_role(interaction.user, "gouvernement"): + await interaction.response.send_message("❌ Réservé au gouvernement.", ephemeral=True) + return + await interaction.response.send_modal(ConfigFiscModal()) + + async def _vfisc(self, interaction): + if not has_role(interaction.user, "gouvernement"): + await interaction.response.send_message("❌ Réservé au gouvernement.", ephemeral=True) + return + await interaction.response.send_modal(GouvVirerModal()) + + async def _lbanques(self, interaction): + if not has_role(interaction.user, "gouvernement"): + await interaction.response.send_message("❌ Réservé au gouvernement.", ephemeral=True) + return + banques = await bank_manager.lister_banques(interaction.guild.id) + if not banques: + await interaction.response.send_message("Aucune banque.", ephemeral=True) + return + embed = discord.Embed(title="🏦 Banques", color=discord.Color.gold()) + for b in banques: + role = interaction.guild.get_role(b["role_id"]) embed.add_field( - name=f"📄 {tx.get('date', 'N/A')}", + name=b["name"], value=( - f"**{tx.get('type', 'N/A')}** — " - f"{tx.get('montant', 0)}€ — {tx.get('source', 'N/A')}" + f"**Rôle :** {role.mention if role else '❌'}\n" + f"**Code :** `{b['establishment_code']}`\n" + f"**Réserve :** `{b['vault_balance']:.2f}€`\n" + f"**Encours :** `{b['clients_total_balance']:.2f}€`" ), inline=False, ) - if txs: - embed.set_footer(text=f"{len(txs)} transaction(s) au total") await interaction.response.send_message(embed=embed, ephemeral=True) - async def _vg(self, interaction): - await interaction.response.send_modal(GouvVirerModal()) - - async def _rg(self, interaction): - await interaction.response.send_modal(RetraitGouvModal()) - - async def _cfbm(self, interaction): + async def _linst(self, interaction): if not has_role(interaction.user, "gouvernement"): - await interaction.response.send_message( - "❌ Réservé au gouvernement.", ephemeral=True - ) + await interaction.response.send_message("❌ Réservé au gouvernement.", ephemeral=True) return - entreprises = _load_json("comptes_entreprises.json") - fbm = entreprises.get("fbm", {}).get("FBM", {}) - solde = fbm.get("solde", 0) - txs = fbm.get("transactions", []) - embed = discord.Embed( - title="📄 Compte FBM", color=discord.Color.orange() - ) - embed.add_field(name="💰 Solde", value=f"{solde:.2f}€", inline=False) - for tx in txs[-5:]: - embed.add_field( - name=f"📄 {tx.get('date', 'N/A')}", - value=f"**{tx.get('type', 'N/A')}** — {tx.get('montant', 0)}€", - inline=False, - ) - await interaction.response.send_message(embed=embed, ephemeral=True) - - async def _vfbm(self, interaction): - await interaction.response.send_modal(VirementFBMModal()) - - async def _curs(self, interaction): - if not has_role(interaction.user, "gouvernement"): - await interaction.response.send_message( - "❌ Réservé au gouvernement.", ephemeral=True - ) + insts = await bank_manager.lister_institutions(interaction.guild.id) + if not insts: + await interaction.response.send_message("Aucune institution.", ephemeral=True) return - entreprises = _load_json("comptes_entreprises.json") - urs = entreprises.get("ursaff", {}).get("URSAFF", {}) - solde = urs.get("solde", 0) - txs = urs.get("transactions", []) - embed = discord.Embed( - title="📄 Compte URSAFF", color=discord.Color.orange() - ) - embed.add_field(name="💰 Solde", value=f"{solde:.2f}€", inline=False) - for tx in txs[-5:]: + embed = discord.Embed(title="🏛️ Institutions", color=discord.Color.gold()) + for i in insts: + role = interaction.guild.get_role(i["role_id"]) if i["role_id"] else None embed.add_field( - name=f"📄 {tx.get('date', 'N/A')}", - value=f"**{tx.get('type', 'N/A')}** — {tx.get('montant', 0)}€", - inline=False, - ) - await interaction.response.send_message(embed=embed, ephemeral=True) - - async def _vurs(self, interaction): - await interaction.response.send_modal(VirementURSAFFModal()) - - async def _bn(self, interaction): - if not has_role(interaction.user, "gouvernement"): - await interaction.response.send_message( - "❌ Réservé au gouvernement.", ephemeral=True - ) - return - entreprises = _load_json("comptes_entreprises.json") - bn = entreprises.get("banque_nationale", {}).get("BN", {}) - solde = bn.get("solde", 0) - txs = bn.get("transactions", []) - embed = discord.Embed( - title="🏦 Banque Nationale", color=discord.Color.blue() - ) - embed.add_field(name="💰 Solde", value=f"{solde:.2f}€", inline=False) - for tx in txs[-5:]: - embed.add_field( - name=f"📄 {tx.get('date', 'N/A')}", - value=f"**{tx.get('type', 'N/A')}** — {tx.get('montant', 0)}€", + name=i["name"], + value=( + f"**ID :** `{i['id']}`\n" + f"**Rôle :** {role.mention if role else 'Non défini'}\n" + f"**Audit global :** {'Oui' if i['can_audit_all'] else 'Non'}" + ), inline=False, ) await interaction.response.send_message(embed=embed, ephemeral=True) -# ─── IDENTITE (tout le monde, boutons conditionnels) ──────────── +# ═══════════════════════════════════════════════════════════════════ +# VIEWS — IDENTITE +# ═══════════════════════════════════════════════════════════════════ class IdentitePanelView(discord.ui.LayoutView): def __init__(self, author, bot): @@ -1886,9 +949,7 @@ class IdentitePanelView(discord.ui.LayoutView): items = [ discord.ui.TextDisplay("## 🪪 Identité"), - discord.ui.TextDisplay( - "Cartes d'identité, permis, visites médicales." - ), + discord.ui.TextDisplay("Cartes d'identité, permis, visites médicales."), discord.ui.Separator(), discord.ui.ActionRow( _btn("🪪 Ma Carte", discord.ButtonStyle.primary, "ip_carte", self._carte), @@ -1904,13 +965,9 @@ class IdentitePanelView(discord.ui.LayoutView): action_items = [] if has_role(author, "auto_ecole"): - action_items.append( - _btn("🚗 Permis", discord.ButtonStyle.primary, "ip_permis", self._permis) - ) + action_items.append(_btn("🚗 Permis", discord.ButtonStyle.primary, "ip_permis", self._permis)) if has_role(author, "ems"): - action_items.append( - _btn("🏥 Visite Médicale", discord.ButtonStyle.primary, "ip_vismed", self._vismed) - ) + action_items.append(_btn("🏥 Visite Médicale", discord.ButtonStyle.primary, "ip_vismed", self._vismed)) if action_items: items.append(discord.ui.ActionRow(*action_items)) @@ -1919,10 +976,7 @@ class IdentitePanelView(discord.ui.LayoutView): discord.ui.ActionRow(_back_btn("back_ip", self._back)), ]) - self.add_item(discord.ui.Container( - *items, - accent_colour=discord.Colour(C_ID), - )) + self.add_item(discord.ui.Container(*items, accent_colour=discord.Colour(C_ID))) async def _back(self, interaction): await interaction.response.edit_message( @@ -1930,17 +984,15 @@ class IdentitePanelView(discord.ui.LayoutView): ) async def _carte(self, interaction): + from utils.data_manager import DataManager uid = str(interaction.user.id) dm = DataManager() identity = dm.identities.get(uid) if not identity: - await interaction.response.send_message( - "❌ Aucune carte trouvée.", ephemeral=True - ) + await interaction.response.send_message("❌ Aucune carte trouvée.", ephemeral=True) return try: from utils.card_generator import CardGenerator - cg = CardGenerator() card_buffer = await cg.create_identity_card(interaction.user, identity) file = discord.File(card_buffer, filename="carte_identite.png") @@ -1948,9 +1000,7 @@ class IdentitePanelView(discord.ui.LayoutView): embed.set_image(url="attachment://carte_identite.png") await interaction.response.send_message(file=file, embed=embed) except Exception as e: - await interaction.response.send_message( - f"❌ Erreur : {e}", ephemeral=True - ) + await interaction.response.send_message(f"❌ Erreur : {e}", ephemeral=True) async def _ccar(self, interaction): await interaction.response.send_modal(CreerCarteModal()) @@ -1962,7 +1012,9 @@ class IdentitePanelView(discord.ui.LayoutView): await interaction.response.send_modal(VisiteMedModal()) -# ─── TICKETS & MESSAGES (admin uniquement) ────────────────────── +# ═══════════════════════════════════════════════════════════════════ +# VIEWS — TICKETS & MESSAGES +# ═══════════════════════════════════════════════════════════════════ class TicketsPanelView(discord.ui.LayoutView): def __init__(self, author, bot): @@ -1972,9 +1024,7 @@ class TicketsPanelView(discord.ui.LayoutView): items = [ discord.ui.TextDisplay("## 📩 Tickets & Messages"), - discord.ui.TextDisplay( - "Gestion des tickets et messages privés." - ), + discord.ui.TextDisplay("Gestion des tickets et messages privés."), discord.ui.Separator(), discord.ui.ActionRow( _btn("📑 Embed Ticket", discord.ButtonStyle.success, "tp_ticket", self._ticket), @@ -1985,10 +1035,7 @@ class TicketsPanelView(discord.ui.LayoutView): discord.ui.ActionRow(_back_btn("back_tp", self._back)), ] - self.add_item(discord.ui.Container( - *items, - accent_colour=discord.Colour(C_TICKET), - )) + self.add_item(discord.ui.Container(*items, accent_colour=discord.Colour(C_TICKET))) async def _back(self, interaction): await interaction.response.edit_message( @@ -1997,24 +1044,16 @@ class TicketsPanelView(discord.ui.LayoutView): async def _ticket(self, interaction): if not interaction.user.guild_permissions.manage_guild: - await interaction.response.send_message( - "❌ Permission requise.", ephemeral=True - ) + await interaction.response.send_message("❌ Permission requise.", ephemeral=True) return embed = discord.Embed( title="📩 Ticket Whitelist", - description=( - "Pour réaliser votre whitelist, ouvrez un ticket " - "et remplissez le formulaire." - ), + description="Pour réaliser votre whitelist, ouvrez un ticket et remplissez le formulaire.", color=0x00FF00, ) from commandes.ticket import TicketView - await interaction.channel.send(embed=embed, view=TicketView()) - await interaction.response.send_message( - "✅ Embed des tickets envoyé.", ephemeral=True - ) + await interaction.response.send_message("✅ Embed des tickets envoyé.", ephemeral=True) async def _mp(self, interaction): await interaction.response.send_modal(MessageModal()) @@ -2023,7 +1062,9 @@ class TicketsPanelView(discord.ui.LayoutView): await interaction.response.send_modal(MessageTousModal()) -# ─── SESSIONS RP (tout le monde) ──────────────────────────────── +# ═══════════════════════════════════════════════════════════════════ +# VIEWS — SESSIONS RP +# ═══════════════════════════════════════════════════════════════════ class SessionsPanelView(discord.ui.LayoutView): def __init__(self, author, bot): @@ -2033,9 +1074,7 @@ class SessionsPanelView(discord.ui.LayoutView): items = [ discord.ui.TextDisplay("## 📅 Sessions RP"), - discord.ui.TextDisplay( - "Annonces de sessions et gestion des présences." - ), + discord.ui.TextDisplay("Annonces de sessions et gestion des présences."), discord.ui.Separator(), discord.ui.ActionRow( _btn("📢 Annoncer Session", discord.ButtonStyle.success, "sp_annonce", self._annonce), @@ -2045,10 +1084,7 @@ class SessionsPanelView(discord.ui.LayoutView): discord.ui.ActionRow(_back_btn("back_sp", self._back)), ] - self.add_item(discord.ui.Container( - *items, - accent_colour=discord.Colour(C_SESSION), - )) + self.add_item(discord.ui.Container(*items, accent_colour=discord.Colour(C_SESSION))) async def _back(self, interaction): await interaction.response.edit_message( @@ -2062,7 +1098,9 @@ class SessionsPanelView(discord.ui.LayoutView): await interaction.response.send_modal(ListePresenceModal()) -# ─── CONFIGURATION (admin uniquement) ─────────────────────────── +# ═══════════════════════════════════════════════════════════════════ +# VIEWS — CONFIGURATION +# ═══════════════════════════════════════════════════════════════════ class ConfigPanelView(discord.ui.LayoutView): def __init__(self, author, bot): @@ -2072,9 +1110,7 @@ class ConfigPanelView(discord.ui.LayoutView): items = [ discord.ui.TextDisplay("## ⚙️ Configuration"), - discord.ui.TextDisplay( - "Configurer les rôles, salons et catégories de ce serveur." - ), + discord.ui.TextDisplay("Configurer les rôles, salons et catégories de ce serveur."), discord.ui.Separator(), discord.ui.ActionRow( _btn("🎭 Définir Rôle", discord.ButtonStyle.primary, "cfg_role", self._crole), @@ -2089,10 +1125,7 @@ class ConfigPanelView(discord.ui.LayoutView): discord.ui.ActionRow(_back_btn("back_cfg", self._back)), ] - self.add_item(discord.ui.Container( - *items, - accent_colour=discord.Colour(C_CONFIG), - )) + self.add_item(discord.ui.Container(*items, accent_colour=discord.Colour(C_CONFIG))) async def _back(self, interaction): await interaction.response.edit_message( @@ -2110,14 +1143,10 @@ class ConfigPanelView(discord.ui.LayoutView): async def _cvoir(self, interaction): if not interaction.user.guild_permissions.manage_guild: - await interaction.response.send_message( - "❌ Permission requise.", ephemeral=True - ) + await interaction.response.send_message("❌ Permission requise.", ephemeral=True) return config = load_guild_config(interaction.guild.id) - embed = discord.Embed( - title="⚙️ Configuration actuelle", color=C_CONFIG - ) + embed = discord.Embed(title="⚙️ Configuration actuelle", color=C_CONFIG) roles_text = "" for key, label in ROLE_LABELS.items(): rid = config.get("roles", {}).get(key) @@ -2132,9 +1161,7 @@ class ConfigPanelView(discord.ui.LayoutView): cid = config.get("channels", {}).get(key) if cid: ch = interaction.guild.get_channel(int(cid)) - channels_text += ( - f"> {label} : {ch.mention if ch else f'❌ {cid}'}\n" - ) + channels_text += f"> {label} : {ch.mention if ch else f'❌ {cid}'}\n" else: channels_text += f"> {label} : ⚠️ Non défini\n" embed.add_field(name="📢 Salons", value=channels_text, inline=False) @@ -2143,10 +1170,7 @@ class ConfigPanelView(discord.ui.LayoutView): catid = config.get("categories", {}).get(key) if catid: cat = interaction.guild.get_channel(int(catid)) - cats_text += ( - f"> {label} : **{cat.name}**\n" if cat - else f"> {label} : ❌ {catid}\n" - ) + cats_text += f"> {label} : **{cat.name}**\n" if cat else f"> {label} : ❌ {catid}\n" else: cats_text += f"> {label} : ⚠️ Non défini\n" embed.add_field(name="📁 Catégories", value=cats_text, inline=False) @@ -2154,20 +1178,15 @@ class ConfigPanelView(discord.ui.LayoutView): async def _creset(self, interaction): if not interaction.user.guild_permissions.administrator: - await interaction.response.send_message( - "❌ Réservé aux administrateurs.", ephemeral=True - ) + await interaction.response.send_message("❌ Réservé aux administrateurs.", ephemeral=True) return - save_guild_config( - interaction.guild.id, - {"roles": {}, "channels": {}, "categories": {}}, - ) - await interaction.response.send_message( - "✅ Configuration réinitialisée.", ephemeral=True - ) + save_guild_config(interaction.guild.id, {"roles": {}, "channels": {}, "categories": {}}) + await interaction.response.send_message("✅ Configuration réinitialisée.", ephemeral=True) -# ─── DIVERS (tout le monde) ───────────────────────────────────── +# ═══════════════════════════════════════════════════════════════════ +# VIEWS — DIVERS +# ═══════════════════════════════════════════════════════════════════ class DiversPanelView(discord.ui.LayoutView): def __init__(self, author, bot): @@ -2196,10 +1215,7 @@ class DiversPanelView(discord.ui.LayoutView): discord.ui.ActionRow(_back_btn("back_dp", self._back)), ]) - self.add_item(discord.ui.Container( - *items, - accent_colour=discord.Colour(C_FUN), - )) + self.add_item(discord.ui.Container(*items, accent_colour=discord.Colour(C_FUN))) async def _back(self, interaction): await interaction.response.edit_message( @@ -2211,13 +1227,9 @@ class DiversPanelView(discord.ui.LayoutView): async def _stats(self, interaction): from utils.stats_manager import get_stats_detailed - import io - stats = get_stats_detailed() if not stats: - await interaction.response.send_message( - "📊 Aucune statistique disponible.", ephemeral=True - ) + await interaction.response.send_message("📊 Aucune statistique.", ephemeral=True) return await interaction.response.send_message( f"📊 **Statistiques du bot**\n```json\n{json.dumps(stats, indent=2, ensure_ascii=False)[:1800]}\n```", @@ -2232,78 +1244,34 @@ class DiversPanelView(discord.ui.LayoutView): class CategorySelect(discord.ui.Select): def __init__(self, author, bot): options = [ - discord.SelectOption( - label="Mes Comptes", - description="Comptes, virements, abonnements", - emoji="💰", - value="compte", - ), + discord.SelectOption(label="Mes Comptes", description="Comptes, virements, cartes", emoji="💰", value="compte"), ] if has_role(author, "banquier"): options.append( - discord.SelectOption( - label="Opérations Banquier", - description="Ouvrir comptes, ajouter/retirer, transactions", - emoji="🏦", - value="banquier", - ) + discord.SelectOption(label="Opérations Banquier", description="Ouvrir comptes, opérations", emoji="🏦", value="banquier") ) if has_role(author, "gouvernement"): options.append( - discord.SelectOption( - label="Gouvernement", - description="Comptes étatiques, FBM, URSAFF", - emoji="🏛️", - value="gouvernement", - ) + discord.SelectOption(label="Gouvernement", description="Banques, institutions, fisc", emoji="🏛️", value="gouvernement") ) options.append( - discord.SelectOption( - label="Identité", - description="Cartes d'identité, permis, visite médicale", - emoji="🪪", - value="identite", - ) + discord.SelectOption(label="Identité", description="Cartes d'identité, permis", emoji="🪪", value="identite") ) if author.guild_permissions.administrator: options.append( - discord.SelectOption( - label="Tickets & Messages", - description="Tickets, MP privés", - emoji="📩", - value="tickets", - ) + discord.SelectOption(label="Tickets & Messages", description="Tickets, MP privés", emoji="📩", value="tickets") ) options.append( - discord.SelectOption( - label="Sessions RP", - description="Annonces, présences", - emoji="📅", - value="sessions", - ) + discord.SelectOption(label="Sessions RP", description="Annonces, présences", emoji="📅", value="sessions") ) options.append( - discord.SelectOption( - label="Divers", - description="Dé, stats", - emoji="🎲", - value="divers", - ) + discord.SelectOption(label="Divers", description="Dé, stats", emoji="🎲", value="divers") ) if author.guild_permissions.manage_guild: options.append( - discord.SelectOption( - label="Configuration", - description="Configurer le bot", - emoji="⚙️", - value="config", - ) + discord.SelectOption(label="Configuration", description="Configurer le bot", emoji="⚙️", value="config") ) - super().__init__( - placeholder="Sélectionnez une catégorie...", - options=options, - custom_id="pnl_catselect", - ) + super().__init__(placeholder="Sélectionnez une catégorie...", options=options, custom_id="pnl_catselect") self._bot = bot async def callback(self, interaction: discord.Interaction): @@ -2319,9 +1287,10 @@ class CategorySelect(discord.ui.Select): } cls = view_map.get(self.values[0]) if cls: - await interaction.response.edit_message( - view=cls(interaction.user, self._bot) - ) + view = cls(interaction.user, self._bot) + if isinstance(view, ComptePanelView): + await view._load_accounts(interaction) + await interaction.response.edit_message(view=view) class MainPanelView(discord.ui.LayoutView): @@ -2344,16 +1313,14 @@ class MainPanelView(discord.ui.LayoutView): role_parts.append("👑 Admin") role_text = " • ".join(role_parts) if role_parts else "👤 Citoyen" + guild_name = author.guild.name if author.guild else "Victoria's Vice" + self.add_item(discord.ui.Container( - discord.ui.TextDisplay("## 🏦 Victoria's Vice — Panel de Commandes"), + discord.ui.TextDisplay(f"## 🏦 {guild_name} — Panel de Commandes"), discord.ui.TextDisplay(f"**Vos rôles :** {role_text}"), - discord.ui.TextDisplay( - "Sélectionnez une catégorie ci-dessous pour accéder aux commandes." - ), + discord.ui.TextDisplay("Sélectionnez une catégorie ci-dessous pour accéder aux commandes."), discord.ui.Separator(), - discord.ui.ActionRow( - CategorySelect(author, bot) - ), + discord.ui.ActionRow(CategorySelect(author, bot)), accent_colour=discord.Colour(C_MAIN), )) @@ -2376,9 +1343,7 @@ class Panel(commands.Cog): def __init__(self, bot): self.bot = bot - @app_commands.command( - name="panel", description="Ouvre le panneau de commandes du bot" - ) + @app_commands.command(name="panel", description="Ouvre le panneau de commandes du bot") async def panel(self, interaction: discord.Interaction): view = MainPanelView(interaction.user, self.bot) await interaction.response.send_message(view=view, ephemeral=False) diff --git a/utils/bank_card.py b/utils/bank_card.py new file mode 100644 index 0000000..cf6d320 --- /dev/null +++ b/utils/bank_card.py @@ -0,0 +1,158 @@ +import os +import math +from io import BytesIO +from PIL import Image, ImageDraw, ImageFont + + +class BankCardGenerator: + WIDTH = 856 + HEIGHT = 540 + RADIUS = 30 + + COLORS = { + "dark": (25, 25, 35), + "mid": (35, 40, 60), + "light": (50, 55, 80), + "accent": (0, 180, 160), + "accent_dim": (0, 120, 105), + "gold": (212, 175, 55), + "white": (255, 255, 255), + "gray": (160, 165, 180), + "dim": (90, 95, 110), + } + + def __init__(self): + self.font_title = ImageFont.load_default() + self.font_label = ImageFont.load_default() + self.font_value = ImageFont.load_default() + self.font_rib = ImageFont.load_default() + self.font_large = ImageFont.load_default() + try: + base = os.path.join(os.path.dirname(__file__), "..", "ressources", "fonts") + if os.path.isdir(base): + for name in os.listdir(base): + path = os.path.join(base, name) + lower = name.lower() + if "bold" in lower: + self.font_title = ImageFont.truetype(path, 26) + self.font_large = ImageFont.truetype(path, 36) + elif "mono" in lower or "regular" in lower: + self.font_label = ImageFont.truetype(path, 16) + self.font_value = ImageFont.truetype(path, 22) + self.font_rib = ImageFont.truetype(path, 20) + except Exception: + pass + + def _rounded_rect(self, draw, xy, radius, fill, outline=None, width=0): + x0, y0, x1, y1 = xy + r = radius + draw.ellipse([x0, y0, x0 + 2 * r, y0 + 2 * r], fill=fill, outline=outline, width=width) + draw.ellipse([x1 - 2 * r, y0, x1, y0 + 2 * r], fill=fill, outline=outline, width=width) + draw.ellipse([x0, y1 - 2 * r, x0 + 2 * r, y1], fill=fill, outline=outline, width=width) + draw.ellipse([x1 - 2 * r, y1 - 2 * r, x1, y1], fill=fill, outline=outline, width=width) + draw.rectangle([x0 + r, y0, x1 - r, y1], fill=fill) + draw.rectangle([x0, y0 + r, x1, y1 - r], fill=fill) + if outline and width: + draw.arc([x0, y0, x0 + 2 * r, y0 + 2 * r], 180, 270, fill=outline, width=width) + draw.arc([x1 - 2 * r, y0, x1, y0 + 2 * r], 270, 0, fill=outline, width=width) + draw.arc([x0, y1 - 2 * r, x0 + 2 * r, y1], 90, 180, fill=outline, width=width) + draw.arc([x1 - 2 * r, y1 - 2 * r, x1, y1], 0, 90, fill=outline, width=width) + draw.line([x0 + r, y0, x1 - r, y0], fill=outline, width=width) + draw.line([x0 + r, y1, x1 - r, y1], fill=outline, width=width) + draw.line([x0, y0 + r, x0, y1 - r], fill=outline, width=width) + draw.line([x1, y0 + r, x1, y1 - r], fill=outline, width=width) + + def _draw_gradient_rect(self, draw, xy, color_start, color_end, vertical=True): + x0, y0, x1, y1 = xy + if vertical: + for y in range(y0, y1): + t = (y - y0) / max(1, y1 - y0) + r = int(color_start[0] + (color_end[0] - color_start[0]) * t) + g = int(color_start[1] + (color_end[1] - color_start[1]) * t) + b = int(color_start[2] + (color_end[2] - color_start[2]) * t) + draw.line([(x0, y), (x1, y)], fill=(r, g, b)) + else: + for x in range(x0, x1): + t = (x - x0) / max(1, x1 - x0) + r = int(color_start[0] + (color_end[0] - color_start[0]) * t) + g = int(color_start[1] + (color_end[1] - color_start[1]) * t) + b = int(color_start[2] + (color_end[2] - color_start[2]) * t) + draw.line([(x, y0), (x, y1)], fill=(r, g, b)) + + def _draw_chip(self, draw, x, y): + w, h = 55, 40 + self._rounded_rect(draw, (x, y, x + w, y + h), 6, fill=(200, 175, 50), outline=(180, 155, 40), width=2) + draw.line([(x + 8, y + h // 3), (x + w - 8, y + h // 3)], fill=(160, 135, 30), width=1) + draw.line([(x + 8, y + 2 * h // 3), (x + w - 8, y + 2 * h // 3)], fill=(160, 135, 30), width=1) + draw.line([(x + w // 3, y + 4), (x + w // 3, y + h - 4)], fill=(160, 135, 30), width=1) + draw.line([(x + 2 * w // 3, y + 4), (x + 2 * w // 3, y + h - 4)], fill=(160, 135, 30), width=1) + + def _draw_wave(self, draw, y_base, color, amplitude=15, segments=8): + points = [] + w = self.WIDTH + for i in range(segments + 1): + x = int(w * i / segments) + y = y_base + int(amplitude * math.sin(i * 1.2)) + points.append((x, y)) + for i in range(len(points) - 1): + draw.line([points[i], points[i + 1]], fill=color, width=2) + + def generate( + self, + bank_name: str, + holder_name: str, + rib: str, + solde: float, + account_type: str = "particulier", + tva_rate: float = 0.0, + ) -> BytesIO: + img = Image.new("RGB", (self.WIDTH, self.HEIGHT), self.COLORS["dark"]) + draw = ImageDraw.Draw(img) + + self._draw_gradient_rect(draw, (0, 0, self.WIDTH, self.HEIGHT), self.COLORS["dark"], self.COLORS["mid"]) + + self._draw_wave(draw, 120, (*self.COLORS["accent"][:3],), amplitude=20, segments=10) + self._draw_wave(draw, 140, (*self.COLORS["accent_dim"][:3],), amplitude=15, segments=12) + self._draw_wave(draw, 360, (*self.COLORS["light"][:3],), amplitude=12, segments=8) + + self._rounded_rect(draw, (20, 20, self.WIDTH - 20, self.HEIGHT - 20), self.RADIUS, fill=None, outline=self.COLORS["accent"], width=2) + + self._draw_chip(draw, 60, 130) + + draw.text((75, 185), "VICTORIA'S VICE", fill=self.COLORS["accent"], font=self.font_title) + draw.text((75, 218), bank_name.upper(), fill=self.COLORS["gray"], font=self.font_label) + + type_label = "COMPTE ENTREPRISE" if account_type == "entreprise" else "COMPTE PARTICULIER" + draw.text((self.WIDTH - 200, 45), type_label, fill=self.COLORS["dim"], font=self.font_label) + + y_rib = 280 + draw.text((60, y_rib), "RIB", fill=self.COLORS["dim"], font=self.font_label) + + rib_parts = [] + clean_rib = rib.replace(" ", "") + for i in range(0, len(clean_rib), 4): + rib_parts.append(clean_rib[i:i + 4]) + rib_display = " ".join(rib_parts) + draw.text((60, y_rib + 22), rib_display, fill=self.COLORS["white"], font=self.font_rib) + + y_solde = 350 + draw.text((60, y_solde), "SOLDE", fill=self.COLORS["dim"], font=self.font_label) + solde_str = f"{solde:,.2f} EUR".replace(",", " ") + draw.text((60, y_solde + 22), solde_str, fill=self.COLORS["gold"], font=self.font_large) + + y_bottom = self.HEIGHT - 100 + draw.text((60, y_bottom), "TITULAIRE", fill=self.COLORS["dim"], font=self.font_label) + draw.text((60, y_bottom + 20), holder_name.upper(), fill=self.COLORS["white"], font=self.font_value) + + if account_type == "entreprise" and tva_rate > 0: + draw.text((self.WIDTH - 200, y_bottom), "TVA", fill=self.COLORS["dim"], font=self.font_label) + draw.text((self.WIDTH - 200, y_bottom + 20), f"{tva_rate}%", fill=self.COLORS["accent"], font=self.font_value) + + for i in range(5): + x = self.WIDTH - 60 - i * 35 + draw.ellipse([x, self.HEIGHT - 50, x + 25, self.HEIGHT - 25], fill=self.COLORS["accent"] if i < 3 else self.COLORS["dim"]) + + buf = BytesIO() + img.save(buf, format="PNG") + buf.seek(0) + return buf diff --git a/utils/bank_manager.py b/utils/bank_manager.py index ac6393a..e6394f6 100644 --- a/utils/bank_manager.py +++ b/utils/bank_manager.py @@ -212,7 +212,17 @@ class BankManager: raise ValueError("Compte destination introuvable.") if source["solde"] < montant: - raise ValueError(f"Solde insuffisant. Disponible : {source['solde']:.2f}€") + cursor = await db.execute( + "SELECT client_negative_floor FROM banks WHERE id = ? AND guild_id = ?", + (source["bank_id"], guild_id), + ) + bank_row = await cursor.fetchone() + floor = bank_row[0] if bank_row else 0.0 + nouveau_solde = source["solde"] - montant + if nouveau_solde < -floor: + raise ValueError( + f"Solde insuffisant. Disponible : {source['solde']:.2f}€, plafond négatif : {-floor:.2f}€" + ) tva_montant = 0.0 montant_net = montant @@ -287,13 +297,26 @@ class BankManager: if not compte: raise ValueError("Compte introuvable.") + cursor = await db.execute( + "SELECT vault_balance FROM banks WHERE id = ? AND guild_id = ?", + (compte["bank_id"], guild_id), + ) + bank_row = await cursor.fetchone() + if not bank_row: + raise ValueError("Banque introuvable.") + vault = bank_row[0] + if vault < montant: + raise ValueError( + f"Réserve bancaire insuffisante. Disponible : {vault:.2f}€" + ) + cursor = await db.execute( "UPDATE accounts SET solde = solde + ? WHERE rib = ? AND guild_id = ?", (montant, rib, guild_id), ) cursor = await db.execute( - "UPDATE banks SET clients_total_balance = clients_total_balance + ? WHERE id = ? AND guild_id = ?", - (montant, compte["bank_id"], guild_id), + "UPDATE banks SET clients_total_balance = clients_total_balance + ?, vault_balance = vault_balance - ? WHERE id = ? AND guild_id = ?", + (montant, montant, compte["bank_id"], guild_id), ) now = datetime.now().strftime("%Y-%m-%d %H:%M:%S") @@ -318,16 +341,26 @@ class BankManager: compte = await self._get_account(db, guild_id, rib) if not compte: raise ValueError("Compte introuvable.") - if compte["solde"] < montant: - raise ValueError(f"Solde insuffisant. Disponible : {compte['solde']:.2f}€") + + cursor = await db.execute( + "SELECT client_negative_floor FROM banks WHERE id = ? AND guild_id = ?", + (compte["bank_id"], guild_id), + ) + bank_row = await cursor.fetchone() + floor = bank_row[0] if bank_row else 0.0 + nouveau_solde = compte["solde"] - montant + if nouveau_solde < -floor: + raise ValueError( + f"Plafond négatif atteint ({-floor:.2f}€). Solde actuel : {compte['solde']:.2f}€" + ) cursor = await db.execute( "UPDATE accounts SET solde = solde - ? WHERE rib = ? AND guild_id = ?", (montant, rib, guild_id), ) cursor = await db.execute( - "UPDATE banks SET clients_total_balance = clients_total_balance - ? WHERE id = ? AND guild_id = ?", - (montant, compte["bank_id"], guild_id), + "UPDATE banks SET clients_total_balance = clients_total_balance - ?, vault_balance = vault_balance + ? WHERE id = ? AND guild_id = ?", + (montant, montant, compte["bank_id"], guild_id), ) now = datetime.now().strftime("%Y-%m-%d %H:%M:%S") @@ -337,7 +370,7 @@ class BankManager: (guild_id, rib, compte["user_id"], montant, "retrait_banquier", now, json.dumps({"banquier_id": banquier_id})), ) await db.commit() - return {"nouveau_solde": compte["solde"] - montant} + return {"nouveau_solde": nouveau_solde} finally: await db.close() @@ -498,3 +531,27 @@ class BankManager: ) row = await cursor.fetchone() return dict(row) if row else None + + async def set_negative_floor(self, guild_id: int, bank_id: int, floor: float) -> bool: + db = await get_db() + try: + cursor = await db.execute( + "UPDATE banks SET client_negative_floor = ? WHERE id = ? AND guild_id = ?", + (floor, bank_id, guild_id), + ) + await db.commit() + return cursor.rowcount > 0 + finally: + await db.close() + + async def set_vault_balance(self, guild_id: int, bank_id: int, montant: float) -> bool: + db = await get_db() + try: + cursor = await db.execute( + "UPDATE banks SET vault_balance = ? WHERE id = ? AND guild_id = ?", + (montant, bank_id, guild_id), + ) + await db.commit() + return cursor.rowcount > 0 + finally: + await db.close() diff --git a/utils/db.py b/utils/db.py index 44b22d6..1f0690f 100644 --- a/utils/db.py +++ b/utils/db.py @@ -12,6 +12,7 @@ CREATE TABLE IF NOT EXISTS banks ( establishment_code INTEGER NOT NULL, vault_balance REAL DEFAULT 0.0, clients_total_balance REAL DEFAULT 0.0, + client_negative_floor REAL DEFAULT 0.0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UNIQUE(guild_id, name), UNIQUE(guild_id, establishment_code)