Deploy to K8s / deploy (push) Successful in 20s
Go (go/) becomes the primary implementation. The original Python version moves to legacy/ and is kept runnable; new features are still built in both (see the Dual implementation rule in CLAUDE.md). - git mv app.py, scripts/, templates/, tests/, pyproject.toml, uv.lock and build/ into legacy/ - Makefile: Python targets point at legacy/, venv in legacy/.venv, CREDENTIALS_PATH exported for the root .secret/ file - CI and make image build the Python image with legacy/ as context; the Makefile is no longer copied into the image - legacy/.dockerignore moved to the context root so it takes effect - CLAUDE.md, docs/README.md, Go comments, fixture README, capture script and VS Code launch config updated to the new layout Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
90 lines
2.0 KiB
Go
90 lines
2.0 KiB
Go
package membership
|
|
|
|
import (
|
|
"fmt"
|
|
"fuj-management/go/internal/domain/reconcile"
|
|
"io"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// printFeesTable writes a fixed-width adult-fees table to w.
|
|
// Mirrors legacy/scripts/calculate_fees.py main().
|
|
//
|
|
// Verify with:
|
|
//
|
|
// PYTHONPATH=legacy/scripts:legacy python legacy/scripts/calculate_fees.py
|
|
func printFeesTable(w io.Writer, members []reconcile.Member, sortedMonths []string) {
|
|
type row struct {
|
|
name string
|
|
fees map[string]reconcile.FeeData
|
|
}
|
|
|
|
var adults []row
|
|
for _, m := range members {
|
|
if m.Tier == "A" {
|
|
adults = append(adults, row{name: m.Name, fees: m.Fees})
|
|
}
|
|
}
|
|
|
|
if len(adults) == 0 {
|
|
fmt.Fprintln(w, "No data.")
|
|
return
|
|
}
|
|
|
|
monthLabel := func(m string) string {
|
|
t, err := time.Parse("2006-01", m)
|
|
if err != nil {
|
|
return m
|
|
}
|
|
return t.Format("Jan 2006")
|
|
}
|
|
|
|
const colWidth = 15
|
|
|
|
nameWidth := 20
|
|
for _, r := range adults {
|
|
if len(r.name) > nameWidth {
|
|
nameWidth = len(r.name)
|
|
}
|
|
}
|
|
|
|
// separator length: nameWidth + N*(colWidth+3) where +3 is " | "
|
|
sepLen := nameWidth + len(sortedMonths)*(colWidth+3)
|
|
|
|
// Header row
|
|
fmt.Fprintf(w, "%-*s", nameWidth, "Member")
|
|
for _, m := range sortedMonths {
|
|
fmt.Fprintf(w, " | %*s", colWidth, monthLabel(m))
|
|
}
|
|
fmt.Fprintln(w)
|
|
fmt.Fprintln(w, strings.Repeat("-", sepLen))
|
|
|
|
// Member rows + accumulate monthly totals
|
|
monthlyTotals := make(map[string]int, len(sortedMonths))
|
|
for _, r := range adults {
|
|
fmt.Fprintf(w, "%-*s", nameWidth, r.name)
|
|
for _, m := range sortedMonths {
|
|
fd := r.fees[m]
|
|
monthlyTotals[m] += fd.Expected
|
|
var cell string
|
|
if fd.Attendance > 0 {
|
|
cell = fmt.Sprintf("%d CZK (%d)", fd.Expected, fd.Attendance)
|
|
} else {
|
|
cell = "-"
|
|
}
|
|
fmt.Fprintf(w, " | %*s", colWidth, cell)
|
|
}
|
|
fmt.Fprintln(w)
|
|
}
|
|
|
|
// Totals row
|
|
fmt.Fprintln(w, strings.Repeat("-", sepLen))
|
|
fmt.Fprintf(w, "%-*s", nameWidth, "TOTAL")
|
|
for _, m := range sortedMonths {
|
|
cell := fmt.Sprintf("%d CZK", monthlyTotals[m])
|
|
fmt.Fprintf(w, " | %*s", colWidth, cell)
|
|
}
|
|
fmt.Fprintln(w)
|
|
}
|