Skip to content

AmigoUK/northwind

Repository files navigation

Northwind Traders TUI

A terminal-based warehouse and distribution management system built on the classic Northwind sample database. Stack: Python + Textual + SQLite.

Covers the full order-to-cash and purchase-to-pay cycles: Sales Orders → Delivery Notes (DN) → Invoices (INV) (multiple DNs can be consolidated into one invoice), Goods Receipts (GR) for inbound stock from suppliers, and a Cash Register + Bank Account for tracking payments in both directions. The Reconciliation panel provides an AR/AP control centre — an "All Unpaid" view across all customers or suppliers, switchable to a per-entity statement with running balance. All seven document types export to branded A4 PDF with QR codes. Role-based access (admin / manager / user), a searchable Help panel, 12 report types with CSV export, and 172+ automated tests complete the picture.

Contents

Version History · Changelog · Screenshots · Installation · Dependencies · Project Structure · Key Bindings · What I Learned


Version History

Version Theme Key additions
v1.4 Foundation 9 CRUD panels, SQL editor, 6 reports + CSV export, PIN login, role-based UI, multi-column form modals
v2.0 Documents & Finance Document workflow (DN/INV/GR/SI/SO), Cash Register & Bank Account, Charts, extended KPIs, 7 UX enhancements, finance dashboard KPIs
v2.1 PDF Export Branded A4 PDF delivery notes (DN) & invoices (INV) — company logo, theme colours, totals, linked DN references
v2.2 PDF All Docs + UX PDF export for GR, CR, CP & Bank entries; Business Details tabbed layout with docked Save button
v2.3 UI Polish Business Details compact one-screen tabs; form layout optimisation across Company / Tax / Documents
v2.4 Data Integrity 3-tier roles (user/manager/admin), delete guards, document cancellation, Credit Notes (CN), 82 automated tests
v2.5 English International All Polish abbreviations replaced with English (WZ→DN, FV→INV, FK→CN, PZ→GR, PW→SI, RW→SO, KP→CR, KW→CP)
v2.6 UI Polish Wider modals, stretch Select/Button widgets, compact picker layout
v2.7 CSV Import CSV import for master data — Customers, Suppliers, Products, Categories
v2.8 File Selector & Export Cleanup File browser modal for all exports/imports, centralized CSV export logic
v2.9 CSV Round-Trip Fix CSV import now accepts export display headers (ID, Company, Contact…) via alias mappings
v2.10 Import/Export Fixes Fix Ctrl+X/Ctrl+I keybindings globally; fix Products & Orders CSV import column mappings
v2.13 AR/AP All Unpaid View Reconciliation panel default "All Unpaid" view across all customers/suppliers; optional entity filter; sub-view toggle (All Unpaid / Statement)
v2.14 QR Codes on PDFs QR code embedded in every PDF document header; toggle in Business Details → Documents; encodes doc type, number, date, counterparty, amount
v2.15 Help System Searchable help panel with FAQ category; context-sensitive ? shortcut jumps to the relevant topic for the active panel
v2.16 Demo Data UX Test/Production mode switch in Settings; PIN-protected TestModeWarningModal; "Clean Database" modal clarifies what is preserved
v2.17 Cash Register Integrity Non-negative cash balance enforced at the data layer; GR cash payments fall back to bank when cash is insufficient; demo data generation never produces a negative cash register
v2.18 Backup on Quit Ctrl+Q dialog upgraded to Cancel / Quit / Backup & Quit; timestamped SQLite backup written to repo root with toast notification

Full release notes for each version: CHANGELOG.md

Screenshots

Click any thumbnail to view full size.

Sections: Login · Dashboard & Analytics · Master Data · Order → Delivery Note Workflow · Invoices & Credit Notes · Goods Receipts & Stock · Finance — Cash & Bank · Reconciliation · Admin & Settings · Help & Keyboard


Login

PIN-based login gate — the first screen users see on every launch. The app supports multiple user accounts with three roles (admin / manager / user); admin-only panels (SQL editor, Users, Business Details, Settings) are hidden from the sidebar for non-admin logins.

Login — PIN authentication
Login — PIN authentication

Dashboard & Analytics

The dashboard displays 10 KPI cards updated in real time: Orders Today, Revenue MTD, Low Stock alert count, Pending Orders, Average Fulfilment Days, month-over-month trend arrow, Cash Register balance, Bank Account balance, Open Invoices, and Open DNs. The Charts panel plots a Sales Trend sparkline, Top Products bar chart, and Top Employees bar chart (press R to refresh). The Reports panel offers 11 report types with a date-range filter and CSV export.

Dashboard — 10 KPI cards
Dashboard — 10 KPI cards
Charts — Sales Trend + bar charts
Charts — Sales Trend + bar charts
Reports — 11 types + date-range filter
Reports — 11 types + date-range filter

Master Data

Seven CRUD panels cover the core reference data: Customers, Orders, Products, Employees, Suppliers, Categories, Shippers, and Regions. Every list view supports live search (F), keyboard navigation, and a detail modal (Enter) for editing. Products track stock levels with a Low Stock toggle; discontinued products can be hidden via Settings.

Customers — list with live filter
Customers — list with live filter
Customer — detail / edit modal
Customer — detail / edit modal
Orders — list view
Orders — list view
Products — list with stock levels
Products — list with stock levels

Order → Delivery Note Workflow

The core outbound flow converts a Sales Order into one or more Delivery Notes (DN) and then into Invoices. From the Order detail, press D to create a DN pre-populated with the order lines; products with insufficient stock are flagged (stock-control mode is configurable). Each DN can be exported to a branded A4 PDF — with or without pricing, with an embedded QR code, and with a related-document log at the bottom. Pressing N from the DN list creates a new standalone DN without an underlying order.

Order detail — available actions
Order detail — available actions
DN — adding product (dynamic filter)
DN — adding product (dynamic filter)
Converting Order → Delivery Note
Converting Order → Delivery Note
DN — stock-control validation
DN — stock-control validation
DN PDF — pricing hidden (configurable)
DN PDF — pricing hidden (configurable)
Saving DN as branded A4 PDF
Saving DN as branded A4 PDF

Invoices & Credit Notes

An Invoice (INV) is generated from one or more approved Delivery Notes in a single action. The INV detail shows the linked DNs, payment terms, and outstanding balance. Paid invoices can be partially or fully settled; the All Unpaid view aggregates outstanding balances across all customers. When a billing error occurs, a Credit Note (CN) reverses specific line items against the original invoice. Both INV and CN export to PDF with a QR code.

Select DNs to invoice
Select DNs to invoice
Invoice preview from DNs
Invoice preview from DNs
Invoice detail — possible actions
Invoice detail — possible actions
Invoice exported to PDF
Invoice exported to PDF

Goods Receipts & Stock

Goods Receipts (GR) record inbound deliveries from suppliers and automatically increase stock levels on confirmation. A GR can be paid via Cash or Bank at the point of entry; if the cash register balance is insufficient, the payment automatically falls back to the bank account (v2.17 integrity guard). Stock Issues (SI) and Stock Outs (SO) adjust inventory directly for write-offs or internal consumption.

GR — goods receipt list
GR — goods receipt list
GR — detail with supplier & items
GR — detail with supplier & items

Finance — Cash & Bank

The Cash Register panel records Cash Receipts (CR) from customers and Cash Payments (CP) to suppliers, and shows a running balance. The balance can never go negative — any operation that would cause an overdraft is rejected with an error message. The Bank Account panel logs all electronic transfers and bank-backed GR payments. Both panels support PDF export for individual entries, and the Cash Register includes a one-click transfer-to-bank action.

Cash Register — CR/CP ledger
Cash Register — CR/CP ledger
Transfer cash → bank account
Transfer cash → bank account
Non-negative balance enforcement
Non-negative balance enforcement
Bank Account — entry log
Bank Account — entry log
Register customer payment (CR)
Register customer payment (CR)

Reconciliation

The Reconciliation panel is the AR/AP control centre. The default "All Unpaid" view lists every open invoice across all customers (or all outstanding GRs across all suppliers) in a single scrollable table. Filtering by entity switches to a Statement view for one customer or supplier — showing the document trail, payment history, and net outstanding balance. Press S to toggle between All Unpaid and Statement; P to record a payment directly from the panel.

Reconciliation — All Unpaid view
Reconciliation — All Unpaid view
Customer statement view
Customer statement view

Admin & Settings

Business Details stores company identity (name, address, VAT, bank account, logo) used on all PDFs — changes take effect immediately on next export. The Settings panel configures currency, theme (Ctrl+P palette), stock-control rules, Demo/Production mode, and the new Backup & Restore controls. User Management handles account creation, PIN resets, and role assignment. The SQL Query editor accepts any read/write SQLite statement and renders results in a live DataTable. All admin panels are hidden from non-admin users.

Business Details — company identity
Business Details — company identity
Settings — currency & theme
Settings — currency & theme
Theme picker (Ctrl+P)
Theme picker (Ctrl+P)
Backup & Restore settings
Backup & Restore settings
User Management
User Management
SQL Query editor
SQL Query editor

Help & Keyboard

The built-in Help panel (?) contains a searchable FAQ organised by category. Pressing ? from any panel pre-filters the help index to the current context (e.g. pressing ? on the Invoice list jumps to the "invoice" topic). The keyboard cheat-sheet screenshots show the complete shortcut reference available from within the app.

Help panel — context-sensitive search
Help panel — context-sensitive search
Keyboard cheat sheet (page 1)
Keyboard cheat sheet (page 1)
Keyboard cheat sheet (page 2)
Keyboard cheat sheet (page 2)

Installation

Quick start

Linux / macOS

git clone git@github.com:AmigoUK/northwind.git
cd northwind
bash install.sh      # one-time: checks Python 3.9+, installs all packages
bash app.sh          # run the app (re-checks deps on every launch)

Windows

git clone git@github.com:AmigoUK/northwind.git
cd northwind
install.bat          ← one-time installer
app.bat              ← run the app (auto-installs if deps are missing)

Default login: username admin / PIN 1234

Manual installation (all platforms)

# Requires Python 3.9+
pip install -r requirements.txt
python3 app.py          # Linux / macOS
python  app.py          # Windows

What the scripts do

Script Platform Purpose
install.sh Linux/macOS Checks Python ≥ 3.9, runs pip3 install -r requirements.txt
app.sh Linux/macOS Checks deps on every launch; calls install.sh if any are missing
install.bat Windows Checks Python ≥ 3.9, runs pip install -r requirements.txt
app.bat Windows Checks deps on every launch; calls install.bat if any are missing

Dependencies

Package Version Purpose
textual ≥ 0.80.0 TUI framework
plotext ≥ 5.2 ASCII charts in terminal (v2.0)
fpdf2 ≥ 2.7 PDF generation — branded delivery notes & invoices (v2.1)
Pillow ≥ 10.0 Company logo embedding in PDFs; QR image conversion (v2.1, v2.14)
qrcode ≥ 7.4 QR code generation for all PDF document types (v2.14)

Install all at once:

pip install -r requirements.txt

Project Structure

northwind/
├── app.py              # Textual App entry point, login flow, sidebar nav
├── db.py               # SQLite schema DDL + seed data
├── pdf_export.py       # PDF generation for all document types (v2.1–v2.14)
├── northwind.tcss      # Textual CSS (layout, modals, panels, charts)
├── requirements.txt    # Python dependencies
├── assets/             # Static assets — company logo copied here via Business Details browse
├── CLAUDE.md           # Claude Code context — per-commit checklist, architecture notes
├── data/               # Data-access layer (pure SQL, no UI)
│   ├── settings.py     # AppSettings key-value store (currency, theme, business details)
│   ├── users.py        # AppUsers CRUD + PIN authentication + role hierarchy (v2.4)
│   ├── dashboard.py    # KPI aggregations + kpis_extended()
│   ├── reports.py      # 12 report queries (sales, stock, trend, overdue, supplier spending…)
│   ├── delete_guards.py # Centralized delete guards + side-effect handlers (v2.4)
│   ├── dn.py           # DN (Delivery Note) CRUD + issue + cancel workflow
│   ├── inv.py          # INV (Invoice) CRUD + cancel
│   ├── cn.py           # CN (Credit Note) CRUD + business logic (v2.4)
│   ├── gr.py           # GR (Goods Receipt) CRUD + cancel; cash→bank fallback on receive (v2.17)
│   ├── si_so.py        # SI/SO (Stock Issue / Stock Out) CRUD
│   ├── cash.py         # Cash Register entries (CR/CP); get_cash_balance() + non-negative guards (v2.17)
│   ├── bank.py         # Bank Account entries
│   ├── reconciliation.py # AR/AP reconciliation queries, aging, allocation (v2.13)
│   └── ...             # customers, orders, products, employees, …
├── screens/            # Textual Widget subclasses (one per section)
│   ├── login.py        # LoginScreen modal (PIN gate)
│   ├── dashboard.py    # Dashboard KPI cards + recent orders
│   ├── charts.py       # Charts panel — Sales Trend / Category Mix / Employees / Cash & Bank Account
│   ├── reports.py      # Reports panel with 12 report types + CSV export
│   ├── cn.py           # CN Credit Notes panel + creation wizard + detail modal (v2.4)
│   ├── modals.py       # Shared modals: ConfirmDelete, CleanDatabase, TestModeWarning, FileSelectModal, QuitConfirmModal (v2.8, v2.16, v2.18)
│   ├── export_helpers.py # Centralized CSV export logic + FileSelectModal integration (v2.8)
│   ├── dn.py           # DN Delivery Notes panel + modals + cancel button
│   ├── inv.py          # INV Invoices panel + modals + CN integration
│   ├── gr.py           # GR Goods Receipts panel + modals + cancel button
│   ├── reconciliation.py # AR/AP Reconciliation panel — All Unpaid + Statement sub-views (v2.13)
│   ├── help.py         # Help panel — searchable topics + context-sensitive open_with_context() (v2.15)
│   └── ...             # sql, settings, business, users, cash, bank, …
└── tests/              # Automated test suite — 172 tests across 8 modules
    ├── conftest.py     # Fresh temp DB per test
    ├── test_data.py    # Core business logic (19 tests)
    ├── test_delete_guards.py  # Delete guards + side-effects (26 tests)
    ├── test_cancellation.py   # DN/INV/GR cancellation (13 tests)
    ├── test_cn.py             # Credit Notes — all 3 types (24 tests)
    ├── test_csv_import.py     # CSV import round-trip + alias mapping (28 tests)
    ├── test_demo.py           # Demo data insert / clean / has_demo_data (48 tests)
    └── test_reconciliation.py # AR/AP reconciliation + All Unpaid queries (14 tests)

Key Bindings

Key Action
ctrl+Q Quit (shows Cancel / Quit / Backup & Quit dialog)
N New record (in active panel)
F Focus search box (in active panel)
ctrl+r Run SQL query (SQL Query panel)
ctrl+X Export current view to CSV
ctrl+I Import CSV into current panel
R Refresh (Dashboard / Charts / Reports)
U Switch to All Unpaid view (Reconciliation panel)
S Switch to Statement view (Reconciliation panel)
P Pay selected Invoice (Reconciliation panel)
? Open Help (context-sensitive — jumps to topic for the active panel)
ESC Close modal / go back

Quit dialog keys (shown after Ctrl+Q)

Key Action
Esc Cancel — stay in app
Q Quit without backup
B Backup & Quit

What I Learned

Concept What I learned Where
SQLite & SQL CRUD, multi-table JOINs, aggregations, transactions; window functions (strftime, julianday); INSERT OR REPLACE as a key-value upsert; reading column names from cursor.description; conn.execute() vs conn.executescript() for safe schema migration; why JOIN vs LEFT JOIN matters in date-filtered queries (LEFT JOIN lets zero-revenue rows leak through) db.py, data/*.py
MVC layered architecture Keeping all SQL in a pure data/ layer and all Textual widget code in screens/ so queries are independently testable; centralising delete guards as (bool, reasons) tuples with side-effect handlers (stock restore, payment reversal) that execute before the DELETE data/, screens/, data/delete_guards.py
Textual widgets & navigation ContentSwitcher for single-page panel routing; ModalScreen.dismiss(value) callback pattern; stacking modals (file browser pushed from within an import modal); TabbedContent/TabPane; TextArea for freeform SQL input; DirectoryTree subclassing with filter_paths() for extension-based browsing app.py, screens/*.py
Textual CSS layout 1fr grid columns for compact multi-column form rows; CSS selector specificity rules (type+class beats class alone, later rule wins on tie); dock: bottom to pin a Save button regardless of sibling height; overflow-y: auto on a TabPane to make long forms scrollable; balancing height: 2 on Select widgets to keep forms on one screen northwind.tcss
Role-based access control One-way PIN hashing with hashlib.sha256; numeric role levels (user=1 / manager=2 / admin=3) compared with a has_permission() helper; hiding sidebar items and entire panels at login so the app surface adapts to the authenticated role data/users.py, app.py
Document workflow state machine Enforcing draft → issued → cancelled transitions at the data layer; recording audit fields (CancelledAt / CancelledBy / CancelReason) on cancellation; Credit Notes capture original vs corrected line-item values and adjust the parent invoice's TotalNet and status in a single transaction data/dn.py, data/inv.py, data/gr.py, data/cn.py
PDF generation with fpdf2 Subclassing FPDF for custom headers/footers; shared drawing helpers (_draw_header, _draw_amount_box, _draw_signature_line) reused across all document types; voucher-style single-entry PDFs (CR/CP/Bank) vs multi-line table PDFs (DN/INV/GR); embedding a company logo with Pillow (.convert("RGB") to strip PNG alpha before fpdf2 accepts it); generating QR codes with qrcode and a tempfile.NamedTemporaryFile(delete=False) pattern that works on Windows pdf_export.py
CSV round-trip import/export Exporting with csv.writer through a centralized helper that integrates with the file-browser modal; accepting both internal DB column names and human-readable display headers via an alias map with setdefault so exact matches always take priority screens/export_helpers.py, data/csv_import.py
Multi-step modal wizard Implementing a multi-step flow (pick invoice → select CN type → choose line items → confirm) inside a single ModalScreen, keeping all intermediate state local and committing to the DB only on final confirmation screens/cn.py
Finance integrity & fallback routing Raising ValueError at the data layer before any INSERT so the UI can display the error without the DB ever seeing an invalid state; checking a resource constraint at the call site and re-routing (cash → bank account) instead of raising, so automated GR payment flows never crash on an insufficient cash balance data/cash.py, data/gr.py
Async UI timing traps Switch.Changed enqueues its message rather than firing in-place, so a _mode_switching guard flag is already False by the time the handler runs; robust fix: compare event.value against the value persisted in the DB rather than an in-memory flag screens/settings.py
Context-sensitive help Storing a {panel_id: keyword} map in the app; calling open_with_context(keyword) to pre-filter the Help panel when ? is pressed, so the user lands on the relevant topic for whichever panel is active app.py, screens/help.py
Automated testing tmp_path fixture in conftest.py for a fresh isolated SQLite DB per test; testing full cross-document chains (Order → DN → INV → CR → CN → verify all balances update correctly); 172 tests across 8 modules with zero external dependencies tests/
Python compatibility & file utilities X | Y union type syntax requires Python 3.10+; use Optional[X] or bare annotation for 3.9 compat; shutil.copy2() to copy a user-selected logo into assets/ with metadata preserved; same function for timestamped DB backups wrapped in try/except OSError so a permission error surfaces as a toast, not a crash screens/business.py, app.py

About

Northwind Traders TUI — Textual-based terminal app for the classic Northwind SQLite database

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages