yuki-cli 0.1.6

CLI client for the Yuki bookkeeping SOAP API
Documentation
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Build & Test Commands

```sh
make check          # Run clippy (warnings=errors) + fmt check + tests
make test           # cargo test
make lint           # clippy + fmt check
make fmt            # auto-format
make install        # cargo install --path . (installs to ~/.cargo/bin/yuki)
cargo test <name>   # Run a single test by name
```

After any code change that affects the binary, run `make install` to update `~/.cargo/bin/yuki`.

## Architecture

Three-layer design: **CLI** (clap) → **command handlers** → **typed SOAP clients**.

```
src/main.rs              Entry point, clap dispatch, error formatting
src/cli/mod.rs           Cli struct, Commands enum, setup_domain() helper
src/cli/*.rs             Command handlers (one per subcommand group)
src/client/mod.rs        local_name() helper, re-exports
src/client/soap_client.rs  SoapEnvelope builder + SoapClient (HTTP transport, XML parsing)
src/client/*.rs          Service-specific clients wrapping SoapClient
src/config.rs            TOML config (~/.config/yuki/config.toml)
src/error.rs             YukiError enum with exit codes (0/1/2/3/4)
src/output.rs            TTY-aware table (comfy-table) / JSON output
src/period.rs            Period string → (start_date, end_date) conversion
```

### SOAP Client Pattern

Each service client (accounting, archive, vat, contact, sales) wraps `SoapClient` with a specific base URL (`https://api.yukiworks.nl/ws/{Service}.asmx`). The flow is always: `authenticate()` → `set_current_domain()` → operation calls. The `setup_domain()` helper in `cli/mod.rs` handles the first two steps.

`SoapEnvelope` is a builder: `.new("Op").session(sid).param("key", "val").build()` produces the XML envelope. All operations require `administrationID` as a parameter.

### Output Convention

Output is TTY-aware: tables for humans, JSON when piped. The `OutputFormat::from_flag()` method handles this. All command handlers follow the same pattern: build `headers` + `rows` vectors, then format with `format_table`/`format_json`.

## Yuki API Quirks

These are hard-won lessons from the actual API behavior:

- SOAP namespace: `http://www.theyukicompany.com/`
- SOAP faults return as HTTP 500 — must parse XML body, not just status code
- `SetCurrentDomain` needs `domainID` (not `administrationID`)
- Almost all operations need `administrationID` as a parameter
- Administration `ID` is an XML **attribute**, not a child element
- VAT operations use exact casing: `VATReturnList`, `ActiveVATCodesList`
- Real XML field names often differ from what you'd guess (lowercase, different names)
- `paymentMethod` must be int (`0` = unspecified), not empty string
- `SearchDocuments`: `folderID=-1, tabID=-1` means "all folders"
- Amount uses dot decimal separator (`695.74`), not comma

## Config

Stored at `~/.config/yuki/config.toml`. Each administration has both a `domain_id` (for `SetCurrentDomain`) and an `admin_id` (for operation parameters) — they are different UUIDs.