documentation
Kohinos v3 Doc générée le commit 8d60b1904

SSA — implémentation

La SSA (Sécurité Sociale de l'Alimentation, dite SSA) est un dispositif multi-caisses de cotisation et redistribution mensuelle. Côté code, elle s'articule autour de deux entités (SolidoumeParameter, SolidoumeItem), d'un orchestrateur, d'un strategy pattern pour la redistribution, et d'une commande Symfony invoquée par cron.

Architecture des services

flowchart TB
  CMD["bin/console
kohinos:ssa:execute"] --> ORC[SsaOrchestrator] ORC --> REM[SsaReminderService] ORC --> TAK[SsaTakingService] ORC --> RED[SsaRedistributionService] ORC --> CAL[SsaCalculationService] ORC --> DATE[SsaDateService] ORC --> REP[SsaReportService] CAL --> FAC{CalculationStrategyFactory} FAC --> S1[CalculatedStrategy] FAC --> S2[FixedStrategy] FAC --> S3[TiersStrategy] TAK -.persist.-> DB[(Flux + Operation)] RED -.PESSIMISTIC_WRITE.-> DB
Découpe service-oriented. L'orchestrateur ne fait aucune écriture financière directe — chaque service possède sa responsabilité.

Entités

SolidoumeParameter — la caisse

Table solidoume_parameter. Indexée sur enabled et execution_date. Champs notables :

ChampType DoctrineRôle
iduuidUUID v4.
name / descriptionstring / textMétadonnées publiques.
distributionModestringcalculated · fixed · paliers.
cotisationTypestringfree · suggested · fixed_min · restricted.
cotisationTolerancePercentintegerPour type suggested uniquement.
cotisationRestrictedChoicesstringCSV de montants pour type restricted.
minimum / maximumdecimalBornes de cotisation (legacy DOUBLE — ne pas reproduire).
amountPerParticipantdecimalCible pour calculated/fixed.
tiersRulesjsonSchéma [{minPersons, maxPersons, amount}].
commissiondecimalPonctionnée sur la caisse à la redistribution.
executionDateintegerJour mensuel de clôture (1–28).
reminderDaysintegerDélai du rappel cotisation.
allowSepaPaymentbooleanAutorise les RecurringSsaSubscriptionRequest.
sepaMandatoryDebitDayintegerJour de prélèvement SEPA imposé (1–28) ; sinon valeur de l'item.
requireCotisationToParticipatebooleanSort les non-cotisants de la redistribution.
managerManyToOne PrestataireResponsable de la caisse.
adminsManyToMany UserAdmins additionnels.
prestataireSolidoumeParametersOneToManyAffiliations prestataires (vide ⇒ caisse principale).
allowDecimalHouseholdSizebooleanPermet 1,5 personne (garde alternée) pour paliers.

Méthode pivot : isMainSsa(): bool — retourne true si la liste des prestataires affiliés est vide. Cohérent avec la convention : caisse principale = caisse appliquée à tous les achats de la MLCC.

SolidoumeItem — l'engagement

Table solidoume_item. ApiResource protégé par ROLE_ADMIN_SOLIDOUME_LIST / ROLE_ADMIN_SOLIDOUME_EDIT. Index sur enabled, archived, is_recurrent, paiement_date. Champs notables :

ChampTypeRôle
amountdecimalMontant cotisé.
minimumCotisationdecimalMinimum effectif au moment de la souscription (gel).
isRecurrentbooleanRejouée chaque mois sans nouvelle action.
paiementDateintegerJour de prélèvement SEPA (1–28, défaut 5).
isDonationbooleanCotise sans recevoir la redistribution.
autopayWithSsabooleanAutorise paiement d'un achat SSA avec le solde redistribué.
canAutoCompleteFromMainAccountbooleanPonction auto sur le compte eMLc si solde insuffisant.
lastMonthPayedstring(6)Idempotence mensuelle, format YYYYMM.
archiveddatetime?Soft-delete daté.
paymentMethodstringeMLc / HelloAsso / SEPA.
solidoumeParameterManyToOneCaisse cible (FK onDelete=RESTRICT).
adherentManyToOneAdhérent souscripteur.
⚠ Anti-rejeu mensuel

Avant tout prélèvement, SsaTakingService compare $item->getLastMonthPayed() au mois en cours. Si égal, l'item est sauté. L'invariant DB ssa_no_duplicate_monthly_collection confirme la propriété :

SELECT adherent_id, solidoume_parameter_id, YEAR(created_at), MONTH(created_at)
FROM flux WHERE solidoume_parameter_id IS NOT NULL
GROUP BY 1,2,3,4 HAVING COUNT(*) > 1;
-- doit toujours retourner 0 lignes

Constantes & modes

Modes de redistribution (distributionMode)

ConstanteValeur DBStratégie
DISTRIBUTION_MODE_CALCULATEDcalculatedCalculatedStrategy
DISTRIBUTION_MODE_FIXEDfixedFixedStrategy
DISTRIBUTION_MODE_TIERSpaliersTiersStrategy

Types de cotisation (cotisationType)

ConstanteValeur DBEffet sur la saisie
COTISATION_TYPE_FREEfreeEntre minimum et maximum.
COTISATION_TYPE_SUGGESTEDsuggestedamountPerParticipant ± cotisationTolerancePercent %.
COTISATION_TYPE_FIXED_MINfixed_minPlancher imposé, surplus libre.
COTISATION_TYPE_RESTRICTEDrestrictedChoix dans cotisationRestrictedChoices (CSV).

Schéma tiersRules

// JSON dans solidoume_parameter.tiers_rules
[
  { "minPersons": 1, "maxPersons": 1, "amount": "100.00" },
  { "minPersons": 2, "maxPersons": 2, "amount": "150.00" },
  { "minPersons": 3, "maxPersons": null, "amount": "200.00" }
]

Strategy pattern de calcul

Toutes les stratégies implémentent RedistributionStrategyInterface :

interface RedistributionStrategyInterface
{
    public function computeBasePerParticipant(
        SolidoumeParameter $ssa,
        array $eligibleItems,
        string $totalFund   // BigDecimal-string, jamais float
    ): array;
}

CalculationStrategyFactory::build($ssa) route sur le bon service :

match ($ssa->getDistributionMode()) {
    SolidoumeParameter::DISTRIBUTION_MODE_CALCULATED => $this->calculated,
    SolidoumeParameter::DISTRIBUTION_MODE_FIXED      => $this->fixed,
    SolidoumeParameter::DISTRIBUTION_MODE_TIERS      => $this->tiers,
};

Flux d'exécution de la commande

sequenceDiagram
    autonumber
    participant Cron
    participant Cmd as SSACommandRefactored
    participant Orc as SsaOrchestrator
    participant Rem as SsaReminderService
    participant Tak as SsaTakingService
    participant Red as SsaRedistributionService
    participant DB
    Cron->>Cmd: kohinos:ssa:execute
    Cmd->>Orc: execute(options)
    Orc->>Rem: sendRemindersIfDue(ssa)
    Orc->>Tak: takeContributionsForToday(ssa)
    Tak->>DB: wrapInTransaction + Flux + Operation
    Note over Orc: si jour == executionDate
    Orc->>Red: redistribute(ssa)
    Red->>DB: PESSIMISTIC_WRITE sur chaque AccountAdherent
    Red->>DB: Flux Solidoume + Operations crédit
    Orc->>Cmd: SsaExecutionResult
    Cmd->>Cron: exit 0
    
L'orchestrateur n'écrit jamais directement ; il délègue à des services dont la transactionnalité est garantie.

Atomicité financière

SsaRedistributionService est aujourd'hui la référence en matière d'atomicité dans le projet (cf. .claude/rules/money.md) — c'est le code à imiter sur tout nouveau service financier :

$this->em->wrapInTransaction(function () use ($accountId, $amount, $flux) {
    $account = $this->em->find(
        AccountAdherent::class,
        $accountId,
        LockMode::PESSIMISTIC_WRITE
    );

    $account->setBalance(bcadd((string) $account->getBalance(), $amount, 2));

    $this->em->persist(new OperationAdherent($flux, $account, $amount));
});

Détection des flux SSA

⚠ Règle non négociable

Un flux SSA est identifié uniquement par flux.solidoume_parameter_id IS NOT NULL. Jamais par parenttype, discr, ni par le contenu de reference — même si la convention de référence est connue.

// ✅ correct
$qb->andWhere('f.solidoumeParameter IS NOT NULL');

// ❌ interdit
$qb->andWhere("f.reference LIKE 'Solidoume%'");
$qb->andWhere('f.parenttype = :type')->setParameter('type', 'ssa');

Patterns de flux.reference (informatif uniquement)

ÉtapePattern
Prélèvement cotisationSolidoume prélèvement {month}
Cotisation participantSolidoume cotisation {month}
Redistribution mensuelleSolidoume {month}

Commande Symfony

# Exécution réelle (cron mensuel)
php bin/console kohinos:ssa:execute

# Simulation sans persistance / sans email
php bin/console kohinos:ssa:execute --test

# Forcer l'exécution un jour qui n'est pas executionDate
php bin/console kohinos:ssa:execute --execute --simulateDay=15

# Simuler un mois/an différent (régression historique)
php bin/console kohinos:ssa:execute --simulateMonth=4 --simulateYear=2026

# N'exécuter QUE la phase de prélèvement, pas la redistribution
php bin/console kohinos:ssa:execute --onlytaking

# Export JSON du rapport structuré
php bin/console kohinos:ssa:execute --output-json=var/log/ssa-report.json

# Rappel des anciens participants avant clôture
php bin/console kohinos:ssa:send-former-participant-alerts

Intégration SEPA

Emails & placeholders

Les templates par caisse : reminderEmail, confirmEmail, postRedistributionEmail, formerParticipantReminderEmail. Variables substituées : %name%, %amount%, %executionDate%, %daysBefore%, %ssa%.

RGPD & visibilité

Voir docs/rgpd/GUIDE_SSA.md pour les trois zones :

Tests cibles

# Suite ciblée SSA
vendor/bin/phpunit --testsuite=ssa

# Tests stratégies de calcul
vendor/bin/phpunit tests/Service/Ssa/Strategy/

# Tests de bout en bout commande
vendor/bin/phpunit tests/Command/SSACommandRefactoredTest.php

# Vérification des invariants financiers
php bin/ai-workflows/financial/check-invariants.php --env=test

Pour aller plus loin

Sources
  • src/Entity/SolidoumeParameter.php, src/Entity/SolidoumeItem.php
  • src/Service/Ssa/* et src/Service/Ssa/Strategy/*
  • src/Command/SSACommandRefactored.php, SsaFormerParticipantReminderCommand.php
  • docs/rgpd/GUIDE_SSA.md — accès aux données SSA
  • .claude/rules/money.md — règles d'atomicité & idempotence
  • Doc utilisateur — Les caisses SSA