Skip to content
This repository was archived by the owner on Jul 21, 2026. It is now read-only.

Latest commit

 

History

143 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ReqFlow

Purchase requisition workflow for K-12 school districts.

Self-hosted. Open source. Built for districts too small for Skyward or Tyler ERP but tired of paper forms and email chains.

Next.js React TypeScript PostgreSQL License


What is ReqFlow?

ReqFlow turns purchase requests into a tracked, auditable, multi-stage approval workflow. Staff submit a requisition with line items, budget codes, and a vendor. The request routes through site approval, business manager review, optional board approval, and lands at the procurement step where a PO can be generated and items received against. Every step is signed, time-stamped, and tied to an audit log.

It's designed for the typical small-to-mid district setup: a few principals approving site-level requests, a business manager doing district approval and PO generation, and a regional finance system for actual general ledger accounting.

Highlights

  • Multi-stage approvals — Configurable site, district, and board approval stages with department-routed approvers and digital signatures
  • Budget encumbrance ledger — Every transaction (allocation, encumbrance, release, expenditure, adjustment, transfer) recorded in a single canonical ledger with decimal-precision math
  • Amazon PunchOut — Full cXML PunchOut integration. Browse Amazon Business catalogs from inside the app and bring carts back as line items
  • ESIGN-compliant signatures — Drawn signatures captured with IP, user agent, content hash, consent text, and signer email per approval stage
  • Scheduled reminders — Approvers get itemized digests on an escalating cadence (day 3, day 5, daily after day 7) so nothing sits too long
  • Receiving and shipment tracking — Per-line receiving status with UPS/FedEx/USPS tracking integration
  • Real-time collaboration — Comments, watchers, typing indicators, and shared requisition views via SSE
  • PDF and Excel export — Branded POs and audit reports
  • Granular permissions — Permission groups with per-action toggles on top of USER / BUSINESS_MANAGER / ADMIN roles
  • Dark mode across the entire app

Tech Stack

Layer Tools
Framework Next.js 16 (App Router), React 19, TypeScript
Database PostgreSQL 16, Prisma 7.8
Auth NextAuth.js v5 (Google OAuth)
UI Tailwind CSS v4, Recharts, Lucide icons
Financial math decimal.js
Email Nodemailer 8 (SMTP)
Testing Vitest, Testing Library, vitest-mock-extended
Deploy Docker, supercronic (for in-container cron)
CI GitHub Actions (lint, test, build)

Quick Start (Local Development)

Prerequisites: Node.js 20+, PostgreSQL 14+, Google OAuth credentials.

# 1. Clone and install
git clone https://github.com/tgunn-dev/ReqFlow.git
cd ReqFlow
npm install

# 2. Configure environment
cp .env.example .env  # if available, otherwise create manually

Create a .env file:

DATABASE_URL=postgresql://user:password@localhost:5432/reqflow
AUTH_GOOGLE_ID=your-google-client-id
AUTH_GOOGLE_SECRET=your-google-client-secret
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET=                # generate: openssl rand -base64 32
# 3. Set up the database
npx prisma migrate deploy
npx prisma generate

# 4. Run the dev server
npm run dev

Open http://localhost:3000. The first user to sign in is automatically promoted to Admin — sign in with your Google account before anyone else does.

Docker Deployment (Production)

ReqFlow ships with a multi-stage Dockerfile and two compose files. The production setup includes:

  • App container running Next.js standalone
  • PostgreSQL with persistent volume
  • Internal network isolation (DB not exposed)
  • Health checks, resource limits, memory caps
  • Persistent volume for uploaded attachments
  • In-container cron daemon (supercronic) for scheduled reminders

1. Generate secrets

openssl rand -base64 32   # NEXTAUTH_SECRET
openssl rand -base64 32   # CRON_SECRET (enables scheduled reminders)

2. Create .env.production

POSTGRES_PASSWORD=strong-password-here
NEXTAUTH_URL=https://requisitions.yourdomain.com
NEXTAUTH_SECRET=<from openssl>
AUTH_GOOGLE_ID=<google-client-id>
AUTH_GOOGLE_SECRET=<google-client-secret>
CRON_SECRET=<from openssl>

# Optional
REMINDER_SCHEDULE=0 8 * * 1-5    # Default: weekdays at 8am
TZ=America/Chicago               # Default
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_USER=
SMTP_PASSWORD=
SMTP_FROM=noreply@yourdomain.com

# Optional: Amazon PunchOut
AMAZON_PUNCHOUT_URL=
AMAZON_ORDER_URL=
AMAZON_FROM_IDENTITY=
AMAZON_TO_IDENTITY=
AMAZON_SHARED_SECRET=

3. Deploy

docker compose -f docker-compose.prod.yml --env-file .env.production up -d --build

The container runs migrations on startup, then launches both the Next.js server and the supercronic cron daemon.

Project Structure

app/
  actions/        Server Actions organized by domain (requisition, finance, admin, etc.)
  api/            API routes (auth, PunchOut webhooks, cron, exports, v1 REST API)
  admin/          Admin pages (role-protected by middleware and per-page checks)
  dashboard/      User-facing pages
  components/     React components (server and client)

data/             Read-side data fetching (Prisma queries)
services/         Business logic (budget operations, order processing)
lib/              Core utilities (Prisma client, audit, financial math, email)
prisma/           Schema, migrations
tests/            Unit and integration tests (mocked Prisma)

Requisition Lifecycle

DRAFT ──► SUBMITTED ──► APPROVED_SITE ──► APPROVED_DISTRICT ──► ORDERED ──► RECEIVED ──► CLOSED
                                       │                    ▲
                                       └─► READY_FOR_BOARD ─┘
                                       
              ◄── Return for Revision ──
              ✗   REJECTED

The flow is configurable: any of site, district, or board approval can be disabled per district policy. Self-approval can be allowed or blocked per district. The board approval threshold is a dollar amount.

Configuration

Settings are managed through the admin UI at /admin/settings:

  • Approval workflow — Which stages are required, the board approval threshold, self-approval policy, same-approver-multiple-levels policy
  • Requisition rules — Required fields, line item limits, justification text requirements, over-budget blocking
  • Email/SMTP — Server, port, credentials, from address (or use env vars)
  • Notifications — Per-event email and in-app toggles
  • Shipping carriers — UPS, FedEx, USPS API credentials for tracking
  • Shipping budget code — Which code overhead (shipping, tax) gets encumbered against

Departments (/admin/org/departments) hold their own approver assignments — each department has a default site approver and business manager. Changing them notifies the new approver about any inherited pending requisitions.

Scheduled Reminders

A cron daemon inside the container hits /api/cron/reminders on the configured schedule. Default is weekdays at 8am Central. The endpoint sends three streams of notifications:

Audience Trigger
Approvers Day 3, day 5, then daily once items pass 7 days
Business managers When fully-approved requisitions are waiting for PO creation
Requesters Day 7, then weekly once items pass 14 days

Test manually:

curl -H "x-cron-secret: $CRON_SECRET" https://yourdomain.com/api/cron/reminders

Security and Compliance

  • ESIGN Act / UETA compliance: Each approval signature captures the signer's name, email, IP address, user agent, timestamp, consent text, and a SHA-256 hash of the requisition's material content at signing. The PDF export includes an audit certificate with these fields.
  • Audit log: Every mutation runs through lib/audit.ts and is recorded with actor, action, entity type/ID, change diff, IP, and user agent.
  • Soft delete: Requisitions are soft-deleted, not destroyed. Encumbered funds are released on delete and re-encumbered (via DRAFT reset) on restore.
  • Permission system: Database-backed permissions with grouped templates. Permission cache invalidates on change.
  • Rate limiting: Sensitive endpoints (file upload, etc.) are rate-limited per IP.

Scripts

Command Description
npm run dev Start dev server (turbopack)
npm run build Production build
npm run start Start production server
npm run lint Run ESLint
npm test Run tests in watch mode
npm run test:run Run tests once
npm run test:coverage Run tests with V8 coverage
npm run db:seed Seed database with sample data
npm run db:reset Drop and recreate the database

Testing

Tests use Vitest with a deep-mocked Prisma client — no database required.

npm run test:run        # one shot
npm run test:coverage   # with coverage
npm run test:ui         # interactive UI

Current coverage: 331 tests across 10 files, covering financial calculations, approval workflows, budget operations, and PunchOut order confirmation parsing.

Database

Schema changes:

npx prisma migrate dev --name describe_the_change
npx prisma generate

Production deployments:

npx prisma migrate deploy

The Docker entrypoint runs migrate deploy automatically on container start.

License

MIT — See LICENSE for details.


Built for small-to-mid K-12 districts. Issues and PRs welcome.

About

Self-hosted purchase requisition and approval workflow for K-12 school districts. Built with Next.js, PostgreSQL, and Docker.

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages