Ajouter scenario demo resilience
- fermer les tuiles d assemblees par defaut - unifier la gestion des inventaires litterature et jetons - enrichir les donnees demo sur 12 mois avec rencontres, assemblees, rotations et journalisation
This commit is contained in:
parent
1ffce6e92c
commit
1ee392e25d
6 changed files with 281 additions and 17 deletions
|
|
@ -53,11 +53,11 @@ MODULES_SAISIE = {
|
||||||
"Animateur(trice)": [],
|
"Animateur(trice)": [],
|
||||||
"Responsable littérature": [
|
"Responsable littérature": [
|
||||||
ModuleTuile(code="S3", nom="Vente littérature", description="Enregistrer une vente", icone="📖", couleur="purple"),
|
ModuleTuile(code="S3", nom="Vente littérature", description="Enregistrer une vente", icone="📖", couleur="purple"),
|
||||||
ModuleTuile(code="M5", nom="Ajuster inventaire", description="Mettre à jour le stock", icone="📦", couleur="orange"),
|
ModuleTuile(code="M5", nom="Gestion des inventaires", description="Littérature et jetons", icone="📦", couleur="orange"),
|
||||||
],
|
],
|
||||||
"Responsable jetons": [
|
"Responsable jetons": [
|
||||||
ModuleTuile(code="S2", nom="Remise jeton", description="Enregistrer un jeton", icone="🏅", couleur="yellow"),
|
ModuleTuile(code="S2", nom="Remise jeton", description="Enregistrer un jeton", icone="🏅", couleur="yellow"),
|
||||||
ModuleTuile(code="M6", nom="Commander jetons", description="Commander à l'Intergroupe", icone="📦", couleur="orange"),
|
ModuleTuile(code="M5", nom="Gestion des inventaires", description="Littérature et jetons", icone="📦", couleur="orange"),
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,9 @@ from app.models.destinataire_contribution import DestinataireContribution
|
||||||
from app.models.envoi_contribution import EnvoiContribution
|
from app.models.envoi_contribution import EnvoiContribution
|
||||||
from app.models.evenement import Evenement
|
from app.models.evenement import Evenement
|
||||||
from app.models.groupe import Groupe
|
from app.models.groupe import Groupe
|
||||||
|
from app.models.historique_modification import HistoriqueModification
|
||||||
from app.models.jeton import Jeton
|
from app.models.jeton import Jeton
|
||||||
|
from app.models.journal_action import JournalAction
|
||||||
from app.models.litterature import Litterature
|
from app.models.litterature import Litterature
|
||||||
from app.models.membre import Membre
|
from app.models.membre import Membre
|
||||||
from app.models.mouvement_reserve import MouvementReserve
|
from app.models.mouvement_reserve import MouvementReserve
|
||||||
|
|
@ -33,6 +35,7 @@ from app.models.rapport_adopte import RapportAdopte
|
||||||
from app.models.rapport_rsg import RapportRsg
|
from app.models.rapport_rsg import RapportRsg
|
||||||
from app.models.reserve import Reserve
|
from app.models.reserve import Reserve
|
||||||
from app.models.reunion import Reunion
|
from app.models.reunion import Reunion
|
||||||
|
from app.models.rotation import Rotation
|
||||||
from app.models.transaction_comptable import TransactionComptable
|
from app.models.transaction_comptable import TransactionComptable
|
||||||
from app.models.vente_litterature import VenteLitterature
|
from app.models.vente_litterature import VenteLitterature
|
||||||
from app.models.vente_jeton import VenteJeton
|
from app.models.vente_jeton import VenteJeton
|
||||||
|
|
@ -46,8 +49,8 @@ POSTES = [
|
||||||
("Animateur(trice)", "service", "Anime les rencontres hebdomadaires.", "Disponibilité régulière", 5, ["S0"]),
|
("Animateur(trice)", "service", "Anime les rencontres hebdomadaires.", "Disponibilité régulière", 5, ["S0"]),
|
||||||
("Responsable accueil", "physique", "Accueille les nouveaux et veille à l'atmosphère de la salle.", "Fiabilité", 6, ["C9"]),
|
("Responsable accueil", "physique", "Accueille les nouveaux et veille à l'atmosphère de la salle.", "Fiabilité", 6, ["C9"]),
|
||||||
("Responsable café", "physique", "Prépare le café et remet les petites dépenses au trésorier.", "Aucun", 7, ["S5"]),
|
("Responsable café", "physique", "Prépare le café et remet les petites dépenses au trésorier.", "Aucun", 7, ["S5"]),
|
||||||
("Responsable littérature", "service", "Maintient l'inventaire et remet les ventes au trésorier.", "Connaissance de la littérature", 8, ["S3", "C4", "M5"]),
|
("Responsable littérature", "service", "Maintient l'inventaire et remet les ventes au trésorier.", "Connaissance de la littérature", 8, ["S3", "C4", "M5", "M6"]),
|
||||||
("Responsable jetons", "service", "Prépare les jetons et les anniversaires.", "Aucun", 9, ["S2", "M6"]),
|
("Responsable jetons", "service", "Prépare les jetons et les anniversaires.", "Aucun", 9, ["S2", "M5", "M6"]),
|
||||||
("Responsable clés", "physique", "Ouvre et ferme le local.", "Fiabilité", 10, []),
|
("Responsable clés", "physique", "Ouvre et ferme le local.", "Fiabilité", 10, []),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -119,15 +122,17 @@ async def initialiser_demo(db: AsyncSession) -> dict:
|
||||||
"groupe_id": groupe.id,
|
"groupe_id": groupe.id,
|
||||||
"membres": len(membres),
|
"membres": len(membres),
|
||||||
"periode_mois": 12,
|
"periode_mois": 12,
|
||||||
|
"rencontres": 52,
|
||||||
|
"assemblees": 12,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async def _creer_base(db: AsyncSession):
|
async def _creer_base(db: AsyncSession):
|
||||||
groupe = Groupe(
|
groupe = Groupe(
|
||||||
nom="Groupe Méditation",
|
nom="Groupe Résilience",
|
||||||
district="87-16",
|
district="87-16",
|
||||||
region="87",
|
region="87",
|
||||||
adresse_local="Salle communautaire, Montréal",
|
adresse_local="Salle communautaire Saint-Louis, Montréal",
|
||||||
jour_reunion="mardi",
|
jour_reunion="mardi",
|
||||||
heure_reunion=time(19, 30),
|
heure_reunion=time(19, 30),
|
||||||
assemblee_affaires_ordre="premiere",
|
assemblee_affaires_ordre="premiere",
|
||||||
|
|
@ -242,6 +247,7 @@ async def _ajouter_historique_demo(db: AsyncSession, groupe: Groupe, membres: di
|
||||||
banque = 0.0
|
banque = 0.0
|
||||||
encaisse = 0.0
|
encaisse = 0.0
|
||||||
compteurs = {"RES": 0, "NOM": 0, "DON": 0}
|
compteurs = {"RES": 0, "NOM": 0, "DON": 0}
|
||||||
|
rotations_postes = ["Animateur(trice)", "Responsable accueil", "Responsable café", "Responsable clés"]
|
||||||
|
|
||||||
for index, jour in enumerate(dates_reunions):
|
for index, jour in enumerate(dates_reunions):
|
||||||
type_reunion = "affaires" if jour.month not in mois_vus else "reguliere"
|
type_reunion = "affaires" if jour.month not in mois_vus else "reguliere"
|
||||||
|
|
@ -254,12 +260,52 @@ async def _ajouter_historique_demo(db: AsyncSession, groupe: Groupe, membres: di
|
||||||
theme=["Acceptation", "Service", "Gratitude", "Honnêteté"][index % 4],
|
theme=["Acceptation", "Service", "Gratitude", "Honnêteté"][index % 4],
|
||||||
annonces={"district": index % 5 == 0, "anniversaire": index % 8 == 0},
|
annonces={"district": index % 5 == 0, "anniversaire": index % 8 == 0},
|
||||||
note="Réunion de démonstration réaliste.",
|
note="Réunion de démonstration réaliste.",
|
||||||
|
cree_le=_moment(jour, 18, 30),
|
||||||
|
modifie_le=_moment(jour, 18, 30),
|
||||||
)
|
)
|
||||||
db.add(reunion)
|
db.add(reunion)
|
||||||
await db.flush()
|
await db.flush()
|
||||||
|
await _trace(
|
||||||
|
db, groupe, secretaire, jour, "reunions", "reunion.planifiee",
|
||||||
|
f"{'Assemblée d affaires' if type_reunion == 'affaires' else 'Rencontre hebdomadaire'} planifiée.",
|
||||||
|
reunion.id,
|
||||||
|
apres={"date": jour.isoformat(), "type": type_reunion},
|
||||||
|
chemin="/reunions" if type_reunion == "reguliere" else "/presences/reunion",
|
||||||
|
)
|
||||||
|
|
||||||
|
for decalage, poste_nom in enumerate(rotations_postes):
|
||||||
|
poste = postes.get(poste_nom)
|
||||||
|
if not poste:
|
||||||
|
continue
|
||||||
|
serviteur = membres_actifs[(index + decalage) % len(membres_actifs)]
|
||||||
|
rotation = Rotation(
|
||||||
|
groupe_id=groupe.id,
|
||||||
|
date_reunion=jour,
|
||||||
|
poste_id=poste.id,
|
||||||
|
membre_id=serviteur.id,
|
||||||
|
note="Rotation planifiée automatiquement pour la démonstration.",
|
||||||
|
cree_par=secretaire.id,
|
||||||
|
cree_le=_moment(jour, 18, 35),
|
||||||
|
)
|
||||||
|
db.add(rotation)
|
||||||
|
await db.flush()
|
||||||
|
await _trace(
|
||||||
|
db, groupe, secretaire, jour, "rotations", "rotations.planifiees",
|
||||||
|
"Rotations hebdomadaires planifiées.",
|
||||||
|
reunion.id,
|
||||||
|
apres={"postes": rotations_postes},
|
||||||
|
chemin="/rotations/batch",
|
||||||
|
)
|
||||||
|
|
||||||
for membre in membres_actifs:
|
for membre in membres_actifs:
|
||||||
db.add(Presence(reunion_id=reunion.id, membre_id=membre.id, present=(index + len(membre.prenom)) % 7 != 0))
|
db.add(Presence(reunion_id=reunion.id, membre_id=membre.id, present=(index + len(membre.prenom)) % 7 != 0))
|
||||||
|
await _trace(
|
||||||
|
db, groupe, secretaire, jour, "presences", "presences.enregistrees",
|
||||||
|
"Présences de la rencontre enregistrées.",
|
||||||
|
reunion.id,
|
||||||
|
apres={"presents": sum(1 for m in membres_actifs if (index + len(m.prenom)) % 7 != 0)},
|
||||||
|
chemin="/presences/",
|
||||||
|
)
|
||||||
|
|
||||||
montant_collecte = round(82 + (index % 6) * 9 + (index % 3) * 4, 2)
|
montant_collecte = round(82 + (index % 6) * 9 + (index % 3) * 4, 2)
|
||||||
collecte = Collecte(
|
collecte = Collecte(
|
||||||
|
|
@ -274,16 +320,32 @@ async def _ajouter_historique_demo(db: AsyncSession, groupe: Groupe, membres: di
|
||||||
date_confirmation=datetime.combine(jour, time(21, 15)).replace(tzinfo=timezone.utc),
|
date_confirmation=datetime.combine(jour, time(21, 15)).replace(tzinfo=timezone.utc),
|
||||||
recu_par=tresorier.id,
|
recu_par=tresorier.id,
|
||||||
date_reception=jour,
|
date_reception=jour,
|
||||||
|
cree_le=_moment(jour, 20, 55),
|
||||||
|
modifie_le=_moment(jour, 21, 5),
|
||||||
)
|
)
|
||||||
db.add(collecte)
|
db.add(collecte)
|
||||||
await db.flush()
|
await db.flush()
|
||||||
encaisse += montant_collecte
|
encaisse += montant_collecte
|
||||||
db.add(TransactionComptable(groupe_id=groupe.id, date_transaction=jour, type="collecte", compte="encaisse", montant=montant_collecte, sens="debit", source_table="collectes", source_id=collecte.id, note="Collecte 7e Tradition", cree_par=tresorier.id))
|
db.add(TransactionComptable(groupe_id=groupe.id, date_transaction=jour, type="collecte", compte="encaisse", montant=montant_collecte, sens="debit", source_table="collectes", source_id=collecte.id, note="Collecte 7e Tradition", cree_par=tresorier.id))
|
||||||
|
await _trace(
|
||||||
|
db, groupe, tresorier, jour, "collectes", "collecte.recue",
|
||||||
|
"Collecte saisie puis reçue par le trésorier.",
|
||||||
|
collecte.id,
|
||||||
|
apres={"montant": montant_collecte, "statut": "recu"},
|
||||||
|
chemin="/collectes",
|
||||||
|
)
|
||||||
|
|
||||||
if index % 4 == 0:
|
if index % 4 == 0:
|
||||||
don = 40 + (index % 5) * 10
|
don = 40 + (index % 5) * 10
|
||||||
banque += don
|
banque += don
|
||||||
db.add(TransactionComptable(groupe_id=groupe.id, date_transaction=jour, type="don", compte="banque", montant=don, sens="debit", note="Don direct au compte bancaire", cree_par=tresorier.id))
|
db.add(TransactionComptable(groupe_id=groupe.id, date_transaction=jour, type="don", compte="banque", montant=don, sens="debit", note="Don direct au compte bancaire", cree_par=tresorier.id))
|
||||||
|
await _trace(
|
||||||
|
db, groupe, tresorier, jour, "transactions_comptables", "don.direct",
|
||||||
|
"Don reçu directement au compte bancaire.",
|
||||||
|
reunion.id,
|
||||||
|
apres={"montant": don, "compte": "banque"},
|
||||||
|
chemin="/operations-bancaires/don",
|
||||||
|
)
|
||||||
|
|
||||||
if index % 4 == 3:
|
if index % 4 == 3:
|
||||||
depot = round(encaisse - 25, 2) if encaisse > 50 else round(encaisse, 2)
|
depot = round(encaisse - 25, 2) if encaisse > 50 else round(encaisse, 2)
|
||||||
|
|
@ -293,6 +355,14 @@ async def _ajouter_historique_demo(db: AsyncSession, groupe: Groupe, membres: di
|
||||||
db.add(OperationBancaire(groupe_id=groupe.id, type="depot", montant=depot, date_operation=jour, effectue_par=tresorier.id, note="Dépôt mensuel de l'encaisse"))
|
db.add(OperationBancaire(groupe_id=groupe.id, type="depot", montant=depot, date_operation=jour, effectue_par=tresorier.id, note="Dépôt mensuel de l'encaisse"))
|
||||||
db.add(TransactionComptable(groupe_id=groupe.id, date_transaction=jour, type="banque_depot", compte="banque", montant=depot, sens="debit", note="Dépôt de l'encaisse", cree_par=tresorier.id))
|
db.add(TransactionComptable(groupe_id=groupe.id, date_transaction=jour, type="banque_depot", compte="banque", montant=depot, sens="debit", note="Dépôt de l'encaisse", cree_par=tresorier.id))
|
||||||
db.add(TransactionComptable(groupe_id=groupe.id, date_transaction=jour, type="banque_depot", compte="encaisse", montant=depot, sens="credit", note="Dépôt de l'encaisse", cree_par=tresorier.id))
|
db.add(TransactionComptable(groupe_id=groupe.id, date_transaction=jour, type="banque_depot", compte="encaisse", montant=depot, sens="credit", note="Dépôt de l'encaisse", cree_par=tresorier.id))
|
||||||
|
await _trace(
|
||||||
|
db, groupe, tresorier, jour, "operations_bancaires", "encaisse.deposee",
|
||||||
|
"Dépôt de l'encaisse au compte bancaire.",
|
||||||
|
collecte.id,
|
||||||
|
avant={"encaisse": round(encaisse + depot, 2), "banque": round(banque - depot, 2)},
|
||||||
|
apres={"encaisse": round(encaisse, 2), "banque": round(banque, 2)},
|
||||||
|
chemin="/operations-bancaires",
|
||||||
|
)
|
||||||
|
|
||||||
if type_reunion == "affaires":
|
if type_reunion == "affaires":
|
||||||
await _decision(db, groupe, reunion, compteurs, "RES", jour, secretaire, rsg, "Adopter l'ordre du jour", "L'assemblée doit traiter les affaires courantes.", "L'ordre du jour est adopté tel que présenté.")
|
await _decision(db, groupe, reunion, compteurs, "RES", jour, secretaire, rsg, "Adopter l'ordre du jour", "L'assemblée doit traiter les affaires courantes.", "L'ordre du jour est adopté tel que présenté.")
|
||||||
|
|
@ -314,7 +384,22 @@ async def _ajouter_historique_demo(db: AsyncSession, groupe: Groupe, membres: di
|
||||||
mois=jour.month,
|
mois=jour.month,
|
||||||
contenu_html=f"<html><body><h1>Rapport du trésorier</h1><p>Rapport adopté pour {jour.strftime('%Y-%m')}.</p><p>Banque: {banque:.2f} $</p><p>Encaisse: {encaisse:.2f} $</p></body></html>",
|
contenu_html=f"<html><body><h1>Rapport du trésorier</h1><p>Rapport adopté pour {jour.strftime('%Y-%m')}.</p><p>Banque: {banque:.2f} $</p><p>Encaisse: {encaisse:.2f} $</p></body></html>",
|
||||||
donnees={"banque": round(banque, 2), "encaisse": round(encaisse, 2)},
|
donnees={"banque": round(banque, 2), "encaisse": round(encaisse, 2)},
|
||||||
|
adopte_le=_moment(jour, 21, 35),
|
||||||
))
|
))
|
||||||
|
await _trace(
|
||||||
|
db, groupe, secretaire, jour, "pv_reunions", "pv.redige",
|
||||||
|
"Procès-verbal d'assemblée rédigé et classé.",
|
||||||
|
pv.id,
|
||||||
|
apres={"approuve": pv.approuve},
|
||||||
|
chemin="/pv/",
|
||||||
|
)
|
||||||
|
await _trace(
|
||||||
|
db, groupe, tresorier, jour, "rapports_adoptes", "rapport_tresorier.adopte",
|
||||||
|
"Rapport du trésorier adopté par la conscience de groupe.",
|
||||||
|
reunion.id,
|
||||||
|
apres={"annee": jour.year, "mois": jour.month, "banque": round(banque, 2), "encaisse": round(encaisse, 2)},
|
||||||
|
chemin="/rapports/etat-resultats/adopter",
|
||||||
|
)
|
||||||
|
|
||||||
if index % 6 == 1:
|
if index % 6 == 1:
|
||||||
montant = 32 + (index % 4) * 6
|
montant = 32 + (index % 4) * 6
|
||||||
|
|
@ -329,6 +414,13 @@ async def _ajouter_historique_demo(db: AsyncSession, groupe: Groupe, membres: di
|
||||||
db.add(depense)
|
db.add(depense)
|
||||||
await db.flush()
|
await db.flush()
|
||||||
db.add(TransactionComptable(groupe_id=groupe.id, date_transaction=jour, type="depense", compte=compte, montant=montant, sens="credit", source_table="depenses", source_id=depense.id, note=depense.quoi, cree_par=tresorier.id))
|
db.add(TransactionComptable(groupe_id=groupe.id, date_transaction=jour, type="depense", compte=compte, montant=montant, sens="credit", source_table="depenses", source_id=depense.id, note=depense.quoi, cree_par=tresorier.id))
|
||||||
|
await _trace(
|
||||||
|
db, groupe, tresorier, jour, "depenses", "depense.confirmee",
|
||||||
|
"Dépense soumise puis confirmée par le trésorier.",
|
||||||
|
depense.id,
|
||||||
|
apres={"montant": montant, "methode": methode, "quoi": depense.quoi},
|
||||||
|
chemin="/depenses",
|
||||||
|
)
|
||||||
|
|
||||||
if index % 8 == 2 and litterature:
|
if index % 8 == 2 and litterature:
|
||||||
item = litterature[index % len(litterature)]
|
item = litterature[index % len(litterature)]
|
||||||
|
|
@ -339,6 +431,13 @@ async def _ajouter_historique_demo(db: AsyncSession, groupe: Groupe, membres: di
|
||||||
await db.flush()
|
await db.flush()
|
||||||
encaisse += montant
|
encaisse += montant
|
||||||
db.add(TransactionComptable(groupe_id=groupe.id, date_transaction=jour, type="vente_litterature", compte="encaisse", montant=montant, sens="debit", source_table="ventes_litterature", source_id=vente.id, note="Vente de littérature", cree_par=tresorier.id))
|
db.add(TransactionComptable(groupe_id=groupe.id, date_transaction=jour, type="vente_litterature", compte="encaisse", montant=montant, sens="debit", source_table="ventes_litterature", source_id=vente.id, note="Vente de littérature", cree_par=tresorier.id))
|
||||||
|
await _trace(
|
||||||
|
db, groupe, membres["karim.demo@example.test"], jour, "ventes_litterature", "vente_litterature.recue",
|
||||||
|
"Vente de littérature enregistrée et remise au trésorier.",
|
||||||
|
vente.id,
|
||||||
|
apres={"titre": item.titre, "quantite": 2, "montant": montant},
|
||||||
|
chemin="/ventes-litterature",
|
||||||
|
)
|
||||||
|
|
||||||
if index % 12 == 6:
|
if index % 12 == 6:
|
||||||
montant = 6
|
montant = 6
|
||||||
|
|
@ -358,6 +457,13 @@ async def _ajouter_historique_demo(db: AsyncSession, groupe: Groupe, membres: di
|
||||||
await db.flush()
|
await db.flush()
|
||||||
encaisse += montant
|
encaisse += montant
|
||||||
db.add(TransactionComptable(groupe_id=groupe.id, date_transaction=jour, type="vente_jeton", compte="encaisse", montant=montant, sens="debit", source_table="ventes_jetons", source_id=vente_jeton.id, note="Vente de jetons", cree_par=tresorier.id))
|
db.add(TransactionComptable(groupe_id=groupe.id, date_transaction=jour, type="vente_jeton", compte="encaisse", montant=montant, sens="debit", source_table="ventes_jetons", source_id=vente_jeton.id, note="Vente de jetons", cree_par=tresorier.id))
|
||||||
|
await _trace(
|
||||||
|
db, groupe, membres["melanie.demo@example.test"], jour, "ventes_jetons", "vente_jeton.recue",
|
||||||
|
"Vente de jetons enregistrée et remise au trésorier.",
|
||||||
|
vente_jeton.id,
|
||||||
|
apres={"type_jeton": vente_jeton.type_jeton, "quantite": 2, "montant": montant},
|
||||||
|
chemin="/jetons/ventes",
|
||||||
|
)
|
||||||
|
|
||||||
if index % 13 == 4:
|
if index % 13 == 4:
|
||||||
montant = 125
|
montant = 125
|
||||||
|
|
@ -368,6 +474,13 @@ async def _ajouter_historique_demo(db: AsyncSession, groupe: Groupe, membres: di
|
||||||
db.add(contribution)
|
db.add(contribution)
|
||||||
await db.flush()
|
await db.flush()
|
||||||
db.add(TransactionComptable(groupe_id=groupe.id, date_transaction=jour, type="contribution", compte="banque", montant=montant, sens="credit", source_table="envois_contributions", source_id=contribution.id, note="Contribution au district", cree_par=tresorier.id))
|
db.add(TransactionComptable(groupe_id=groupe.id, date_transaction=jour, type="contribution", compte="banque", montant=montant, sens="credit", source_table="envois_contributions", source_id=contribution.id, note="Contribution au district", cree_par=tresorier.id))
|
||||||
|
await _trace(
|
||||||
|
db, groupe, tresorier, jour, "envois_contributions", "contribution.debitee",
|
||||||
|
"Contribution décidée, engagée et débitée.",
|
||||||
|
contribution.id,
|
||||||
|
apres={"destination": contribution.destination, "montant": montant},
|
||||||
|
chemin="/contributions",
|
||||||
|
)
|
||||||
|
|
||||||
if index in {9, 22, 35}:
|
if index in {9, 22, 35}:
|
||||||
candidat = membres_actifs[index % len(membres_actifs)]
|
candidat = membres_actifs[index % len(membres_actifs)]
|
||||||
|
|
@ -378,9 +491,26 @@ async def _ajouter_historique_demo(db: AsyncSession, groupe: Groupe, membres: di
|
||||||
decision = await _decision(db, groupe, reunion, compteurs, "NOM", jour, rsg, secretaire, f"Nomination de {candidat.prenom}", "Un poste doit être pourvu pour soutenir le groupe.", f"Le groupe nomme {candidat.prenom} au poste de {poste.nom}.")
|
decision = await _decision(db, groupe, reunion, compteurs, "NOM", jour, rsg, secretaire, f"Nomination de {candidat.prenom}", "Un poste doit être pourvu pour soutenir le groupe.", f"Le groupe nomme {candidat.prenom} au poste de {poste.nom}.")
|
||||||
decision.candidature_id = candidature.id
|
decision.candidature_id = candidature.id
|
||||||
candidature.proposition_id = decision.id
|
candidature.proposition_id = decision.id
|
||||||
|
db.add(Affectation(groupe_id=groupe.id, membre_id=candidat.id, poste_id=poste.id, date_debut=jour, statut="actif"))
|
||||||
|
await _trace(
|
||||||
|
db, groupe, rsg, jour, "candidatures", "nomination.adoptee",
|
||||||
|
"Nomination proposée, secondée et adoptée.",
|
||||||
|
candidature.id,
|
||||||
|
apres={"membre": candidat.prenom, "poste": poste.nom, "resolution": decision.numero_resolution},
|
||||||
|
chemin="/postes/candidatures",
|
||||||
|
)
|
||||||
|
|
||||||
if index % 10 == 5:
|
if index % 10 == 5:
|
||||||
db.add(Jeton(groupe_id=groupe.id, reunion_id=reunion.id, membre_id=membres_actifs[index % len(membres_actifs)].id, type_jeton="multiple", date_remise=jour, annees=1 + index % 5, remis_par=membres["melanie.demo@example.test"].id))
|
jeton = Jeton(groupe_id=groupe.id, reunion_id=reunion.id, membre_id=membres_actifs[index % len(membres_actifs)].id, type_jeton="multiple", date_remise=jour, annees=1 + index % 5, remis_par=membres["melanie.demo@example.test"].id, cree_le=_moment(jour, 20, 5))
|
||||||
|
db.add(jeton)
|
||||||
|
await db.flush()
|
||||||
|
await _trace(
|
||||||
|
db, groupe, membres["melanie.demo@example.test"], jour, "jetons", "jeton.remis",
|
||||||
|
"Remise de jeton enregistrée pour statistiques.",
|
||||||
|
jeton.id,
|
||||||
|
apres={"annees": jeton.annees},
|
||||||
|
chemin="/jetons",
|
||||||
|
)
|
||||||
|
|
||||||
if index % 17 == 7:
|
if index % 17 == 7:
|
||||||
reserve = reserves[["Loyer", "Littérature", "Prudentielle"][index % 3]]
|
reserve = reserves[["Loyer", "Littérature", "Prudentielle"][index % 3]]
|
||||||
|
|
@ -388,6 +518,14 @@ async def _ajouter_historique_demo(db: AsyncSession, groupe: Groupe, membres: di
|
||||||
if banque >= montant:
|
if banque >= montant:
|
||||||
reserve.montant = float(reserve.montant or 0) + montant
|
reserve.montant = float(reserve.montant or 0) + montant
|
||||||
db.add(MouvementReserve(groupe_id=groupe.id, source_id=None, destination_id=reserve.id, montant=montant, type="reserve_virement", note="Affectation au solde réservé", cree_par=tresorier.id, cree_le=datetime.combine(jour, time(21, 0)).replace(tzinfo=timezone.utc)))
|
db.add(MouvementReserve(groupe_id=groupe.id, source_id=None, destination_id=reserve.id, montant=montant, type="reserve_virement", note="Affectation au solde réservé", cree_par=tresorier.id, cree_le=datetime.combine(jour, time(21, 0)).replace(tzinfo=timezone.utc)))
|
||||||
|
await _trace(
|
||||||
|
db, groupe, tresorier, jour, "mouvements_reserves", "reserve.transfert",
|
||||||
|
"Montant transféré du disponible vers une réserve.",
|
||||||
|
reserve.id,
|
||||||
|
avant={"reserve": round(float(reserve.montant or 0) - montant, 2)},
|
||||||
|
apres={"reserve": round(float(reserve.montant or 0), 2), "montant": montant},
|
||||||
|
chemin="/operations-bancaires/reserves/transfert",
|
||||||
|
)
|
||||||
|
|
||||||
db.add(Collecte(groupe_id=groupe.id, date_collecte=dates_reunions[-1] + timedelta(days=7), montant=96, detenteur_id=membres["luc.demo@example.test"].id, statut="en_attente", saisi_par=membres["luc.demo@example.test"].id))
|
db.add(Collecte(groupe_id=groupe.id, date_collecte=dates_reunions[-1] + timedelta(days=7), montant=96, detenteur_id=membres["luc.demo@example.test"].id, statut="en_attente", saisi_par=membres["luc.demo@example.test"].id))
|
||||||
db.add(Depense(groupe_id=groupe.id, date_depense=date.today(), montant=48, qui=membres["anne.demo@example.test"].id, quoi="Fournitures à confirmer", paye=False, methode=None, saisi_par=membres["anne.demo@example.test"].id))
|
db.add(Depense(groupe_id=groupe.id, date_depense=date.today(), montant=48, qui=membres["anne.demo@example.test"].id, quoi="Fournitures à confirmer", paye=False, methode=None, saisi_par=membres["anne.demo@example.test"].id))
|
||||||
|
|
@ -414,12 +552,65 @@ async def _decision(db, groupe, reunion, compteurs, prefixe, jour, proposeur, se
|
||||||
date_adoption=jour,
|
date_adoption=jour,
|
||||||
etant_donne_que=attendu,
|
etant_donne_que=attendu,
|
||||||
groupe_a_decide_de=decide,
|
groupe_a_decide_de=decide,
|
||||||
|
cree_le=_moment(jour, 20, 30),
|
||||||
|
modifie_le=_moment(jour, 20, 45),
|
||||||
)
|
)
|
||||||
db.add(proposition)
|
db.add(proposition)
|
||||||
await db.flush()
|
await db.flush()
|
||||||
|
await _trace(
|
||||||
|
db, groupe, proposeur, jour, "propositions", f"decision.{prefixe.lower()}",
|
||||||
|
"Décision adoptée par la conscience de groupe.",
|
||||||
|
proposition.id,
|
||||||
|
apres={"numero": numero, "sujet": sujet, "decision": decide},
|
||||||
|
chemin="/propositions/voter",
|
||||||
|
)
|
||||||
return proposition
|
return proposition
|
||||||
|
|
||||||
|
|
||||||
|
def _moment(jour: date, heure: int = 20, minute: int = 0) -> datetime:
|
||||||
|
return datetime.combine(jour, time(heure, minute)).replace(tzinfo=timezone.utc)
|
||||||
|
|
||||||
|
|
||||||
|
async def _trace(
|
||||||
|
db: AsyncSession,
|
||||||
|
groupe: Groupe,
|
||||||
|
membre: Membre,
|
||||||
|
jour: date,
|
||||||
|
table: str,
|
||||||
|
action: str,
|
||||||
|
raison: str,
|
||||||
|
objet_id=None,
|
||||||
|
avant: dict | None = None,
|
||||||
|
apres: dict | None = None,
|
||||||
|
chemin: str | None = None,
|
||||||
|
):
|
||||||
|
moment = _moment(jour, 21, 50)
|
||||||
|
cible = objet_id or groupe.id
|
||||||
|
db.add(HistoriqueModification(
|
||||||
|
groupe_id=groupe.id,
|
||||||
|
table_cible=table,
|
||||||
|
objet_id=cible,
|
||||||
|
action=action,
|
||||||
|
raison=raison,
|
||||||
|
membre_id=membre.id if membre else None,
|
||||||
|
chemin_api=chemin,
|
||||||
|
avant=avant,
|
||||||
|
apres=apres,
|
||||||
|
cree_le=moment,
|
||||||
|
))
|
||||||
|
db.add(JournalAction(
|
||||||
|
groupe_id=groupe.id,
|
||||||
|
membre_id=membre.id if membre else None,
|
||||||
|
methode="POST",
|
||||||
|
chemin=chemin or f"/demo/{table}",
|
||||||
|
statut_http=200,
|
||||||
|
adresse_ip="127.0.0.1",
|
||||||
|
user_agent="DemoSeeder/1.0",
|
||||||
|
details={"action": action, "raison": raison},
|
||||||
|
cree_le=moment,
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
def _premier_mardi(jour: date) -> date:
|
def _premier_mardi(jour: date) -> date:
|
||||||
delta = (1 - jour.weekday()) % 7
|
delta = (1 - jour.weekday()) % 7
|
||||||
return jour + timedelta(days=delta)
|
return jour + timedelta(days=delta)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
"""
|
"""
|
||||||
Script d'initialisation — Groupe Méditation
|
Script d'initialisation — Groupe Résilience
|
||||||
Crée les postes, le groupe initial et le premier membre.
|
Crée les postes, le groupe initial et le premier membre.
|
||||||
Usage: cd /opt/groupe-meditation/backend && venv/bin/python ../deploy/seed.py
|
Usage: cd /opt/groupe-meditation/backend && venv/bin/python ../deploy/seed.py
|
||||||
"""
|
"""
|
||||||
|
|
@ -92,14 +92,14 @@ async def seed():
|
||||||
|
|
||||||
async with async_session() as db:
|
async with async_session() as db:
|
||||||
# ── Créer le groupe ──
|
# ── Créer le groupe ──
|
||||||
print("\n═══ Initialisation du Groupe Méditation ═══\n")
|
print("\n═══ Initialisation du Groupe Résilience ═══\n")
|
||||||
groupe = Groupe(
|
groupe = Groupe(
|
||||||
nom="Groupe Méditation",
|
nom="Groupe Résilience",
|
||||||
district="87-16",
|
district="87-16",
|
||||||
region="87",
|
region="87",
|
||||||
adresse_local="",
|
adresse_local="Salle communautaire Saint-Louis, Montréal",
|
||||||
jour_reunion="mardi",
|
jour_reunion="mardi",
|
||||||
heure_reunion=None,
|
heure_reunion=datetime.strptime("19:30", "%H:%M").time(),
|
||||||
format_reunion="Discussion ouverte",
|
format_reunion="Discussion ouverte",
|
||||||
lien_aa87="https://aa87.org",
|
lien_aa87="https://aa87.org",
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,13 @@ const COULEURS = {
|
||||||
|
|
||||||
const NAVIGABLE = ['C7', 'S1', 'S1T', 'S2', 'S3', 'S4', 'S5', 'C1', 'C2', 'C4', 'C5', 'C9', 'C11', 'C12', 'G2', 'G9', 'G11', 'G12', 'G13', 'M0', 'M1', 'M2', 'M3', 'M4', 'M5', 'M6'];
|
const NAVIGABLE = ['C7', 'S1', 'S1T', 'S2', 'S3', 'S4', 'S5', 'C1', 'C2', 'C4', 'C5', 'C9', 'C11', 'C12', 'G2', 'G9', 'G11', 'G12', 'G13', 'M0', 'M1', 'M2', 'M3', 'M4', 'M5', 'M6'];
|
||||||
const MODULES_CONTEXTE = new Set(['S0', 'M0', 'M1', 'M2']);
|
const MODULES_CONTEXTE = new Set(['S0', 'M0', 'M1', 'M2']);
|
||||||
|
const MODULE_INVENTAIRES = {
|
||||||
|
code: 'M5',
|
||||||
|
nom: 'Gestion des inventaires',
|
||||||
|
description: 'Littérature et jetons',
|
||||||
|
icone: '📦',
|
||||||
|
couleur: 'orange',
|
||||||
|
};
|
||||||
|
|
||||||
const CONTEXTES = {
|
const CONTEXTES = {
|
||||||
accueil: {
|
accueil: {
|
||||||
|
|
@ -41,7 +48,7 @@ const CONTEXTES = {
|
||||||
sections: [
|
sections: [
|
||||||
{ titre: 'Finances', codes: ['S1T', 'S5', 'M4'] },
|
{ titre: 'Finances', codes: ['S1T', 'S5', 'M4'] },
|
||||||
{ titre: 'Membres et structure', codes: ['G2', 'G11', 'G12', 'G9', 'G13'] },
|
{ titre: 'Membres et structure', codes: ['G2', 'G11', 'G12', 'G9', 'G13'] },
|
||||||
{ titre: 'Inventaire', codes: ['M5', 'M6'] },
|
{ titre: 'Inventaire', codes: ['M5'] },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
@ -96,6 +103,7 @@ export default function Accueil() {
|
||||||
const moduleParCode = useMemo(() => {
|
const moduleParCode = useMemo(() => {
|
||||||
const map = new Map();
|
const map = new Map();
|
||||||
tousModules.forEach((m) => map.set(m.code, m));
|
tousModules.forEach((m) => map.set(m.code, m));
|
||||||
|
if (map.has('M5') || map.has('M6')) map.set('M5', MODULE_INVENTAIRES);
|
||||||
return map;
|
return map;
|
||||||
}, [tousModules]);
|
}, [tousModules]);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ const CATEGORIES = [
|
||||||
|
|
||||||
export default function AjusterInventaire() {
|
export default function AjusterInventaire() {
|
||||||
const [items, setItems] = useState([]);
|
const [items, setItems] = useState([]);
|
||||||
|
const [jetons, setJetons] = useState([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [success, setSuccess] = useState('');
|
const [success, setSuccess] = useState('');
|
||||||
const [error, setError] = useState('');
|
const [error, setError] = useState('');
|
||||||
|
|
@ -19,9 +20,16 @@ export default function AjusterInventaire() {
|
||||||
|
|
||||||
const charger = async () => {
|
const charger = async () => {
|
||||||
try {
|
try {
|
||||||
const data = await api.get('/litterature/');
|
const [data, stockJetons] = await Promise.all([
|
||||||
|
api.get('/litterature/'),
|
||||||
|
api.get('/commandes-jetons/stock'),
|
||||||
|
]);
|
||||||
setItems(data);
|
setItems(data);
|
||||||
} catch { setItems([]); }
|
setJetons(stockJetons);
|
||||||
|
} catch {
|
||||||
|
setItems([]);
|
||||||
|
setJetons([]);
|
||||||
|
}
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -43,9 +51,16 @@ export default function AjusterInventaire() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gray-100">
|
<div className="min-h-screen bg-gray-100">
|
||||||
<PageHeader titre="Ajuster inventaire" sousTitre="M5 — Stock littérature" />
|
<PageHeader titre="Gestion des inventaires" sousTitre="Littérature et jetons" />
|
||||||
|
|
||||||
<div className="p-4 space-y-4">
|
<div className="p-4 space-y-4">
|
||||||
|
<section className="space-y-2">
|
||||||
|
<h2 className="text-xs font-bold text-gray-400 uppercase tracking-wider px-1">Jetons</h2>
|
||||||
|
<StockJetons stock={jetons} loading={loading} />
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="space-y-2">
|
||||||
|
<h2 className="text-xs font-bold text-gray-400 uppercase tracking-wider px-1">Littérature</h2>
|
||||||
{/* Bouton ajouter un titre */}
|
{/* Bouton ajouter un titre */}
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowAjout(!showAjout)}
|
onClick={() => setShowAjout(!showAjout)}
|
||||||
|
|
@ -71,11 +86,62 @@ export default function AjusterInventaire() {
|
||||||
<p className="text-gray-400">Aucun titre dans le catalogue</p>
|
<p className="text-gray-400">Aucun titre dans le catalogue</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function StockJetons({ stock, loading }) {
|
||||||
|
const aCommander = stock.filter((s) => s.stock_suggestion !== 'OK');
|
||||||
|
if (loading) return null;
|
||||||
|
return (
|
||||||
|
<div className="space-y-2">
|
||||||
|
{aCommander.length > 0 && (
|
||||||
|
<div className="bg-orange-50 border-2 border-orange-300 rounded-2xl p-4">
|
||||||
|
<p className="font-bold text-orange-700 text-sm mb-2">
|
||||||
|
Jetons à commander ({aCommander.length})
|
||||||
|
</p>
|
||||||
|
<div className="space-y-1">
|
||||||
|
{aCommander.map((s) => (
|
||||||
|
<p key={s.type_jeton} className="text-sm text-orange-600">
|
||||||
|
{s.label} — {s.remis_dernier_mois} remis ce mois
|
||||||
|
</p>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{stock.map((s) => (
|
||||||
|
<div
|
||||||
|
key={s.type_jeton}
|
||||||
|
className={`bg-white rounded-2xl p-4 shadow-sm flex items-center gap-3 border-2 ${
|
||||||
|
s.stock_suggestion !== 'OK' ? 'border-orange-300' : 'border-gray-100'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<span className="text-2xl">{s.label.split(' ')[0]}</span>
|
||||||
|
<div className="flex-1 min-w-0">
|
||||||
|
<p className="font-bold text-sm">{s.label.split(' ').slice(1).join(' ')}</p>
|
||||||
|
<p className="text-xs text-gray-400">{s.remis_dernier_mois} remis ou vendu récemment</p>
|
||||||
|
</div>
|
||||||
|
<span className={`text-xs font-bold px-3 py-1 rounded-full ${
|
||||||
|
s.stock_suggestion === 'OK'
|
||||||
|
? 'bg-green-100 text-green-700'
|
||||||
|
: 'bg-orange-100 text-orange-700'
|
||||||
|
}`}>
|
||||||
|
{s.stock_suggestion}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
{stock.length === 0 && (
|
||||||
|
<div className="text-center py-8 bg-white rounded-2xl shadow-sm">
|
||||||
|
<span className="text-4xl block mb-2">🏅</span>
|
||||||
|
<p className="text-gray-400">Aucune donnée de jetons pour le moment</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function ItemStock({ item, onAjuster }) {
|
function ItemStock({ item, onAjuster }) {
|
||||||
const [ajustement, setAjustement] = useState('');
|
const [ajustement, setAjustement] = useState('');
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -86,7 +86,6 @@ export default function AssembleeAffaires({ contexte = false }) {
|
||||||
secondeur_id: f.secondeur_id || m[1]?.id || '',
|
secondeur_id: f.secondeur_id || m[1]?.id || '',
|
||||||
}));
|
}));
|
||||||
if (!membreProposeId && m[0]) setMembreProposeId(m[0].id);
|
if (!membreProposeId && m[0]) setMembreProposeId(m[0].id);
|
||||||
if (!ouverteId && r.length) setOuverteId(r[0].id);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setError(e.message);
|
setError(e.message);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue