216 lines
8.8 KiB
Python
216 lines
8.8 KiB
Python
"""
|
|
Script d'initialisation — Groupe Méditation
|
|
Crée les postes, le groupe initial et le premier membre.
|
|
Usage: cd /opt/groupe-meditation/backend && venv/bin/python ../deploy/seed.py
|
|
"""
|
|
import asyncio
|
|
import sys
|
|
import os
|
|
import uuid
|
|
import secrets
|
|
import csv
|
|
from datetime import date, datetime, timedelta, timezone
|
|
from pathlib import Path
|
|
|
|
# Ajouter le backend au path
|
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "backend"))
|
|
|
|
from app.core.database import engine, Base, async_session
|
|
from app.core.security import hash_pin
|
|
# Importer TOUS les modèles pour que create_all crée toutes les tables
|
|
from app.models.groupe import Groupe
|
|
from app.models.membre import Membre
|
|
from app.models.poste import Poste
|
|
from app.models.affectation import Affectation
|
|
from app.models.invitation import Invitation
|
|
from app.models.collecte import Collecte
|
|
from app.models.depense import Depense
|
|
from app.models.reunion import Reunion
|
|
from app.models.presence import Presence
|
|
from app.models.pv_reunion import PvReunion
|
|
from app.models.rotation import Rotation
|
|
from app.models.rapport_rsg import RapportRsg
|
|
from app.models.jeton import Jeton
|
|
from app.models.litterature import Litterature
|
|
from app.models.vente_litterature import VenteLitterature
|
|
from app.models.envoi_contribution import EnvoiContribution
|
|
from app.models.config_repartition import ConfigRepartition
|
|
from app.models.sync_log import SyncLog
|
|
from app.models.proposition import Proposition
|
|
from app.models.operation_bancaire import OperationBancaire
|
|
from app.models.evenement import Evenement
|
|
from app.models.candidature import Candidature
|
|
from app.models.poste_module import PosteModule
|
|
from app.models.reserve import Reserve
|
|
from app.models.transaction_comptable import TransactionComptable
|
|
from app.models.mouvement_reserve import MouvementReserve
|
|
from app.models.destinataire_contribution import DestinataireContribution
|
|
|
|
|
|
POSTES = [
|
|
("RSG", "executif", "Représentant aux Services du Groupe", "730 days", "2 ans sobriété", 1),
|
|
("RSG Substitut(e)", "executif", "Remplace le RSG, se prépare à assumer le rôle", "730 days", "1 an sobriété", 2),
|
|
("Trésorier(ière)", "executif", "Gestion financière, 7e Tradition", "548 days", "1 an sobriété", 3),
|
|
("Secrétaire", "executif", "PV, correspondance, mémoire du groupe", "548 days", "6 mois sobriété", 4),
|
|
("Animateur(trice)", "service", "Anime les réunions régulières", None, "6 mois sobriété", 5),
|
|
("Responsable accueil", "physique", "Accueil des membres et nouveaux", "365 days", "Sobriété stable", 6),
|
|
("Responsable café", "physique", "Café, entretien de la salle", "365 days", "Aucun", 7),
|
|
("Responsable littérature", "service", "Inventaire et vente de littérature AA", "365 days", "Connaissance littérature AA", 8),
|
|
("Responsable jetons", "service", "Jetons et anniversaires de sobriété", "365 days", "Aucun", 9),
|
|
("Responsable clés", "physique", "Ouverture et fermeture du local", None, "Fiabilité", 10),
|
|
]
|
|
|
|
|
|
def charger_catalogue_litterature():
|
|
chemin = Path(__file__).resolve().parents[1] / "data" / "import" / "Liste-de-prix-rev.-Fevrier-2026.csv"
|
|
if not chemin.exists():
|
|
return [
|
|
{"titre": "Les Alcooliques anonymes (Gros Livre)", "categorie": "livre", "prix_coutant": 12.00, "code_bsg": "B-1"},
|
|
{"titre": "Les Douze Étapes et les Douze Traditions", "categorie": "livre", "prix_coutant": 11.00, "code_bsg": "B-2"},
|
|
{"titre": "Vivre sans alcool", "categorie": "livre", "prix_coutant": 9.00, "code_bsg": "B-7"},
|
|
{"titre": "Les AA sont-ils pour vous?", "categorie": "brochure", "prix_coutant": 0.00, "code_bsg": "P-3"},
|
|
{"titre": "Un nouveau arrive", "categorie": "depliant", "prix_coutant": 0.00, "code_bsg": "P-16"},
|
|
]
|
|
|
|
with chemin.open(newline="", encoding="utf-8") as fichier:
|
|
return [
|
|
{
|
|
"code_bsg": row["code_bsg"].strip() or None,
|
|
"titre": row["titre"].strip(),
|
|
"categorie": row["categorie"].strip(),
|
|
"prix_coutant": float(row["prix_coutant"]) if row["prix_coutant"].strip() else None,
|
|
}
|
|
for row in csv.DictReader(fichier)
|
|
if row.get("titre", "").strip()
|
|
]
|
|
|
|
|
|
async def seed():
|
|
# Créer les tables
|
|
async with engine.begin() as conn:
|
|
await conn.run_sync(Base.metadata.create_all)
|
|
|
|
async with async_session() as db:
|
|
# ── Créer le groupe ──
|
|
print("\n═══ Initialisation du Groupe Méditation ═══\n")
|
|
groupe = Groupe(
|
|
nom="Groupe Méditation",
|
|
district="87-16",
|
|
region="87",
|
|
adresse_local="",
|
|
jour_reunion="mardi",
|
|
heure_reunion=None,
|
|
format_reunion="Discussion ouverte",
|
|
lien_aa87="https://aa87.org",
|
|
)
|
|
db.add(groupe)
|
|
await db.flush()
|
|
print(f"✓ Groupe créé: {groupe.nom} (district {groupe.district})")
|
|
|
|
# ── Créer les postes ──
|
|
postes_map = {}
|
|
for nom, cat, desc, duree, prereq, ordre in POSTES:
|
|
poste = Poste(
|
|
nom=nom,
|
|
categorie=cat,
|
|
description=desc,
|
|
prerequis=prereq,
|
|
ordre_affichage=ordre,
|
|
)
|
|
db.add(poste)
|
|
await db.flush()
|
|
postes_map[nom] = poste
|
|
print(f" ✓ Poste: {nom} ({cat})")
|
|
|
|
# ── Premier membre ──
|
|
print("\n── Premier membre (vous) ──")
|
|
prenom = input(" Prénom: ").strip().title()
|
|
nom_membre = input(" Nom (optionnel, Entrée pour passer): ").strip()
|
|
nom_membre = nom_membre.title() if nom_membre else None
|
|
courriel = input(" Courriel: ").strip().lower()
|
|
while not courriel or ("@" not in courriel and courriel != "sysadmin"):
|
|
courriel = input(" Courriel invalide. Réessayer: ").strip().lower()
|
|
telephone = input(" Téléphone: ").strip()
|
|
while not telephone:
|
|
telephone = input(" Téléphone obligatoire: ").strip()
|
|
pin = input(" PIN (4 chiffres): ").strip()
|
|
while not pin.isdigit() or len(pin) != 4:
|
|
pin = input(" PIN invalide. 4 chiffres: ").strip()
|
|
|
|
poste_choisi = None
|
|
if courriel != "sysadmin":
|
|
print("\n Postes disponibles:")
|
|
for i, (nom_poste, _, _, _, _, _) in enumerate(POSTES[:4], 1):
|
|
print(f" {i}. {nom_poste}")
|
|
choix = input(" Votre poste (1-4): ").strip()
|
|
poste_idx = int(choix) - 1 if choix.isdigit() and 1 <= int(choix) <= 4 else 0
|
|
poste_choisi = POSTES[poste_idx][0]
|
|
|
|
membre = Membre(
|
|
groupe_id=groupe.id,
|
|
prenom=prenom,
|
|
nom=nom_membre,
|
|
courriel=courriel,
|
|
telephone=telephone,
|
|
pin_hash=hash_pin(pin),
|
|
)
|
|
db.add(membre)
|
|
await db.flush()
|
|
|
|
if poste_choisi:
|
|
affectation = Affectation(
|
|
groupe_id=groupe.id,
|
|
membre_id=membre.id,
|
|
poste_id=postes_map[poste_choisi].id,
|
|
date_debut=date.today(),
|
|
statut="actif",
|
|
)
|
|
db.add(affectation)
|
|
|
|
# ── Première invitation ──
|
|
code = secrets.token_hex(6).upper()
|
|
invitation = Invitation(
|
|
groupe_id=groupe.id,
|
|
code=code,
|
|
cree_par=membre.id,
|
|
expire_le=datetime.now(timezone.utc) + timedelta(days=7),
|
|
)
|
|
db.add(invitation)
|
|
|
|
# ── Littérature de base ──
|
|
catalogue_litterature = charger_catalogue_litterature()
|
|
for item in catalogue_litterature:
|
|
db.add(Litterature(
|
|
groupe_id=groupe.id,
|
|
titre=item["titre"],
|
|
categorie=item["categorie"],
|
|
prix_coutant=item["prix_coutant"],
|
|
quantite_stock=0,
|
|
seuil_commande=2,
|
|
code_bsg=item["code_bsg"],
|
|
))
|
|
print(f" ✓ Littérature de base : {len(catalogue_litterature)} titres")
|
|
|
|
db.add(DestinataireContribution(
|
|
groupe_id=groupe.id,
|
|
nom="District 87-16",
|
|
protege=True,
|
|
actif=True,
|
|
))
|
|
print(" ✓ Destinataire de contribution : District 87-16")
|
|
|
|
await db.commit()
|
|
|
|
print(f"\n═══ Initialisation terminée ═══")
|
|
print(f" Membre: {prenom} ({courriel})")
|
|
print(f" Groupe: Groupe Méditation")
|
|
print(f" Poste: {poste_choisi or 'Sysadmin'}")
|
|
print(f"\n Code d'invitation pour les prochains membres: {code}")
|
|
print(f" (Expire dans 7 jours)")
|
|
print(f"\n Connectez-vous sur l'app avec:")
|
|
print(f" Courriel: {courriel}")
|
|
print(f" PIN: {pin}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
asyncio.run(seed())
|