Récurrences et rappels
Génération automatique des factures régulières, échelle de rappels, avis de retard et intérêts.
Trois tâches quotidiennes, enregistrées dans core/cron.
| Tâche | Heure | Rôle |
|---|---|---|
billing-interest | 01 h 05 | Accumule les intérêts sur les factures en retard. |
billing-recurrences | 06 h 30 | Produit les factures récurrentes dues. |
billing-reminders | 09 h 00 | Envoie les rappels et les avis de retard. |
L'ordre dans la journée compte pour les intérêts : ils s'accumulent avant que les rappels ne citent un solde.
Le verrou
schedule() garde ses tâches sur globalThis : chaque instance de serveur enregistre et déclenche sa propre copie. Trois instances enverraient trois copies de chaque rappel.
Chaque tâche prend donc un verrou en base avant de travailler, sur lrvo_billing_job_lock. L'instance qui l'obtient travaille, les autres passent leur tour en le journalisant. Le verrou porte une durée de vie, ce qui fait qu'une instance qui plante en cours de route ne bloque pas la tâche indéfiniment.
Un verrou consultatif Postgres serait le mauvais outil : query() tire un client arbitraire du pool, donc la prise et la libération pourraient atterrir sur des sessions différentes.
Récurrences
Une récurrence est un gabarit de lignes, plus une cadence.
| Réglage | Rôle |
|---|---|
| Fréquence | Tous les n jours, semaines, mois ou ans. |
| Jour de récurrence | Pour une cadence mensuelle ou annuelle, le jour du mois visé. Plafonné à 28. |
| Création | Combien de jours avant l'échéance la facture est produite. |
| Émission automatique | Émet et envoie, ou laisse la facture en brouillon. |
| Fin | Une date de fin, un nombre d'occurrences, ou aucune limite. |
Le plafond à 28 supprime la question de février : aucune cadence mensuelle ne peut déborder sur le mois suivant.
La fenêtre de génération s'ouvre au délai de création et ne se referme pas. Une panne de tâche de dix jours retarde une facture, elle ne saute pas une période. Le curseur next_due_date n'avance qu'une fois la facture réellement créée, donc un échec laisse la récurrence retenter au passage suivant plutôt que de perdre un mois de facturation.
L'éditeur affiche les trois prochaines échéances : une fréquence combinée à un jour d'ancrage se visualise mal, et se tromper facture le mauvais mois.
Le bouton « Générer maintenant » produit l'occurrence courante sans attendre, ce qui sert dès qu'une récurrence est créée après l'ouverture de sa fenêtre.
Rappels avant échéance
L'échelle par défaut est 30, 14, 7, 3, 1 jours avant l'échéance. Deux filtres la réduisent avant qu'un créneau ne soit retenu.
Chaque facture porte son propre délai de premier rappel, et les échelons plus lointains que ce délai ne sont jamais utilisés : une facture réglée sur 7 jours ne déclenche pas le rappel à 30 jours.
Un échelon aussi long que le délai accordé au client est écarté lui aussi. Le délai accordé, ce sont les jours entre la date d'émission et l'échéance. Un échelon qui l'égale ou le dépasse tomberait le matin même de l'émission : ce n'est pas un rappel, c'est la facture. Une facture à 30 jours de délai démarre donc son échelle à J-14, seize jours après son envoi, et non le jour même.
La conséquence sur les délais courts mérite d'être connue : une facture à 15 jours de délai démarre à J-14, soit le lendemain de l'émission. C'est l'échelle configurée qui le veut, pas un accident.
Le créneau retenu un jour donné est le plus petit échelon retenu encore supérieur ou égal au nombre de jours restants. Sur un compte à rebours complet, le client reçoit donc exactement un message par échelon, dans l'ordre, quel que soit le jour où la tâche s'exécute.
Avis de retard
Après l'échéance, un avis tous les LRVO_MODULE_BILLING_OVERDUE_INTERVAL_DAYS jours, jusqu'à LRVO_MODULE_BILLING_OVERDUE_MAX_NOTICES avis. Aux valeurs par défaut : un avis à J+1, puis à J+8, J+15 et J+22.
Le créneau se calcule à partir de la date, pas d'un compteur incrémenté à l'envoi. Une première exécution le dixième jour de retard tombe donc dans le bon créneau et relance le client, au lieu de considérer que le moment est passé.
Une copie interne part à LRVO_MODULE_BILLING_ADMIN_EMAIL au premier retard de chaque facture, une seule fois.
Comment un courriel ne part jamais deux fois
Chaque message occupe un créneau (facture, type, numéro) dans lrvo_billing_reminder. Le créneau est réservé avant l'envoi par une insertion qu'un index unique partiel refuse de dupliquer. Pas de ligne insérée, pas d'envoi.
Si l'envoi échoue ensuite, la ligne est marquée en échec, ce qui libère le créneau et laisse le passage suivant réessayer.
L'idempotence tient donc aux données, pas à l'horaire. Exécuter une tâche deux fois dans la journée n'envoie rien de plus, et une journée sautée est rattrapée.
Une pause de 900 ms sépare deux envois, pour rester sous la limite de débit du fournisseur de courriel.
Le contenu de ces messages est décrit dans Courriels.
Intérêts de retard
Intérêts simples, non composés, sur le solde taxes comprises, après un délai de grâce.
tauxQuotidien = tauxMensuel ÷ 30,4375
intérêts = solde × tauxQuotidien × joursÉcoulésLe diviseur est la longueur moyenne d'un mois, 365,25 ÷ 12. Au taux par défaut de 1 % par mois, un solde de 1 000,00 $ accumule environ 0,33 $ par jour.
Le curseur interest_accrued_on porte toute la logique : les intérêts sont dus pour les jours écoulés entre le curseur, ou la fin du délai de grâce si elle est plus tardive, et aujourd'hui. Trois conséquences :
- relancer la tâche le même jour n'ajoute rien ;
- une journée manquée est rattrapée au passage suivant ;
- une première exécution longtemps après l'échéance facture à partir de la fin du délai de grâce, jamais depuis l'échéance elle-même.
Les intérêts accumulés ne portent pas eux-mêmes d'intérêts, et ne sont pas taxés : des frais de retard ne sont pas la contrepartie d'une fourniture.
Les intérêts se décident pour tout le site, dans LRVO_MODULE_BILLING_INTEREST_ENABLED. Il n'y a pas de réglage par facture.
Suivre et déclencher
L'onglet « Transactions » de l'admin montre l'état des trois tâches : expression horaire, dernier passage, dernier résultat, et un bouton pour déclencher immédiatement. Le déclenchement manuel passe par le même verrou, donc il ne peut pas doubler une exécution en cours.
Notes
- Les tâches ne sont pas enregistrées pendant
next build: Next peut exécuter le fichier d'instrumentation en phase de compilation, ce qui les ferait tourner sur les données de production au milieu d'un déploiement. - Une facture annulée sort des trois tâches.
- Les rappels ne partent qu'aux clients qui ont un courriel, ce qui est une contrainte de la table.