No description
  • Ruby 55.9%
  • HTML 40.9%
  • JavaScript 2.4%
  • Dockerfile 0.5%
  • Shell 0.2%
  • Other 0.1%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
whysthatso 2193bf5d8d
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
Merge development into main
2026-10-12 14:18:07 +03:00
.claude Document renaming stored receipts 2026-10-12 14:18:04 +03:00
app Rename stored receipts from the bill edit form 2026-10-12 14:18:04 +03:00
bin Ruby 4 image, synchronous app, Woodpecker CI and CapRover deploy 2026-10-08 13:51:33 +03:00
config Rename stored receipts from the bill edit form 2026-10-12 14:18:04 +03:00
db Persist bank statements and register bookings one by one 2026-10-09 12:48:32 +03:00
documentation Document renaming stored receipts 2026-10-12 14:18:04 +03:00
lib/tasks Add test suite and fixtures describing the target behaviour 2026-10-08 12:22:18 +03:00
log initial commit 2016-12-19 17:00:27 +02:00
public add missing pieces from old version 2025-02-05 00:25:51 +02:00
script Align the app with rails_rules: REST, per-element Turbo forms, receipts PDF 2026-10-08 12:22:18 +03:00
storage rewrite to rails 8 2025-01-31 11:17:22 +02:00
test Rename stored receipts from the bill edit form 2026-10-12 14:18:04 +03:00
tmp rewrite to rails 8 2025-01-31 11:17:22 +02:00
vendor rewrite to rails 8 2025-01-31 11:17:22 +02:00
.annotaterb.yml Baseline: toolchain update before rails_rules refactor 2026-10-07 16:09:17 +03:00
.dockerignore Ruby 4 image, synchronous app, Woodpecker CI and CapRover deploy 2026-10-08 13:51:33 +03:00
.gitattributes rewrite to rails 8 2025-01-31 11:17:22 +02:00
.gitignore Align the app with rails_rules: REST, per-element Turbo forms, receipts PDF 2026-10-08 12:22:18 +03:00
.herb.yml Ruby 4 image, synchronous app, Woodpecker CI and CapRover deploy 2026-10-08 13:51:33 +03:00
.standard.yml Align the app with rails_rules: REST, per-element Turbo forms, receipts PDF 2026-10-08 12:22:18 +03:00
.woodpecker.yml no lint and test for now, done locally 2026-10-08 16:32:19 +03:00
CLAUDE.md Document renaming stored receipts 2026-10-12 14:18:04 +03:00
config.ru rewrite to rails 8 2025-01-31 11:17:22 +02:00
Dockerfile Ruby 4 image, synchronous app, Woodpecker CI and CapRover deploy 2026-10-08 13:51:33 +03:00
Gemfile Ruby 4 image, synchronous app, Woodpecker CI and CapRover deploy 2026-10-08 13:51:33 +03:00
Gemfile.lock Ruby 4 image, synchronous app, Woodpecker CI and CapRover deploy 2026-10-08 13:51:33 +03:00
mise.toml Ruby 4 image, synchronous app, Woodpecker CI and CapRover deploy 2026-10-08 13:51:33 +03:00
package-lock.json Ruby 4 image, synchronous app, Woodpecker CI and CapRover deploy 2026-10-08 13:51:33 +03:00
package.json Ruby 4 image, synchronous app, Woodpecker CI and CapRover deploy 2026-10-08 13:51:33 +03:00
Procfile.dev Align the app with rails_rules: REST, per-element Turbo forms, receipts PDF 2026-10-08 12:22:18 +03:00
Rakefile rewrite to rails 8 2025-01-31 11:17:22 +02:00
README.md Document persisted statements and receipt names 2026-10-09 13:00:40 +03:00
why_logo_200.svg add missing pieces from old version 2025-02-05 00:25:51 +02:00

why-accounting

The internal invoicing and bookkeeping application of whyservices OÜ, built with Rails 8.1, Hotwire and Tailwind 4.

It keeps clients, projects, invoices and their line items; renders invoices as PDF in English or German; mails invoices and payment reminders with the PDF attached; records vendors, expense accounts and bills with their receipts, named YYYY-MM-DD_vendor_invoicenumber.ext; and imports LHV bank statements (CSV), keeps every upload, and registers its bookings one by one as bills or invoice payments. Bookings seen in an earlier upload are skipped.

There is no login. Run it only where you control who can reach it (see documentation/issues/no-authentication-2026-10-07.md).

Setup

Requirements: mise and network access to the PostgreSQL host db.

mise trust                        # once per checkout
mise install                      # Ruby 4 and Node, as pinned in mise.toml

The development server needs the development database password. Put it in mise.local.toml (git-ignored) next to mise.toml:

[env]
WHY_ACCOUNTING_DB_PASS_DEV = "development database password"

Tests, linters and CI do not need it. Then install dependencies and prepare the database:

bundle install
npm install
npx playwright install chromium   # once per machine, for system tests
bin/rails db:prepare

bin/setup does the installation and database steps in one go and starts the server. JavaScript packages are managed with npm only (package-lock.json).

Running

bin/dev

bin/dev starts Procfile.dev through overmind: the Rails server on http://localhost:3002, esbuild and the Tailwind CLI in watch mode, and a Tailscale funnel to port 3002. Stop the tunnel process if you do not want the app reachable from outside.

In development no mail leaves the machine: every mail opens at http://localhost:3002/letter_opener (letter_opener_web). Only production delivers through mailbox.org SMTP; tests use the :test delivery method.

There is no job worker. Active Job runs :inline, so everything, including Active Storage's analysis and purging of receipts, happens within the request.

Tests

bin/rails test                    # models, controllers, mailers, PDF, i18n, docs
bin/rails test:system             # Playwright browser test
PLAYWRIGHT_HEADED=1 bin/rails test:system

The test database is why_accounting_test on host db. To use a separate database (a second checkout, or a parallel run), set WHY_ACCOUNTING_TEST_DB:

RAILS_ENV=test WHY_ACCOUNTING_TEST_DB=why_accounting_test_2 bin/rails db:test:prepare test

Linters

bundle exec standardrb                   # Ruby
bundle exec herb lint                    # ERB (.herb.yml and package.json pin 0.11.0)
bin/brakeman                             # security
bundle exec i18n-tasks missing           # translations; also unused, normalize
bundle exec bundle-audit check --update  # vulnerable gems

Continuous integration

Woodpecker runs .woodpecker.yml on git.whyservices.net:

Step When What
lint every push, pull request and tag standardrb, herb, brakeman, bundle-audit
test every push, pull request and tag bin/rails test and bin/rails test:system against a Postgres service named db
build tag builds Dockerfile, pushes git.whyservices.net/whyservices/accounting:<tag>
deploy tag, after a successful build caprover deploy of that image to the app why-accounting

Steps run in order, so a lint or test failure stops the release.

Deployment

Production is the CapRover app why-accounting on https://captain.d.p.0000-shed.hel1.he.wlhs.eu. To release, tag a commit on main with the next number (tags are plain integers) and push the tag:

git tag 39 main
git push origin 39

The image (ruby:4-alpine, puma on port 3000) runs bin/rails db:prepare on start, so pending migrations are applied before the server starts. Production uses one PostgreSQL database, set by DB_HOST, DB_NAME, DB_USER and DB_PASS, and stores receipts on Backblaze B2.

The CapRover app settings, environment variables and Woodpecker secrets it needs are listed in documentation/user_manual/deployment.md.

Documentation

Directory Contents
documentation/plans/ Approved plans, archived as written
documentation/history/ Numbered technical records of each milestone: what changed, why and how
documentation/features/ One page per feature, for users and developers new to the code
documentation/user_manual/ How to use the application, development-setup.md for developers and deployment.md for operations
documentation/development_manual/ Implementation notes per subsystem: contracts and traps for whoever changes the code
documentation/issues/ Only the no-authentication issue, not yet moved to the Basecamp board
documentation/references/ External documents the project relies on

Issues are tracked on the Basecamp card table https://app.basecamp.com/5939009/buckets/43205343/card_tables/10389155796 (Triage column), not in the repository.

Receipts are uploaded from the browser straight to Backblaze B2, in development too; the bucket needs a CORS rule, described in documentation/user_manual/deployment.md.

CLAUDE.md and .claude/project_rules.md guide AI coding agents; they import the shared rules through the .rails_rules symlink, which is per machine and git-ignored.