Skip to content

Latest commit

 

History

63 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Consulting realization kernel

CI License Node

Approved consulting time becomes utilization, WIP dollars, a draft milestone or contract invoice, and project margin. A draft invoice does not consume WIP; time and expenses stay unbilled until the invoice is issued.

Permit rule: Realization numbers come from approved time, WIP, and contracts — not from a model. Humans operate this kernel. Same instinct: SafetyMP.

Why not Kimai

  • Kimai is a time tracker. Hours, approvals, and timesheets are its product.
  • This repo is realization. Approved time becomes utilization, WIP dollars, a draft milestone or contract invoice, and project margin.
  • Postgres RLS is the tenancy model. Organization isolation is enforced in the database, not only in application middleware.

PSA platform demo — dashboard, invoices, and profitability views

Features

  • Multi-tenant organizations with role-based access (Admin, Manager, Consultant)
  • Clients & projects — tasks, team members, budgets, billing models (T&M, fixed fee, retainer, milestone)
  • Time & expenses — approval workflows, categories, receipt uploads, bulk approval
  • Resources — profiles, allocations, utilization reporting
  • Billing — WIP and contract/progress invoicing, milestone billing, PDF export, journal CSV
  • Accounting — chart of accounts, Xero / QuickBooks OAuth, push journals from invoices
  • Reporting — profitability with contract/milestone columns, expense breakdown by category
  • Row-level security — PostgreSQL RLS enforces organization isolation

Screenshots

Dashboard Invoices Profitability Accounting
Dashboard Invoices Profitability Accounting

Stack

Prerequisites

  • Node.js 22+ (see .nvmrc)
  • Docker (for local Postgres) or an existing PostgreSQL 16 instance
  • npm

The one job without Postgres is realization. A draft keeps approved time as unbilled WIP (an ASC 606 contract asset). Issuing the invoice is what marks it invoiced, and voiding a draft puts it back.

npm install
npm run realize

Quick start

git clone https://github.com/SafetyMP/Professional-Service-Automation.git
cd Professional-Service-Automation

cp .env.example .env
# Edit .env: set AUTH_SECRET (e.g. openssl rand -base64 32)

docker compose up -d

npm install
npm run db:migrate
npm run db:seed

npm run dev -- -p 3005
# http://localhost:3005

Demo login

Field Value
Organization demo-firm
Admin admin@demo.com / password123
Manager manager@demo.com / password123

Do not use demo credentials in production. Change passwords and rotate AUTH_SECRET before deploying.

Environment variables

See .env.example:

Variable Purpose
DATABASE_URL App database URL (RLS role psa_app)
DIRECT_URL Direct Postgres URL for migrations (postgres superuser)
AUTH_SECRET Session signing secret (required)
AUTH_URL Public app URL (e.g. http://localhost:3005)
XERO_CLIENT_ID / XERO_CLIENT_SECRET Optional — Xero OAuth
QUICKBOOKS_CLIENT_ID / QUICKBOOKS_CLIENT_SECRET Optional — QuickBooks OAuth
QUICKBOOKS_ENV sandbox (default) or production

Local Docker Postgres listens on port 5440 (mapped from container 5432).

Deploy

For production or a one-command demo stack (Postgres + app in Docker):

export AUTH_SECRET="$(openssl rand -base64 32)"
docker compose -f docker-compose.stack.yml up --build -d
# http://localhost:3000 — demo-firm / admin@demo.com / password123

See docs/deploy.md for Railway, Fly.io, manual Node deployment, and the production checklist.

Scripts

Command Description
npm run dev Prisma generate + Next.js dev server
npm run build Production build
npm run lint ESLint
npm run typecheck TypeScript check
npm run test Vitest unit tests
npm run realize Draft holds WIP; issue realizes it; void refuses an issued invoice
npm run tenancy psa_app sees no password hashes until an org or login email is set
npm run db:migrate Apply Prisma migrations
npm run db:seed Reset and seed demo data
npm run check:boundaries Module import boundary lint
./scripts/verify.sh Full local verification gate
npm run screenshots Capture README screenshots and demo GIF (requires running server)

Project layout

lib/           Domain services (clients, projects, time, billing, …)
src/app/       Next.js App Router pages and API routes
prisma/        Schema, migrations, RLS policies
specs/         Domain rules and product scope
tests/         Unit tests
scripts/       Seed, verify, boundary checks

Domain logic lives in lib/<domain>/service.ts. Cross-domain imports must go through public service files only (enforced by scripts/check-boundaries.ts).

Contributing

Contributions are welcome. See CONTRIBUTING.md for setup, conventions, and the pull request checklist.

Roadmap

Phase 1 (v0.1.x) and Phase 2 (v0.2.0 – v0.5.0) are shipped. Scope is in specs/product/mvp-scope.md. Phase 3+ (tax, retainer auto-draft, client portal, approval email, public API, native mobile) is out of scope.

Release history: CHANGELOG.md.

CI

GitHub Actions on every push and pull request:

Workflow Purpose
CI Lint, typecheck, unit tests, boundaries, Prisma validate, production build, E2E smoke tests
CodeQL Security analysis (weekly + on PR)
Release Publish Docker image to GHCR on release
Screenshots Regenerate README images on release

Dependabot opens weekly PRs for npm, GitHub Actions, and Docker base images.

Local E2E: npm run test:e2e (requires running server + seeded DB on port 3005).

Documentation

Security

See SECURITY.md for reporting vulnerabilities.

License

Apache License 2.0

Documentation map

docs/README.md lists the repository layout. Community policy files are the root CONTRIBUTING.md, CODE_OF_CONDUCT.md, and SECURITY.md.

Releases

Packages

Used by

Contributors

Languages