diff --git a/Documentation/cli.md b/Documentation/cli.md index 852b003..0931797 100644 --- a/Documentation/cli.md +++ b/Documentation/cli.md @@ -1,12 +1,16 @@ ## Command structure ``` rpnc -├── batch +├── 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 +├── 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 @@ -15,87 +19,132 @@ rpnc │ ├── read # fetch card details │ ├── update # modify card │ ├── delete # delete card -│ │ # only allowed on cards not referenced by any transactions +│ │ # only allowed on cards not referenced by any transactions │ └── list # list all cards -│ # allow filtering by customer, status +│ # optional filters --customer, --status │ -├── customer +├── 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 transactions +│ │ # only allowed on customers not referenced by any +│ │ # transaction, invoice, or card │ └── list # list all customers │ -├── daemon -│ ├── start # start the web server daemon -│ ├── status # check status of daemon -│ │ # - running? -│ │ # - port? -│ │ # - db access? -│ │ # - accessible from internet? -│ │ # - fqdn? +├── 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 -│ ├── reset # drop and recreate database -│ ├── status # check if connection to db is ok -│ ├── backup # create a backup of database -│ └── restore # recreate database from backup +│ ├── 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 -│ ├── import # read CSV into DB: -│ │ # 1. create any missing customers -│ │ # 2. create any missing cards +├── 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 -│ └── export # export file to specified format +│ │ # (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 +├── 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 errors is needed to be fixed on a sent 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 -│ # - allow filtering list by customer, date +│ # optional filters --customer, --from, --to │ -└── transaction # CRUD not needed via cli, transactions will only be added via "file import" +└── transaction # immutable; created only via "file import"; no create/update/delete ├── read # fetch transaction details - │ # - CREATE not needed, they will only be created via "file import" function - │ # - UPDATE not needed, transactions are immutable - │ # - DELETE not needed, transactions are immutable - └── list # list transactions (with filtering) + └── list # list transactions + # optional filters --customer, --card, --batch, --from, --to ``` -## Global flags: ---env=[dev,test] # production assumed ---quiet # for scripts only caring about exit codes ---help # display basic usage information ---format=[raw,json,csv,columns] # columns assumed +## 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 +``` -## Implementation Notes (added during CLI expansion) +## Exit codes +| Code | Meaning | +|---|---| +| 0 | success | +| 1 | domain error (validation, import mismatch, constraint violation, ...) | +| 2 | usage error (clap argument parsing) | +| 3 | entity not found | -The following clarifications were made while implementing the CLI stubs: +## 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. -- **Positional IDs**: `read`, `update`, `delete`, and `credit` subcommands that operate on a single entity take a positional `id` argument (e.g., `card read `, `invoice credit `). +## 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 export**: Takes `--format` to specify the target export format. -- **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`. -- **Invoice list**: Optional filters `--customer` and `--date`. +- **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. +- **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`. -- **Global `--env`**: Accepts `dev` or `test`; omitted means production. -- **Global `--format`**: Defaults to `columns`. +- **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.