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/consoleDécoupe service-oriented. L'orchestrateur ne fait aucune écriture financière directe — chaque service possède sa responsabilité.
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
Entités
SolidoumeParameter — la caisse
Table solidoume_parameter. Indexée sur enabled et execution_date.
Champs notables :
| Champ | Type Doctrine | Rôle |
|---|---|---|
id | uuid | UUID v4. |
name / description | string / text | Métadonnées publiques. |
distributionMode | string | calculated · fixed · paliers. |
cotisationType | string | free · suggested · fixed_min · restricted. |
cotisationTolerancePercent | integer | Pour type suggested uniquement. |
cotisationRestrictedChoices | string | CSV de montants pour type restricted. |
minimum / maximum | decimal | Bornes de cotisation (legacy DOUBLE — ne pas reproduire). |
amountPerParticipant | decimal | Cible pour calculated/fixed. |
tiersRules | json | Schéma [{minPersons, maxPersons, amount}]. |
commission | decimal | Ponctionnée sur la caisse à la redistribution. |
executionDate | integer | Jour mensuel de clôture (1–28). |
reminderDays | integer | Délai du rappel cotisation. |
allowSepaPayment | boolean | Autorise les RecurringSsaSubscriptionRequest. |
sepaMandatoryDebitDay | integer | Jour de prélèvement SEPA imposé (1–28) ; sinon valeur de l'item. |
requireCotisationToParticipate | boolean | Sort les non-cotisants de la redistribution. |
manager | ManyToOne Prestataire | Responsable de la caisse. |
admins | ManyToMany User | Admins additionnels. |
prestataireSolidoumeParameters | OneToMany | Affiliations prestataires (vide ⇒ caisse principale). |
allowDecimalHouseholdSize | boolean | Permet 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 :
| Champ | Type | Rôle |
|---|---|---|
amount | decimal | Montant cotisé. |
minimumCotisation | decimal | Minimum effectif au moment de la souscription (gel). |
isRecurrent | boolean | Rejouée chaque mois sans nouvelle action. |
paiementDate | integer | Jour de prélèvement SEPA (1–28, défaut 5). |
isDonation | boolean | Cotise sans recevoir la redistribution. |
autopayWithSsa | boolean | Autorise paiement d'un achat SSA avec le solde redistribué. |
canAutoCompleteFromMainAccount | boolean | Ponction auto sur le compte eMLc si solde insuffisant. |
lastMonthPayed | string(6) | Idempotence mensuelle, format YYYYMM. |
archived | datetime? | Soft-delete daté. |
paymentMethod | string | eMLc / HelloAsso / SEPA. |
solidoumeParameter | ManyToOne | Caisse cible (FK onDelete=RESTRICT). |
adherent | ManyToOne | Adhérent souscripteur. |
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)
| Constante | Valeur DB | Stratégie |
|---|---|---|
DISTRIBUTION_MODE_CALCULATED | calculated | CalculatedStrategy |
DISTRIBUTION_MODE_FIXED | fixed | FixedStrategy |
DISTRIBUTION_MODE_TIERS | paliers | TiersStrategy |
Types de cotisation (cotisationType)
| Constante | Valeur DB | Effet sur la saisie |
|---|---|---|
COTISATION_TYPE_FREE | free | Entre minimum et maximum. |
COTISATION_TYPE_SUGGESTED | suggested | amountPerParticipant ± cotisationTolerancePercent %. |
COTISATION_TYPE_FIXED_MIN | fixed_min | Plancher imposé, surplus libre. |
COTISATION_TYPE_RESTRICTED | restricted | Choix 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
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)
| Étape | Pattern |
|---|---|
| Prélèvement cotisation | Solidoume prélèvement {month} |
| Cotisation participant | Solidoume cotisation {month} |
| Redistribution mensuelle | Solidoume {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
- Lorsque
SolidoumeParameter::allowSepaPayment === true, unRecurringSsaSubscriptionRequestpeut être lié à unSepaMandateactif. - Le jour de prélèvement effectif est :
sepaMandatoryDebitDayde la caisse s'il est défini, sinonSolidoumeItem::paiementDate(défaut5). - L'annulation atomique d'un mandat révoque toutes les requêtes
récurrentes liées dans la même transaction (cf.
doc/DOMAIN.md).
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 :
- Publique : nom de caisse, prestataires affiliés, totaux agrégés.
- Restreinte : liste des participants, montants moyens.
Accès
ROLE_ADMIN_SOLIDOUME_LIST. - Sensible : montants individuels, taille du foyer, IBAN.
Accès
ROLE_SOLIDOUMEouROLE_SUPER_ADMINavec justification tracée.
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
src/Entity/SolidoumeParameter.php,src/Entity/SolidoumeItem.phpsrc/Service/Ssa/*etsrc/Service/Ssa/Strategy/*src/Command/SSACommandRefactored.php,SsaFormerParticipantReminderCommand.phpdocs/rgpd/GUIDE_SSA.md— accès aux données SSA.claude/rules/money.md— règles d'atomicité & idempotence- Doc utilisateur — Les caisses SSA