groupe-meditation/backend/app/services/finance.py

117 lines
4.3 KiB
Python

"""Services financiers: registre comptable, soldes et réserves."""
from sqlalchemy import desc, func, select
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.financial_position import PositionTresorerie
from app.models.depense import Depense
from app.models.envoi_contribution import EnvoiContribution
from app.models.mouvement_reserve import MouvementReserve
from app.models.reserve import Reserve
from app.models.transaction_comptable import TransactionComptable
async def solde_compte(groupe_id, compte: str, db: AsyncSession) -> float:
debits = float((await db.execute(
select(func.coalesce(func.sum(TransactionComptable.montant), 0)).where(
TransactionComptable.groupe_id == groupe_id,
TransactionComptable.compte == compte,
TransactionComptable.sens == "debit",
)
)).scalar())
credits = float((await db.execute(
select(func.coalesce(func.sum(TransactionComptable.montant), 0)).where(
TransactionComptable.groupe_id == groupe_id,
TransactionComptable.compte == compte,
TransactionComptable.sens == "credit",
)
)).scalar())
return debits - credits
async def encaisse(groupe_id, db: AsyncSession) -> float:
return await solde_compte(groupe_id, "encaisse", db)
async def solde_banque(groupe_id, db: AsyncSession) -> float:
return await solde_compte(groupe_id, "banque", db)
async def engagements_non_debites(groupe_id, db: AsyncSession) -> float:
depenses = float((await db.execute(
select(func.coalesce(func.sum(Depense.montant), 0)).where(
Depense.groupe_id == groupe_id,
Depense.paye == False,
Depense.methode.in_(["VIREMENT", "CHEQUE"]),
)
)).scalar())
contributions = float((await db.execute(
select(func.coalesce(func.sum(EnvoiContribution.montant), 0)).where(
EnvoiContribution.groupe_id == groupe_id,
EnvoiContribution.engagee == True,
EnvoiContribution.debitee == False,
)
)).scalar())
return depenses + contributions
async def solde_reserve(reserve_id, db: AsyncSession) -> float:
entrees = float((await db.execute(
select(func.coalesce(func.sum(MouvementReserve.montant), 0)).where(
MouvementReserve.destination_id == reserve_id,
)
)).scalar())
sorties = float((await db.execute(
select(func.coalesce(func.sum(MouvementReserve.montant), 0)).where(
MouvementReserve.source_id == reserve_id,
)
)).scalar())
return entrees - sorties
async def total_reserves(groupe_id, db: AsyncSession, exclure_id: str | None = None) -> float:
result = await db.execute(select(Reserve).where(Reserve.groupe_id == groupe_id, Reserve.active == True))
total = 0.0
for reserve in result.scalars().all():
if exclure_id and str(reserve.id) == str(exclure_id):
continue
total += await solde_reserve(reserve.id, db)
return total
async def reserve_disponible(groupe_id, db: AsyncSession) -> float:
return (await position_tresorerie(groupe_id, db)).disponible_bancaire
async def position_tresorerie(groupe_id, db: AsyncSession) -> PositionTresorerie:
return PositionTresorerie(
solde_banque=await solde_banque(groupe_id, db),
encaisse=await encaisse(groupe_id, db),
total_reserves=await total_reserves(groupe_id, db),
engagements_non_debites=await engagements_non_debites(groupe_id, db),
)
async def dernier_releve(groupe_id, db: AsyncSession):
from app.models.operation_bancaire import OperationBancaire
result = await db.execute(
select(OperationBancaire).where(
OperationBancaire.groupe_id == groupe_id,
OperationBancaire.type == "releve",
).order_by(desc(OperationBancaire.date_operation)).limit(1)
)
return result.scalar_one_or_none()
def transaction(groupe_id, membre_id, date_transaction, type_, compte, montant, sens, source_table=None, source_id=None, note=None):
return TransactionComptable(
groupe_id=groupe_id,
date_transaction=date_transaction,
type=type_,
compte=compte,
montant=montant,
sens=sens,
source_table=source_table,
source_id=source_id,
note=note,
cree_par=membre_id,
)