Nikki Pipeline
Syncs member contribution/dues data from the Nikki accounting system to Rondo Club ACF fields.
Schedule
Section titled “Schedule”Runs daily at 7:00 AM (Amsterdam time).
scripts/sync.sh nikki # Production (with locking + email report)node pipelines/sync-nikki.js --verbose # Direct execution (verbose)Pipeline Flow
Section titled “Pipeline Flow”pipelines/sync-nikki.js├── Step 1: steps/download-nikki-contributions.js → data/nikki-sync.sqlite└── Step 2: steps/sync-nikki-to-rondo-club.js → Rondo Club APIStep-by-Step Details
Section titled “Step-by-Step Details”Step 1: Download Nikki Contributions
Section titled “Step 1: Download Nikki Contributions”Script: steps/download-nikki-contributions.js
Function: runNikkiDownload({ logger, verbose })
- Launches headless Chromium via Playwright
- Navigates to the Nikki web application
- Scrapes the HTML contribution table:
- Extracts
knvb_id,year,nikki_id,saldo,statusper row - Parses European currency format (e.g., “EUR 1.234,56” → 1234.56)
- Extracts
- Exports CSV data for
hoofdsom(total amount) values - A single member can have multiple contribution lines per year (e.g., separate amounts for different family members)
- Each line is stored as a separate row in SQLite, keyed by
(knvb_id, year, nikki_id) - Computes
source_hashper contribution record - Upserts into
data/nikki-sync.sqlite→nikki_contributionstable
Output: { success, count }
Database written: data/nikki-sync.sqlite → nikki_contributions
Step 2: Sync to Rondo Club
Section titled “Step 2: Sync to Rondo Club”Script: steps/sync-nikki-to-rondo-club.js
Function: runNikkiRondoClubSync({ logger, verbose, force })
- Groups contributions by
knvb_idandyear - For members with multiple lines per year: sums
saldoandhoofdsomacross all lines - Looks up
rondo_club_idfromdata/rondo-sync.sqlite→rondo_club_members(cross-database lookup) - Skips members without a
rondo_club_id(not yet synced to Rondo Club) - For each member with changes, sends
PUT /wp/v2/people/{rondo_club_id}with:first_nameandlast_name(always required by Rondo Club API)- Per-year Nikki ACF fields (up to 4 years of history)
- Rate limited: 500ms between updates
Output: { updated, skipped, noRondoClubId, errors }
Important: The PUT request must include first_name and last_name even when only updating Nikki fields. This requires a GET request first to fetch existing required fields.
Field Mappings
Section titled “Field Mappings”Nikki → Rondo Club ACF Fields
Section titled “Nikki → Rondo Club ACF Fields”For each contribution year, three ACF fields are written per person:
| Rondo Club ACF Field | Source | Example |
|---|---|---|
_nikki_{YEAR}_total | Sum of hoofdsom for that year | _nikki_2025_total: 1500.00 |
_nikki_{YEAR}_saldo | Sum of saldo for that year | _nikki_2025_saldo: 250.00 |
_nikki_{YEAR}_status | status value | _nikki_2025_status: “Betaald” |
Up to 4 years of history are retained (e.g., 2023, 2024, 2025, 2026).
Data Aggregation
Section titled “Data Aggregation”When a member has multiple contribution lines for the same year:
Member KNVB123 in 2025: Line 1: saldo=100, hoofdsom=500, status="Betaald" Line 2: saldo=150, hoofdsom=750, status="Open"
Result for Rondo Club: _nikki_2025_saldo = 250 (100 + 150) _nikki_2025_total = 1250 (500 + 750) _nikki_2025_status = "Open" (worst status takes priority)Database Tables Used
Section titled “Database Tables Used”| Database | Table | Usage |
|---|---|---|
nikki-sync.sqlite | nikki_contributions | Contribution records per member per year |
rondo-sync.sqlite | rondo_club_members | KNVB ID → Rondo Club ID lookup (read-only) |
CLI Flags
Section titled “CLI Flags”| Flag | Effect |
|---|---|
--verbose | Detailed per-member logging |
--force | Re-sync all members regardless of change detection |
Error Handling
Section titled “Error Handling”- Download failure is logged but doesn’t prevent reporting
- Members without a
rondo_club_idare skipped (counted separately asnoRondoClubId) - Individual member update failures don’t stop the pipeline
- All errors collected in summary report
Source Files
Section titled “Source Files”| File | Purpose |
|---|---|
pipelines/sync-nikki.js | Pipeline orchestrator |
steps/download-nikki-contributions.js | Nikki web scraping (Playwright) |
steps/sync-nikki-to-rondo-club.js | Rondo Club API sync |
lib/nikki-db.js | Nikki SQLite operations |
lib/rondo-club-db.js | Rondo Club ID lookup |
lib/rondo-club-client.js | Rondo Club HTTP client |