1 changed files with 212 additions and 0 deletions
@ -0,0 +1,212 @@ |
|||||||
|
# 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. |
||||||
Loading…
Reference in new issue