You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

11 KiB

Local MVP Workflows Design

Purpose and scope

Roadmap step 5 gives the local owner a server-rendered Django interface for managing reference data and transactions, categorizing imported activity, and inspecting monthly summaries. It also provides durable, local exports and PostgreSQL backup and restore-verification commands. Together these complete a documented synthetic import-to-report-to-backup workflow.

The reviewed-import parser and decision workflow remains command based. The web application begins with the committed financial records and links to the local import instructions. Bank APIs, investments, forecasting, receipt recognition, mobile clients, hosted multi-user operation, and automatic or AI categorization remain outside the MVP.

Chosen approach

Use Django templates, forms, and ordinary HTTP requests. Keep read behavior in explicit query and report modules, presentation in core, and all mutations in the existing audited finance.services boundary. This adds no JavaScript framework and does not expose Django admin as a financial editor.

Alternatives considered were a command-only workflow and a client-rendered API application. Command-only operation would not provide the planned local screens. A separate API and JavaScript client would add authentication, serialization, state-management, and build-tooling work without improving this single-owner milestone.

Application surface

The root page is a monthly dashboard. The local navigation exposes accounts, transactions, categories, payees, reports, and exports, plus a link to the reviewed-import guide. Server-rendered pages use semantic HTML and remain usable without client-side JavaScript.

Transaction pages provide:

  • a paginated list ordered by posting date and stable identifier;
  • filters for inclusive date range, account, kind, payee, category, currency, and uncategorized status;
  • a detail page with relationships and source provenance;
  • manual creation through the supported financial posting service; and
  • annotation of description, payee, and category through annotate_transaction.

The interface does not delete transactions or rewrite amount, posting date, occurrence date, account, kind, refund, transfer, statement, installment, or source identity. Financial corrections are represented by new entries, refunds, or transfers through the domain services. Imported transaction evidence remains immutable.

Reference pages list active and archived accounts, categories, and payees. Creation and archival use create_account, create_category, create_payee, and archive. Archived references remain visible in historical records and cannot be selected for new assignments.

Request security and accountability

All mutation forms use Django CSRF protection and POST requests. Views never call model save() directly for financial writes. The local server derives the actor as Actor(kind="HUMAN", identifier="local-owner"); actor identity is not accepted from request data. Domain errors become safe form messages keyed by their stable error codes and never include transaction descriptions or source rows.

The application continues to bind only to loopback. Formal login and multi-user authorization are outside this local single-owner release. Django session and message support may be enabled only to carry local form feedback; no financial values are stored in cookies.

Query boundaries

finance.queries owns reusable read models for transaction and reference lists. It validates filter values, applies deterministic ordering, selects related objects deliberately, and returns querysets or immutable result values without mutating records. Views remain responsible for HTTP parsing and pagination.

finance.reports owns aggregate calculations and returns immutable report values. It performs decimal aggregation in PostgreSQL and never combines currencies. Empty periods return explicit zero totals and empty category rows.

Only query shapes used by the screens and exports receive indexes. Initial indexes cover transaction posting date and the common account-plus-posting-date path. Their migrations are verified against PostgreSQL.

Reporting semantics

All monthly reports use posting_date and an inclusive calendar month selected by the owner. Every report selects exactly one currency. Archived accounts and references remain included when they have activity in the selected period.

The monthly cash-flow summary uses economic activity across bank and credit-card accounts:

  • INCOME contributes its positive amount to inflow;
  • EXPENSE contributes the absolute value of its negative amount to spending;
  • REFUND reduces spending in the month in which the refund posts;
  • TRANSFER is excluded, including credit-card payments; and
  • OPENING is excluded.

Net activity is inflow minus net spending. Net spending may be negative when a month contains refunds greater than expenses. A card purchase is counted as spending when it posts; its later card-payment transfer is excluded, so it is not counted twice.

The spending summary groups EXPENSE and REFUND amounts by expense category. An explicit Uncategorized row includes transactions without a category. Refunds use their own assigned category and are not automatically inherited from the original purchase. Rows show expense, refund, and net-spending totals and sort by net spending descending with a stable category-name tie-breaker.

Account balances are separate from cash flow. A balance is the signed sum of all entries through the selected month end, including openings and transfer legs. Balances are reported per account and currency and are never summed across currencies.

Export format and safety

An export service and management command create a versioned ZIP package under the ignored repository-root exports/ directory. The browser can request the same package and receives it as a download; it does not choose a server filesystem path.

Each package contains a JSON manifest and UTF-8 CSV files for accounts, payees, categories, statements, installment plans, transactions, transfers, and source records. Stable UUIDs, ISO-8601 dates and timestamps, exact decimal strings, archival state, relationship UUIDs, and provenance metadata are retained. Audit events and import-review working state belong to backups rather than the portable financial export.

The manifest records the export schema version, creation time, application version, file names, row counts, and SHA-256 checksums. CSV headers and file ordering are fixed by export schema version. Database reads occur inside one consistent transaction so the package represents one snapshot.

Command output contains only the package name, version, and row counts. Export names are generated or validated as plain file names. Paths, traversal, symlinks, existing destinations, and files outside EXPORT_ROOT are rejected. The package is written to a private temporary file, flushed, and atomically renamed. Failures remove the temporary file without replacing an existing export.

PostgreSQL backups and restore verification

Backup management commands invoke the PostgreSQL 17 pg_dump and pg_restore clients from the application image. They use the management service's migration database credentials and never accept passwords as command arguments. Backups use PostgreSQL custom format with --no-owner and --no-acl and are stored under the ignored repository-root backups/ directory.

Each backup has a JSON manifest containing format version, creation time, application version, PostgreSQL client version, database name, applied Django migrations, byte size, and SHA-256 checksum. Creation uses a private temporary file, fsync, and atomic rename; existing targets are never overwritten. Terminal output excludes financial data and credentials.

Restore verification requires a database connection explicitly marked as a verification target and rejects the configured live database host/name pair. The target database must be empty. Verification checks the manifest and digest, restores with pg_restore --exit-on-error --single-transaction, then runs Django schema checks and deterministic row-count/integrity checks. The acceptance test uses a disposable PostgreSQL service populated only with synthetic data.

Restoring over the live database is not part of the web UI or automated verification. The owner-facing recovery guide documents a deliberate command sequence and requires stopping the web service, creating a separate safety backup, and naming the target database explicitly.

Configuration and storage

EXPORT_ROOT and BACKUP_ROOT default to /app/exports and /app/backups in containers. Docker Compose mounts the ignored repository directories into the management and web services with only the access each workflow needs. The PostgreSQL client major version matches the PostgreSQL 17 server.

Generated exports, backups, temporary files, database dumps, reports, and logs remain ignored. Automated tests use synthetic records and temporary directories. No fixture or assertion contains copied financial data.

Error handling

Form validation errors stay on the submitted page without mutation. Domain conflicts are mapped to stable, human-readable local messages. Missing objects return 404. Invalid report and list filters return field-level errors rather than silently changing the requested range.

Export and backup commands fail closed for invalid paths, existing files, checksum mismatch, incompatible format versions, unavailable PostgreSQL tools, database errors, or nonempty verification targets. They emit stable safe error codes and leave no published partial artifact.

Verification and completion

Unit and PostgreSQL-backed tests cover query filters, pagination stability, report calculations, currency separation, refunds, transfers, openings, uncategorized spending, archived references, and account balances. Request tests cover page rendering, CSRF-protected POST workflows, audited service writes, immutable financial fields, and safe errors.

Export tests inspect package schemas, ordering, exact decimals, manifests, checksums, snapshot behavior, path rejection, non-overwrite behavior, and failed write cleanup. Backup integration tests create a synthetic database state, produce a dump, restore it into a separate empty database, verify migrations and row integrity, and confirm the original database is unchanged.

The documented acceptance workflow creates synthetic reference data, previews and commits a synthetic import, categorizes a transaction in the browser, inspects monthly cash flow and spending, downloads an export, creates a backup, and verifies restoration into the disposable database. Roadmap step 5 is complete when that workflow and the full automated suite pass from the Docker Compose environment.