feat(go): M5.2 — HTTP handlers for /api/adults, /api/juniors, /api/payments, /api/version
All checks were successful
Deploy to K8s / deploy (push) Successful in 10s
All checks were successful
Deploy to K8s / deploy (push) Successful in 10s
- Add web/api/handler.go: Handler struct wiring Sources+Config into ServeAdults, ServeJuniors, ServePayments, ServeVersion - Add web/api/build_common.go: getMonthLabels, groupRawPaymentsByPerson, settledBalance, domain-to-wire converters, ensureSlice generic helper - Add web/api/build_adults.go: buildAdultsResponse + buildAdultMemberRow mirroring scripts/views.py:build_adults_view_model - Add web/api/build_juniors.go: buildJuniorsResponse + buildJuniorMemberRow mirroring scripts/views.py:build_juniors_view_model, including "?" sentinel and :NJ,MA breakdown - Add web/api/build_payments.go: buildPaymentsResponse with Unmatched/Unknown bucket - Extend reconcile.FeeData/MonthData with IsUnknown, JuniorAttendance, AdultAttendance - Extend reconcile.Transaction with ManualFix, VS, BankID, SyncID for raw_payments wire field - Export membership.AdultMergedMonths and JuniorMergedMonths - Update sources.go to propagate new FeeData fields and parse extra transaction columns - Wire sources+cfg into web.Run; register /api/* routes via Go 1.22 method+path patterns - Fix pre-existing gofumpt formatting in fio_test.go and fio_table.go Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -29,7 +29,7 @@ func tx(person, purpose string, amount float64) Transaction {
|
||||
|
||||
func TestReconcileExceptionOverride(t *testing.T) {
|
||||
t.Parallel()
|
||||
members := []Member{{Name: "Alice", Tier: "A", Fees: map[string]FeeData{"2026-01": {750, 4}}}}
|
||||
members := []Member{{Name: "Alice", Tier: "A", Fees: map[string]FeeData{"2026-01": {Expected: 750, Attendance: 4}}}}
|
||||
exceptions := map[ExceptionKey]Exception{
|
||||
{Name: "alice", Period: "2026-01"}: {Amount: 400, Note: "Test exception"},
|
||||
}
|
||||
@@ -54,7 +54,7 @@ func TestReconcileExceptionOverride(t *testing.T) {
|
||||
|
||||
func TestReconcileFallbackToAttendance(t *testing.T) {
|
||||
t.Parallel()
|
||||
members := []Member{{Name: "Alice", Tier: "A", Fees: map[string]FeeData{"2026-01": {750, 4}}}}
|
||||
members := []Member{{Name: "Alice", Tier: "A", Fees: map[string]FeeData{"2026-01": {Expected: 750, Attendance: 4}}}}
|
||||
|
||||
result := Reconcile(members, []string{"2026-01"}, nil, nil, defaultYear)
|
||||
|
||||
@@ -68,9 +68,9 @@ func TestReconcileGreedyExactMatch(t *testing.T) {
|
||||
members := []Member{{
|
||||
Name: "Alice", Tier: "A",
|
||||
Fees: map[string]FeeData{
|
||||
"2026-02": {750, 3},
|
||||
"2026-03": {350, 3},
|
||||
"2026-04": {150, 2},
|
||||
"2026-02": {Expected: 750, Attendance: 3},
|
||||
"2026-03": {Expected: 350, Attendance: 3},
|
||||
"2026-04": {Expected: 150, Attendance: 2},
|
||||
},
|
||||
}}
|
||||
sortedMonths := []string{"2026-02", "2026-03", "2026-04"}
|
||||
@@ -93,7 +93,7 @@ func TestReconcileGreedyOverpaymentGoesToCredit(t *testing.T) {
|
||||
t.Parallel()
|
||||
members := []Member{{
|
||||
Name: "Alice", Tier: "A",
|
||||
Fees: map[string]FeeData{"2026-01": {750, 3}, "2026-02": {750, 3}},
|
||||
Fees: map[string]FeeData{"2026-01": {Expected: 750, Attendance: 3}, "2026-02": {Expected: 750, Attendance: 3}},
|
||||
}}
|
||||
sortedMonths := []string{"2026-01", "2026-02"}
|
||||
|
||||
@@ -115,7 +115,7 @@ func TestReconcileProportionalUnderpayment(t *testing.T) {
|
||||
t.Parallel()
|
||||
members := []Member{{
|
||||
Name: "Alice", Tier: "A",
|
||||
Fees: map[string]FeeData{"2026-02": {750, 3}, "2026-03": {350, 3}, "2026-04": {750, 3}},
|
||||
Fees: map[string]FeeData{"2026-02": {Expected: 750, Attendance: 3}, "2026-03": {Expected: 350, Attendance: 3}, "2026-04": {Expected: 750, Attendance: 3}},
|
||||
}}
|
||||
sortedMonths := []string{"2026-02", "2026-03", "2026-04"}
|
||||
amount := 1250.0
|
||||
@@ -146,7 +146,7 @@ func TestReconcileProportionalUnderpayment(t *testing.T) {
|
||||
|
||||
func TestReconcileSingleMonthUnchanged(t *testing.T) {
|
||||
t.Parallel()
|
||||
members := []Member{{Name: "Alice", Tier: "A", Fees: map[string]FeeData{"2026-01": {750, 3}}}}
|
||||
members := []Member{{Name: "Alice", Tier: "A", Fees: map[string]FeeData{"2026-01": {Expected: 750, Attendance: 3}}}}
|
||||
|
||||
result := Reconcile(members, []string{"2026-01"}, []Transaction{tx("Alice", "2026-01", 750)}, nil, defaultYear)
|
||||
|
||||
@@ -158,8 +158,8 @@ func TestReconcileSingleMonthUnchanged(t *testing.T) {
|
||||
func TestReconcileTwoMembersMultiMonth(t *testing.T) {
|
||||
t.Parallel()
|
||||
members := []Member{
|
||||
{Name: "Alice", Tier: "A", Fees: map[string]FeeData{"2026-01": {750, 3}, "2026-02": {350, 3}}},
|
||||
{Name: "Bob", Tier: "A", Fees: map[string]FeeData{"2026-01": {750, 3}, "2026-02": {350, 3}}},
|
||||
{Name: "Alice", Tier: "A", Fees: map[string]FeeData{"2026-01": {Expected: 750, Attendance: 3}, "2026-02": {Expected: 350, Attendance: 3}}},
|
||||
{Name: "Bob", Tier: "A", Fees: map[string]FeeData{"2026-01": {Expected: 750, Attendance: 3}, "2026-02": {Expected: 350, Attendance: 3}}},
|
||||
}
|
||||
sortedMonths := []string{"2026-01", "2026-02"}
|
||||
|
||||
@@ -180,7 +180,7 @@ func TestReconcileEvenSplitFallbackWhenNoExpected(t *testing.T) {
|
||||
t.Parallel()
|
||||
members := []Member{{
|
||||
Name: "Alice", Tier: "A",
|
||||
Fees: map[string]FeeData{"2026-01": {0, 0}, "2026-02": {0, 0}},
|
||||
Fees: map[string]FeeData{"2026-01": {Expected: 0, Attendance: 0}, "2026-02": {Expected: 0, Attendance: 0}},
|
||||
}}
|
||||
sortedMonths := []string{"2026-01", "2026-02"}
|
||||
|
||||
@@ -197,7 +197,7 @@ func TestReconcileEvenSplitFallbackWhenNoExpected(t *testing.T) {
|
||||
|
||||
func TestReconcileDiacriticsTolerantPersonMatching(t *testing.T) {
|
||||
t.Parallel()
|
||||
members := []Member{{Name: "Mária Maco", Tier: "A", Fees: map[string]FeeData{"2026-04": {750, 4}}}}
|
||||
members := []Member{{Name: "Mária Maco", Tier: "A", Fees: map[string]FeeData{"2026-04": {Expected: 750, Attendance: 4}}}}
|
||||
txFn := func(person string) Transaction {
|
||||
return Transaction{
|
||||
Date: "2026-04-15", Amount: 750, Person: person, Purpose: "2026-04",
|
||||
@@ -232,7 +232,7 @@ func TestReconcileDiacriticsTolerantPersonMatching(t *testing.T) {
|
||||
|
||||
func TestReconcileTrulyUnknownPersonIsUnmatched(t *testing.T) {
|
||||
t.Parallel()
|
||||
members := []Member{{Name: "Mária Maco", Tier: "A", Fees: map[string]FeeData{"2026-04": {750, 4}}}}
|
||||
members := []Member{{Name: "Mária Maco", Tier: "A", Fees: map[string]FeeData{"2026-04": {Expected: 750, Attendance: 4}}}}
|
||||
txs := []Transaction{{
|
||||
Date: "2026-04-15", Amount: 750,
|
||||
Person: "Někdo Neznámý", Purpose: "2026-04",
|
||||
@@ -252,7 +252,7 @@ func TestReconcileTrulyUnknownPersonIsUnmatched(t *testing.T) {
|
||||
// [Go] Test that [?] markers are stripped from the Person field before lookup.
|
||||
func TestReconcileQuestionMarkMarkerStripped(t *testing.T) {
|
||||
t.Parallel()
|
||||
members := []Member{{Name: "Alice", Tier: "A", Fees: map[string]FeeData{"2026-01": {750, 3}}}}
|
||||
members := []Member{{Name: "Alice", Tier: "A", Fees: map[string]FeeData{"2026-01": {Expected: 750, Attendance: 3}}}}
|
||||
txs := []Transaction{{
|
||||
Date: "2026-01-01", Amount: 750,
|
||||
Person: "[?] Alice", Purpose: "2026-01",
|
||||
@@ -269,7 +269,7 @@ func TestReconcileQuestionMarkMarkerStripped(t *testing.T) {
|
||||
// [Go] Purpose "other:shirt" puts payment in OtherTransactions, not in month ledger.
|
||||
func TestReconcileOtherPurpose(t *testing.T) {
|
||||
t.Parallel()
|
||||
members := []Member{{Name: "Alice", Tier: "A", Fees: map[string]FeeData{"2026-01": {750, 3}}}}
|
||||
members := []Member{{Name: "Alice", Tier: "A", Fees: map[string]FeeData{"2026-01": {Expected: 750, Attendance: 3}}}}
|
||||
txs := []Transaction{{
|
||||
Date: "2026-01-01", Amount: 300,
|
||||
Person: "Alice", Purpose: "other:shirt",
|
||||
@@ -297,7 +297,7 @@ func TestReconcileOtherPurpose(t *testing.T) {
|
||||
func TestReconcileOutOfWindowGoesToCredit(t *testing.T) {
|
||||
t.Parallel()
|
||||
// Window shows only 2026-01. Transaction references 2026-01 (in) and 2026-02 (out).
|
||||
members := []Member{{Name: "Alice", Tier: "A", Fees: map[string]FeeData{"2026-01": {600, 3}}}}
|
||||
members := []Member{{Name: "Alice", Tier: "A", Fees: map[string]FeeData{"2026-01": {Expected: 600, Attendance: 3}}}}
|
||||
txs := []Transaction{{
|
||||
Date: "2026-01-01", Amount: 1200,
|
||||
Person: "Alice", Purpose: "2026-01, 2026-02",
|
||||
@@ -322,7 +322,7 @@ func TestReconcileOutOfWindowGoesToCredit(t *testing.T) {
|
||||
// [Go] No person/purpose → inference fallback resolves sender name and date month.
|
||||
func TestReconcileInferenceFallback(t *testing.T) {
|
||||
t.Parallel()
|
||||
members := []Member{{Name: "Tomáš Němeček", Tier: "A", Fees: map[string]FeeData{"2026-04": {750, 3}}}}
|
||||
members := []Member{{Name: "Tomáš Němeček", Tier: "A", Fees: map[string]FeeData{"2026-04": {Expected: 750, Attendance: 3}}}}
|
||||
txs := []Transaction{{
|
||||
Date: "2026-04-15", Amount: 750,
|
||||
// Person and Purpose are empty → inference path
|
||||
@@ -340,7 +340,7 @@ func TestReconcileInferenceFallback(t *testing.T) {
|
||||
// [Go] Transaction with no match at all ends up in Unmatched; ledger unchanged.
|
||||
func TestReconcileNoMatchGoesToUnmatched(t *testing.T) {
|
||||
t.Parallel()
|
||||
members := []Member{{Name: "Alice", Tier: "A", Fees: map[string]FeeData{"2026-01": {750, 3}}}}
|
||||
members := []Member{{Name: "Alice", Tier: "A", Fees: map[string]FeeData{"2026-01": {Expected: 750, Attendance: 3}}}}
|
||||
txs := []Transaction{{
|
||||
Date: "2026-01-01", Amount: 500,
|
||||
// empty person+purpose and sender name not matching any member
|
||||
@@ -360,7 +360,7 @@ func TestReconcileNoMatchGoesToUnmatched(t *testing.T) {
|
||||
// [Go] Empty transaction list leaves every month at paid=0 and balance=–expected.
|
||||
func TestReconcileNoTransactionsAllUnpaid(t *testing.T) {
|
||||
t.Parallel()
|
||||
members := []Member{{Name: "Alice", Tier: "A", Fees: map[string]FeeData{"2026-01": {750, 3}}}}
|
||||
members := []Member{{Name: "Alice", Tier: "A", Fees: map[string]FeeData{"2026-01": {Expected: 750, Attendance: 3}}}}
|
||||
|
||||
result := Reconcile(members, []string{"2026-01"}, nil, nil, defaultYear)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user