Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
594cf69
feat(contribution): payment emails, one send routed per member
ExtraToast Sep 1, 2026
87395a9
fix(contribution): the payment-emails table has room, and a forced ro…
ExtraToast Sep 1, 2026
01cee66
feat(contribution): a summary stands between Send and the send
ExtraToast Sep 1, 2026
7749850
style(contribution): the send summary reads as a summary
ExtraToast Sep 1, 2026
0fada97
feat(contribution): the browser holds the period-bounds rule, and a t…
ExtraToast Sep 1, 2026
d6d1362
feat(contribution): sending payment emails is a three-step wizard
ExtraToast Sep 1, 2026
207c2f3
feat(api): a rule above the web layer refuses a field in bean validat…
ExtraToast Sep 1, 2026
eda4008
feat(contribution): a send states a future date and caps what it may …
ExtraToast Sep 1, 2026
363232e
feat(contribution): a payment-email send says what it will not do
ExtraToast Sep 1, 2026
dcdf4b6
test(contribution): every payment-email refusal names its field and c…
ExtraToast Sep 1, 2026
e641757
chore(api): regenerate the openapi spec for the capped tick-back-in list
ExtraToast Sep 1, 2026
3808d6b
chore(frontend): regenerate the client from the refusal-carrying spec
ExtraToast Sep 1, 2026
7077fda
feat(frontend): a refused send lands on the step that owns the field
ExtraToast Sep 1, 2026
7992c27
test(frontend): the payment-email journey drives three steps and a re…
ExtraToast Sep 1, 2026
5290c8c
test(system): the payment-email refusals are asserted over http
ExtraToast Sep 1, 2026
2b7c821
docs(payment-emails): the wizard, the refusal routing and what they g…
ExtraToast Sep 1, 2026
845bdba
fix(api): a payment-email refusal states a sentence fixed per code
ExtraToast Sep 1, 2026
2c0a001
feat(api): the plan names an id that is nobody rather than dropping it
ExtraToast Sep 1, 2026
1504bfe
fix(frontend): a refused send keeps the choices it did not contradict
ExtraToast Sep 1, 2026
159300c
refactor(frontend): each rule about a payment-email row is written once
ExtraToast Sep 1, 2026
0de3463
chore(api): regenerate the spec and client over the boards on main
ExtraToast Sep 1, 2026
1803ff9
fix(system): a payment-email step no longer claims a name another flo…
ExtraToast Sep 1, 2026
c73d72c
fix(system): a payment-email scenario answers for its own members
ExtraToast Sep 1, 2026
00ef3c0
style(contribution): the wizard says who, in words that need no decoding
ExtraToast Sep 1, 2026
cf73664
style(contribution): a warning says what happens to whom, in both dir…
ExtraToast Sep 1, 2026
0e37122
chore(api): regenerate the spec, now yaml, over the boards on main
ExtraToast Sep 2, 2026
f28c32b
test(system): a payment-email scenario asserts what a member received
ExtraToast Sep 2, 2026
db27872
fix(system): a payment-email scenario finds the email it is waiting for
ExtraToast Sep 2, 2026
578982c
fix(api): the test outbox keeps both emails when a send queues two at…
ExtraToast Sep 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 37 additions & 21 deletions docs/CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,41 +124,57 @@ fee. A regular membership starting **after** it pays the half year; one starting
or before pays the full year. Policy for the year, so it lives on the period rather
than being retyped on each send.

### Fee cycle
### Payment emails

The one operation that asks every member of a period who has not paid for what they
owe. It is opened for a period, not for a selection: who is asked follows from the
period, which is what makes "has everybody been asked exactly once" answerable.
The one operation that asks a selection of members for what they owe for a period.
One send, whichever statements it turns out to need — not two actions the treasurer
picks between, and not a bulk reminder.

Not a "bulk reminder" and not two sends. One cycle, one confirmation.
"Payment emails" in the interface; `ContributionEmail` in the code, where the noun
sits beside contribution period and contribution reminder.

### The partition
### Contribution reminder and incasso notification

The split of a fee cycle into the two groups that receive different statements,
decided by the `incasso` flag on the membership each decision is judged against.
Called a **group** on the wire and in the code — `FeeCycleGroup`, whose values are
`DIRECT_DEBIT` and `TRANSFER` — and "the partition" only in prose about the pair.

Because the flag decides the side, not having it is not a warning. There is no wrong
side to be selected for.

### Payment request and pre-notification

The two statements a fee cycle sends. A **payment request** asks a member paying by
transfer to pay what they owe by a due date. A **pre-notification** tells a member
The two emails a send puts out. A **contribution reminder** asks a member paying by
transfer to pay what they owe by a due date. An **incasso notification** tells a member
paying by direct debit what will be taken and on what date, and asks for nothing.

Which one a member gets is their `incasso` flag's choice, stated per row as the
member's **kind** — `ContributionEmailKind`, whose values are `REMINDER` and
`INCASSO_NOTIFICATION`. A default rather than a rule: the treasurer may move a member
onto the other one, and a **switched** row says so.

Different statements, so different records: the treasurer's question is which one a
member received. Neither quotes an amount without the reason that amount applies.

### Send to

The checkbox on the first step of the payment-email wizard, and the selection itself: a
member is written to when their box is ticked, and only then. A member the api warns about
starts unticked, and one it cannot write to at all has no box. It replaced **forcibly
include**, which was the same gesture but appeared only on the rows the api had warned
about, so one control now does what two were doing.

`forciblyIncludedUserIds` on the wire still carries the warned rows ticked back in, because
the send re-decides and would otherwise skip them.

### Refusal

A bulk request the api declines whole, naming the request **field** at fault and a stable
**code** rather than a sentence to display. Nothing is written. A **409** means the client's
table is stale; a **400** that a field of the request is wrong. Both arrive in the same
`errors[]`, so one client-side handler reads either and the payment-email wizard can put the
treasurer back on the step that owns the field.

### Ask

One asking of one member to pay for one period — a payment request or a pre-notification.
One asking of one member to pay for one period — a contribution reminder or an incasso notification.
A row each, not one per member and period: the treasurer chases, so a member can be asked
in September, again in February and again the week after, and each is a thing that
happened. "Last asked" is the most recent of them.
happened. "Last sent" is the most recent of them, read per kind: a member moved onto direct debit
has been reminded and never pre-notified, and pooling the two would hide it.

An **incasso** is the direct debit itself; the record of a pre-notification is an
An **incasso** is the direct debit itself; the record of one of these is an
`IncassoNotification`, which is the Dutch word the association uses for the mandate.

## Boards
Expand Down
2 changes: 1 addition & 1 deletion docs/flows/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ tells you what the process is supposed to guarantee.
| [Membership signup](membership-signup/README.md) | Account creation plus an application; the membership commits on whichever fact lands last |
| [Recovery emails](recovery-emails/README.md) | The emails that let somebody into an account, and reading one before it is sent |
| [Bulk contribution marking](bulk-contribution-marking/README.md) | Recording contributions for many users at once; a selection applies whole or not at all |
| [The fee cycle](fee-cycle/README.md) | Asking every unpaid member of a period for their contribution; one operation over the direct-debit partition |
| [Payment emails](payment-emails/README.md) | Asking a selection of members for what they owe; one send, routed per member into a reminder or an incasso notification |

Account creation is the base flow and membership signup builds on it, so the two
are read in that order and neither restates the other.
Expand Down
4 changes: 2 additions & 2 deletions docs/flows/bulk-contribution-marking/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ the counts reported back.

Does not cover how a contribution period is created, how a single contribution is
recorded from a user's own page, or asking members for what they owe — that is
[the fee cycle](../fee-cycle/README.md), which sends email and carries its own audit
rows. This flow only writes contribution records.
[payment emails](../payment-emails/README.md), which sends email and carries its own
audit rows. This flow only writes contribution records.

## Actors and entry points

Expand Down
254 changes: 0 additions & 254 deletions docs/flows/fee-cycle/README.md

This file was deleted.

Loading
Loading