Skip to content

SudoSOS Back-end API / fines

Fines ​

A fine is a small penalty (capped at 5 EUR per round) applied to a user with a persistently negative Balance. Fines are handed out in batches as a debtor-recovery step, not as a per-transaction fee. The amount is min(5 EUR, floor(-balance / 5 EUR) * 1 EUR), never negative, so a user at -3.40 EUR gets nothing and a user at -42 EUR still gets only 5 EUR.

Three entities, one story ​

  • FineHandoutEvent is one batch. A single POST /fines/handout creates one event that groups every fine handed out in that round, records the referenceDate the fines were calculated against, and the admin who triggered the run.
  • Fine is one user's slice of a batch. Each fine owns a debit Transfer that moves the penalty off the user's balance.
  • UserFineGroup is the per-user wrapper that ties every fine a user has ever received together and (optionally) holds the waivedTransfer that cancels them. User.currentFines points to it while the user still owes; a user has at most one open group at a time.

Lifecycle ​

An admin (typically the financial responsible) hands out fines manually. There is no cron. The typical sequence is:

  1. GET /fines/eligible returns users who are below -5 EUR both now and on the reference date (calculateFinesOnDate). The double check exists so a user who just topped up does not get fined for a historical low.
  2. POST /fines/notify sends a warning email (sendFineWarnings) so users have a grace period to top up.
  3. POST /fines/handout runs the actual handout in one database transaction (handOutFines): one FineHandoutEvent, one Fine per eligible user, one debit Transfer per fine, and one notification per fined user.

Waiving ​

POST /users/{id}/fines/waive creates a credit Transfer on the user's UserFineGroup as waivedTransfer. Waiving is replace-not-append: if the group already has a waived transfer it is removed and a fresh one is created with the new total. Waiving the full outstanding amount nulls User.currentFines, even if the user's balance is still negative.

Undoing ​

DELETE /fines/single/{id} removes one fine and its debit transfer; if that was the only fine in the group, the UserFineGroup goes too. DELETE /fines/handout/{id} rolls back a whole batch the same way. Both delete instead of writing a compensating row; fines are the rare exception to the rule that nothing on the ledger disappears.

Reflection on the balance ​

Balance treats fine and waiver transfers like any other transfer. On top of that, BalanceResponse surfaces the fine state as four "now" fields: fine (outstanding), fineSince (timestamp of the first fine), nrFines (count), and fineWaived (amount already waived). Those fields are not historical, so ignore them when date is in the past.

Reporting ​

GET /fines/report?fromDate=...&tillDate=... returns aggregate handed-out and waived totals for treasurer reconciliation; GET /fines/report/pdf produces the printable version. The service orchestrating everything above lives in debtors.

Classes ​

ClassDescription
Fine-
FineHandoutEvent-
UserFineGroup-