## Command structure ``` rpnc ├── batch # derived entity: created and maintained by "file import" │ ├── update # 1. Recalculate all values of batch using data from transactions table │ │ # 2. Update batch values │ │ # Manual reconciliation: transactions are immutable, so this │ │ # exists to recompute stored values after migration/correction │ └── list # list all batches in DB │ # optional filters --from, --to, --year │ ├── card # contract fuel card only (epsilon row with a non-empty │ # customer number); retail transactions carry no card │ ├── create # add card to DB │ │ # - require customer │ │ # - require status │ │ # - require description │ │ # - require pin │ ├── read # fetch card details │ ├── update # modify card │ ├── delete # delete card │ │ # only allowed on cards not referenced by any transactions │ └── list # list all cards │ # optional filters --customer, --status │ ├── config # effective configuration │ └── show # print effective config, secrets masked │ ├── customer # contract customer; identity is the register customer number │ ├── create # add customer │ │ # - require customer number (ID) and name │ ├── read # fetch customer details │ ├── update # modify customer │ ├── delete # delete customer │ │ # only allowed on customers not referenced by any │ │ # transaction, invoice, or card │ └── list # list all customers │ ├── daemon # web server daemon (axum API + Leptos SPA) │ ├── start # start the web server daemon (detached; pidfile and logs │ │ # under the state directory from config) │ ├── status # check status of daemon (local checks only, no external call): │ │ # - running? (pid alive) │ │ # - port? (listening) │ │ # - db access? (connection ok) │ │ # - fqdn? (reported from config, not verified externally) │ └── stop # stop daemon │ ├── db │ ├── setup # create database and schema (embedded migrations) │ ├── reset # drop and recreate database (requires --force) │ ├── status # check if connection to db is ok; reports migration version │ ├── backup # create a backup of database (timestamped file in the │ │ # configured backup directory) │ └── restore # recreate database from backup file (requires --force) │ ├── file # imported source files; the filename is the natural key, │ # so ingesting the same file twice must not duplicate data │ ├── import # read source file into DB (v1: epsilon TSV only): │ │ # 1. create any missing customers (placeholder name: │ │ # "Customer "; the file carries no name) │ │ # 2. create any missing cards (pin/description nullable) │ │ # 3. create transactions │ │ # 4. create any missing batches │ │ # 5. verify batch values match calculated value │ │ # one import is one DB transaction: a step 5 mismatch │ │ # aborts and persists nothing (exit code 1) │ ├── list # list all files stored in DB │ │ # (name, format, batch, row count, imported-at) │ └── export # export a stored file to the specified format │ # --format raw = byte-faithful copy of the source │ ├── invoice # outgoing fuel invoices only (a Swedish invoice names │ # one buyer); tsdrms/subfranchise data is never invoiced │ ├── create # add invoice to DB: │ │ # - require batch OR date range │ │ # - require "all" or specific customer │ │ # - "all" fans out: one invoice per customer in the period │ │ # - amounts stored VAT-inclusive; the 25% base/VAT split │ │ # is computed at creation │ ├── read # fetch invoice details │ ├── update # modify invoice │ │ # - only allowed on invoices with "draft" status │ ├── send # transition invoice from "draft" to "sent" │ ├── delete # delete invoice │ │ # - only allowed on invoice with the highest ID number │ │ # - only allowed if invoice status is "draft" │ │ # - known limitation: a still-draft credit invoice can no │ │ # longer be deleted once a newer invoice exists; fix it │ │ # with a newer credit │ ├── credit # create a credit invoice │ │ # - required if an error needs to be fixed on a sent invoice │ ├── export # write HTML files to disk │ │ # - pure file writer; does not change invoice status │ └── list # list all invoices │ # optional filters --customer, --from, --to │ └── transaction # immutable; created only via "file import"; no create/update/delete ├── read # fetch transaction details └── list # list transactions # optional filters --customer, --card, --batch, --from, --to ``` ## Global flags ``` --config= # explicit config path; takes precedence over --env --env=[dev,test] # select config..toml; production (config.toml) assumed --quiet # suppress all output; the exit code is authoritative --format=[raw,json,csv,columns] # columns assumed; raw is only honored by `file export` --help, --version # generated by clap, per subcommand ``` ## Exit codes | Code | Meaning | |---|---| | 0 | success | | 1 | domain error (validation, import mismatch, constraint violation, ...) | | 2 | usage error (clap argument parsing) | | 3 | entity not found | ## ID semantics Positional `id` arguments take the business key of the entity, never a surrogate key: customer number, card number, batch number, invoice id, or filename, depending on the entity. ## Status values - card: active / suspended / cancelled - invoice: draft / sent Sent invoices are immutable; corrections go through `credit`. (A "paid" state is a future concern, out of scope for v1.) ## Design decisions (added during CLI design) The following decisions were made while designing the CLI command structure: - **Positional IDs**: `read`, `update`, `delete`, `send`, and `credit` subcommands that operate on a single entity take a positional `id` argument (e.g., `card read `, `invoice credit `). - **Card**: only contract fuel cards exist in the DB; a card always belongs to a customer. - **Card create**: Uses flags `--customer`, `--status`, `--description`, `--pin`. - **Card list**: Optional filters `--customer` and `--status`. - **Customer create**: Uses flags `--id` and `--name`. - **File import**: Takes a positional path to the source file; v1 accepts epsilon TSV files only (tsdrms xlsx and subfranchise PDF are future work). - **File export**: Takes a positional filename; `--format` selects the target export format, `raw` being a byte-faithful copy of the original source. - **Db backup / restore**: Take an optional positional file path; default to the configured backup directory (the latest dump for restore). - **Invoice scope**: outgoing fuel invoices only; tsdrms/subfranchise data feeds the bookkeeping/voucher feature, never invoicing. - **Invoice create**: Uses flags `--batch`, `--from`, `--to`, `--customer`, and `--all`. The caller must supply either `--batch` or a `--from`/`--to` date range, and either `--customer` or `--all`. `--all` fans out to one invoice per customer found in the period. - **Invoice send**: Takes a positional `id`; the only status transition in v1. - **Invoice list**: Optional filters `--customer` and `--from`/`--to`. - **Transaction list**: Optional filters `--customer`, `--card`, `--batch`, `--from`, `--to`. - **VAT**: transaction amounts are stored VAT-inclusive as delivered by the register; the 25% base/VAT split is computed at invoice creation. - **Global `--env`**: Accepts `dev` or `test`; omitted means production. An explicit `--config` path takes precedence. - **Global `--format`**: Defaults to `columns`; `raw` is only honored by `file export` and is a usage error (exit code 2) on any other command.