Handover workspace

ERS, Todo, OfferReview, and Docu in one view

Imported from live server docs, code structure, and deployment notes.

Jul 30, 2026, 9:57 AM

ERS

Offer Letter and Renewal System

**Current implementation:** v0.4.0 **Last updated:** 2026-07-14

README.md

Updated Jul 14, 2026, 7:17 AM

Offer Letter and Renewal System

Current implementation: v0.4.0 Last updated: 2026-07-14

Offer Letter and Renewal System is an internal offer-letter approval and renewal workflow system built with Next.js App Router + Prisma + Postgres.

Specs

  • docs/specs/ERS-v0.1-spec.md
  • docs/specs/ERS-v0.2-build-pack.md
  • docs/specs/ERS-v0.3-runnable-pack.md
  • docs/USER-MANUAL.md

Stack

  • Next.js (App Router, TypeScript)
  • Prisma + PostgreSQL
  • Local filesystem uploads (UPLOAD_BASE_DIR)
  • JWT cookie auth (email + password)
  • Resend-backed durable email outbox
  • PDF signature stamping with pdf-lib
  • OpenAI-assisted offer extraction, contract variation review, and signature placement suggestions
  • Server-rendered senior-management compensation analytics with table and chart views

Environment

Set values in .env (and in /etc/ers-prod.env if you run via ers-prod.service):

  • DATABASE_URL
  • AUTH_JWT_SECRET
  • AUTH_COOKIE_NAME
  • UPLOAD_BASE_DIR
  • UPLOAD_MAX_MB
  • RESEND_API_KEY
  • EMAIL_FROM
  • NEXT_PUBLIC_APP_URL (preferred public app base URL, e.g. https://review.r32a.com)
  • NEXTAUTH_URL or APP_URL (supported public URL fallbacks)
  • OPENAI_API_KEY (optional, for offer-letter extraction and AI contract review)
  • OPENAI_MODEL (optional, default gpt-4.1-mini)
  • INTERNAL_API_SECRET

UPLOAD_BASE_DIR must point to a persistent directory such as /var/app/ers_uploads. Do not use /tmp or any other temporary directory in production, or uploaded offer letters and CEO signatures can disappear after service restarts.

Install

npm install
npm run prisma:generate

Database Setup

Apply migrations in production without running the demo seed:

npx prisma migrate deploy

For an empty local development database only:

npm run prisma:migrate
npm run db:seed

npm run db:seed is destructive: it deletes existing application data and creates demo @ers.local users and sample cases. Never run it against production.

Run

npm run dev

Open http://localhost:3000.

Core URLs

  • /login
  • /dashboard (CEO-only management analytics landing page)
  • /hr/renewals
  • /manager/requests
  • /ceo/renewals
  • /ceo/signature
  • /admin/settings
  • /admin/system-health
  • /admin/audit
  • /admin/users
  • /help
  • /notifications
  • /api/version (public build identity; no secrets)

Navigation and case review

  • Desktop uses grouped role navigation; mobile uses an accessible navigation drawer.
  • Every authenticated page has a breadcrumb and a route-specific page header.
  • Case lists can be filtered by employee, status, grade, and department. Opening and returning from a case preserves the filtered list URL.
  • Long case pages provide sticky section navigation for overview, evidence, contract AI, decisions, manager input, and documents.
  • The user menu contains role-specific Help, About ERS, and Sign out.

Release versioning

ERS uses Semantic Versioning. package.json is the single editable version source. Before local development and production builds, scripts/generate-build-info.mjs writes lib/generated/build-info.ts with the version, Git SHA, build timestamp, environment, and dirty worktree state. The resulting identity appears on login, in the authenticated sidebar, in About ERS, on CEO System Health, and at /api/version.

For a release:

npm version <major|minor|patch> --no-git-tag-version
npm run build

Update CHANGELOG.md in the same change. Release versions cannot be edited from Admin Settings. Create the Git commit and tag only from a reviewed clean worktree.

Supported Case Types

  • Renewal: requires offer letter, performance snapshot, and manager justification before CEO approval.
  • New Hire: requires offer letter only; manager justification and performance snapshot are not required.

Offer Letter Autofill

  • HR can upload an offer-letter PDF on /hr/renewals/new and click Extract & Prefill.
  • If OPENAI_API_KEY is configured, extraction uses OpenAI structured parsing with heuristic fallback.
  • Extraction prepares an editable review; it does not create an employee until HR submits the completed profile.
  • Employee name, department, role title, current grade, applicable grade, contract dates, salary, currency, and the offer PDF are required. Placeholder values such as Unknown Department and UNASSIGNED are rejected.
  • Foreign contractors without an internal grade must use an explicit category such as FOREIGN CONTRACTOR - NO INTERNAL GRADE.
  • Interview and performance evaluation forms are rejected when uploaded as OFFER_LETTER.
  • Uploading OFFER_LETTER in a case detail also auto-maps extracted fields to RenewalCase.

Performance Snapshot

Renewal cases require a real performance snapshot before CEO approval. The snapshot contains:

  • review period (required)
  • overall score (optional)
  • HR notes (optional)
  • manager comments (optional)

New-hire cases use the internal __NEW_HIRE__ marker and do not require performance or manager inputs. A submitted manager justification does not make a renewal ready by itself; the latest offer letter and performance snapshot must also be present.

Before manager outreach or CEO review, ERS rechecks the employee profile, offer PDF, dates, positive salary values, three-letter currency code, and applicable grade.

CEO Decision Brief

Each CEO case review places a decision brief directly after the case overview. It shows:

  • proposed monthly and annualized salary, salary movement, and contract term
  • same-grade percentile, P25, median, P75, range, and confidence based on active peer records
  • narrower same-grade plus department and same-grade plus exact-role cohorts when available
  • salary differences from the grade median and performance position among scored grade peers
  • manager recommendation, grade movement, required evidence, and AI contract variation risk
  • an advisory Aligned, Needs Review, Caution, or Incomplete posture with explicit reasons

The reviewed employee is excluded from the peer population. Cohorts below five peers are marked insufficient and are not classified on the curve. The posture is decision support only and never approves or rejects a case automatically.

Senior Management Analytics

CEO users land on /dashboard; HR users land on /hr/renewals. The CEO-only dashboard provides:

  • Executive Overview, Grade Curve, Increases & Outliers, and Budget & Risk tabs
  • filters for dates, case type, department, normalized grade, manager, status, curve position, and increase
  • one effective compensation record per active employee as of the selected date
  • current, projected, committed, pending, annualized, 12-month, and finite contract-term impact
  • configurable large-increase and high-percentile alerts
  • table and SVG box-plot chart modes on the Grade Curve tab
  • clickable proposed-salary dots with employee details and renewal links
  • CEO-only, audited, spreadsheet-safe CSV export from /api/analytics/export

Curve bands use the target grade and midrank percentiles. P25-P75 is ON_CURVE; values outside that range are classified below or above. Peer groups under five are not classified, and groups of five to nine are marked limited. Equivalent grade labels such as 13, 13 Executive, and Grade 13 are normalized for grouping without merging separate G scales such as G5. The dashboard defaults to a combined portfolio view, and the Case Type filter can isolate Renewal or New Hire offers so each curve can be reviewed separately.

Salary coverage is shown as confirmed, inferred, missing, or conversion-failed. Future-effective approved compensation is excluded from the current curve. Open-ended 9999 contract dates are excluded from finite contract-term totals and remain included in the 12-month view.

AI Contract Review

HR and CEO can run AI contract review against the latest offer letter. The review stores:

  • suggested signature page and PDF coordinates with confidence and rationale
  • comparison against a previous signed contract baseline
  • variation risk, clause differences, missing clauses, warnings, and recommendations
  • model, prompt version, status, errors, and actor audit information

The PDF signer can load a suggestion into its normal placement queue. AI output is advisory; the CEO reviews placement and contract differences before signing.

Email Dispatch

Outbound emails are queued in EmailOutbox. Dispatch endpoint:

  • POST /api/internal/email/dispatch
  • Header: x-internal-secret: <INTERNAL_API_SECRET>

Recommended VPS cron (every minute):

* * * * * curl -X POST https://your-domain/api/internal/email/dispatch -H "x-internal-secret: your-secret"

Notes

  • HR/Manager cannot modify locked cases (locked_at set by CEO decision).
  • Renewal-case CEO approval requires latest offer letter, performance snapshot, and manager justification.
  • New-hire CEO approval requires latest offer letter only.
  • Signature stamping is enabled via /api/renewals/:id/sign-pdf with multi-placement payload.
  • Signing is allowed only when case status is APPROVED.
  • After signing, case status becomes SIGNED (closed/read-only) and HR users are notified via outbox email.
  • If the employee or candidate declines after CEO signature, CEO can mark the signed case DECLINED; ERS voids the linked approved compensation, removes it from default analytics, and keeps the signed document and audit trail.
  • If a latest document row exists but its stored file is missing, HR and CEO can re-upload the latest missing file from the case detail page to restore downloads or signing.
  • /ceo/signature shows the current signature preview and allows the CEO to re-upload the PNG if the stored file is missing.
  • HR and CEO can delete wrong supporting documents or an eligible pre-approval case. Approved, rejected, signed, and compensation-linked cases remain protected.
  • Confirmed salary data corrections must record before/after values in AuditLog. Corrections to a renewal's current salary do not alter its approved compensation record or signed document.
  • Voided compensation rows are retained for audit but excluded from salary curves and peer stats.

Verification

npm run test:analytics
npm run typecheck
npm run lint
npm run build
npx prisma migrate status

npm run lint currently reports only the existing no-img-element optimization warnings in the PDF signer and CEO signature preview.