Redesign CLI command structure after design review
Reframe the implementation notes as design decisions (no code exists yet) and resolve the open design questions: - card: contract fuel card only; retail rows carry no card - customer delete also guards against invoices and cards - invoice: outgoing fuel only; add explicit send (draft -> sent); --all fans out to one invoice per customer; document statuses - VAT: amounts stored inclusive, 25% base/VAT split at invoice time - file import: v1 = epsilon TSV only; one DB transaction per import; batch-total mismatch aborts with exit code 1 - file export --format raw is source-faithful; raw errors elsewhere - batch: document derived nature; update = manual reconciliation; list gains --from/--to/--year filters - daemon status is local-only (no external check); pidfile/logs under a configured state directory - db: --force on reset/restore; timestamped backups in configured dir; status reports migration version - add config show, --version, --config precedence, exit-code table, business-key ID semantics, and status values
This commit is contained in:
+93
-44
@@ -1,12 +1,16 @@
|
|||||||
## Command structure
|
## Command structure
|
||||||
```
|
```
|
||||||
rpnc
|
rpnc
|
||||||
├── batch
|
├── batch # derived entity: created and maintained by "file import"
|
||||||
│ ├── update # 1. Recalculate all values of batch using data from transactions table
|
│ ├── update # 1. Recalculate all values of batch using data from transactions table
|
||||||
│ │ # 2. Update batch values
|
│ │ # 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
|
│ └── 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
|
│ ├── create # add card to DB
|
||||||
│ │ # - require customer
|
│ │ # - require customer
|
||||||
│ │ # - require status
|
│ │ # - require status
|
||||||
@@ -17,85 +21,130 @@ rpnc
|
|||||||
│ ├── delete # delete 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
|
│ └── 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
|
│ ├── create # add customer
|
||||||
│ │ # - require customer number (ID) and name
|
│ │ # - require customer number (ID) and name
|
||||||
│ ├── read # fetch customer details
|
│ ├── read # fetch customer details
|
||||||
│ ├── update # modify customer
|
│ ├── update # modify customer
|
||||||
│ ├── delete # delete 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
|
│ └── list # list all customers
|
||||||
│
|
│
|
||||||
├── daemon
|
├── daemon # web server daemon (axum API + Leptos SPA)
|
||||||
│ ├── start # start the web server daemon
|
│ ├── start # start the web server daemon (detached; pidfile and logs
|
||||||
│ ├── status # check status of daemon
|
│ │ # under the state directory from config)
|
||||||
│ │ # - running?
|
│ ├── status # check status of daemon (local checks only, no external call):
|
||||||
│ │ # - port?
|
│ │ # - running? (pid alive)
|
||||||
│ │ # - db access?
|
│ │ # - port? (listening)
|
||||||
│ │ # - accessible from internet?
|
│ │ # - db access? (connection ok)
|
||||||
│ │ # - fqdn?
|
│ │ # - fqdn? (reported from config, not verified externally)
|
||||||
│ └── stop # stop daemon
|
│ └── stop # stop daemon
|
||||||
│
|
│
|
||||||
├── db
|
├── db
|
||||||
│ ├── setup # create database and schema
|
│ ├── setup # create database and schema (embedded migrations)
|
||||||
│ ├── reset # drop and recreate database
|
│ ├── reset # drop and recreate database (requires --force)
|
||||||
│ ├── status # check if connection to db is ok
|
│ ├── status # check if connection to db is ok; reports migration version
|
||||||
│ ├── backup # create a backup of database
|
│ ├── backup # create a backup of database (timestamped file in the
|
||||||
│ └── restore # recreate database from backup
|
│ │ # configured backup directory)
|
||||||
|
│ └── restore # recreate database from backup file (requires --force)
|
||||||
│
|
│
|
||||||
├── file
|
├── file # imported source files; the filename is the natural key,
|
||||||
│ ├── import # read CSV into DB:
|
│ # so ingesting the same file twice must not duplicate data
|
||||||
│ │ # 1. create any missing customers
|
│ ├── import # read source file into DB (v1: epsilon TSV only):
|
||||||
│ │ # 2. create any missing cards
|
│ │ # 1. create any missing customers (placeholder name:
|
||||||
|
│ │ # "Customer <number>"; the file carries no name)
|
||||||
|
│ │ # 2. create any missing cards (pin/description nullable)
|
||||||
│ │ # 3. create transactions
|
│ │ # 3. create transactions
|
||||||
│ │ # 4. create any missing batches
|
│ │ # 4. create any missing batches
|
||||||
│ │ # 5. verify batch values match calculated value
|
│ │ # 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
|
│ ├── 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:
|
│ ├── create # add invoice to DB:
|
||||||
│ │ # - require batch OR date range
|
│ │ # - require batch OR date range
|
||||||
│ │ # - require "all" or specific customer
|
│ │ # - 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
|
│ ├── read # fetch invoice details
|
||||||
│ ├── update # modify invoice
|
│ ├── update # modify invoice
|
||||||
│ │ # - only allowed on invoices with "draft" status
|
│ │ # - only allowed on invoices with "draft" status
|
||||||
|
│ ├── send # transition invoice from "draft" to "sent"
|
||||||
│ ├── delete # delete invoice
|
│ ├── delete # delete invoice
|
||||||
│ │ # - only allowed on invoice with the highest ID number
|
│ │ # - only allowed on invoice with the highest ID number
|
||||||
│ │ # - only allowed if invoice status is "draft"
|
│ │ # - 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
|
│ ├── 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
|
│ ├── export # write HTML files to disk
|
||||||
|
│ │ # - pure file writer; does not change invoice status
|
||||||
│ └── list # list all invoices
|
│ └── 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
|
├── read # fetch transaction details
|
||||||
│ # - CREATE not needed, they will only be created via "file import" function
|
└── list # list transactions
|
||||||
│ # - UPDATE not needed, transactions are immutable
|
# optional filters --customer, --card, --batch, --from, --to
|
||||||
│ # - DELETE not needed, transactions are immutable
|
|
||||||
└── list # list transactions (with filtering)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Global flags:
|
## Global flags
|
||||||
--env=[dev,test] # production assumed
|
```
|
||||||
--quiet # for scripts only caring about exit codes
|
--config=<path> # explicit config path; takes precedence over --env
|
||||||
--help # display basic usage information
|
--env=[dev,test] # select config.<env>.toml; production (config.toml) assumed
|
||||||
--format=[raw,json,csv,columns] # columns 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 <id>`, `invoice credit <id>`).
|
## 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 <id>`, `invoice credit <id>`).
|
||||||
|
- **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 create**: Uses flags `--customer`, `--status`, `--description`, `--pin`.
|
||||||
- **Card list**: Optional filters `--customer` and `--status`.
|
- **Card list**: Optional filters `--customer` and `--status`.
|
||||||
- **Customer create**: Uses flags `--id` and `--name`.
|
- **Customer create**: Uses flags `--id` and `--name`.
|
||||||
- **File export**: Takes `--format` to specify the target export format.
|
- **File import**: Takes a positional path to the source file; v1 accepts epsilon TSV files only (tsdrms xlsx and subfranchise PDF are future work).
|
||||||
- **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`.
|
- **File export**: Takes a positional filename; `--format` selects the target export format, `raw` being a byte-faithful copy of the original source.
|
||||||
- **Invoice list**: Optional filters `--customer` and `--date`.
|
- **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`.
|
- **Transaction list**: Optional filters `--customer`, `--card`, `--batch`, `--from`, `--to`.
|
||||||
- **Global `--env`**: Accepts `dev` or `test`; omitted means production.
|
- **VAT**: transaction amounts are stored VAT-inclusive as delivered by the register; the 25% base/VAT split is computed at invoice creation.
|
||||||
- **Global `--format`**: Defaults to `columns`.
|
- **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.
|
||||||
|
|||||||
Reference in New Issue
Block a user