Files

9.7 KiB

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.