Move the untracked 'schema draft.sql' to Documentation/schema.sql: the agreed v1 fuel-domain schema (files, customers, batches, cards, transactions, invoices, invoice_items), design decisions recorded in the header: NULL-able cleartext card PINs, slim ledger projection (10 of 16 source fields, the files table is the canonical archive), string customer business key, unified DECIMAL SEK money, full invoice traceability, cli.md status values. cli.md: transaction read takes <date> <receipt> -- the register's receipt counter repeats across days (9,990 distinct receipts in the 138k-row sample), the day+receipt pair is the ledger dedup key, verified unique in all samples. File import step 5 clarified as an internal consistency check, since source files carry no totals of their own. Invoice business key corrected to invoice number. readme.md: point the database bullet at Documentation/schema.sql.
161 lines
9.7 KiB
Markdown
161 lines
9.7 KiB
Markdown
## 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 <number>"; 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: the
|
|
│ │ # file carries no totals of its own, so this is an
|
|
│ │ # internal check -- recompute each batch touched by
|
|
│ │ # the import from the transactions table and compare
|
|
│ │ # with the stored values
|
|
│ │ # 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 by <date> <receipt>:
|
|
# the register's receipt counter repeats across days,
|
|
# so the day + receipt pair is the business key
|
|
└── list # list transactions
|
|
# optional filters --customer, --card, --batch, --from, --to
|
|
```
|
|
|
|
## Global flags
|
|
```
|
|
--config=<path> # explicit config path; takes precedence over --env
|
|
--env=[dev,test] # select config.<env>.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 number,
|
|
or filename, depending on the entity. The exception is transactions, whose
|
|
business key is the pair day + receipt (`transaction read <date> <receipt>`):
|
|
the register's receipt counter repeats across days, and the pair is the
|
|
ledger's dedup key (verified unique in all samples).
|
|
|
|
## 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 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.
|