Offer Letter and Renewal System
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.mddocs/specs/ERS-v0.2-build-pack.mddocs/specs/ERS-v0.3-runnable-pack.mddocs/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
Environment
Copy .env.example to .env and set values:
DATABASE_URLAUTH_JWT_SECRETAUTH_COOKIE_NAMEUPLOAD_BASE_DIRUPLOAD_MAX_MBNEXT_PUBLIC_APP_VERSIONRESEND_API_KEYEMAIL_FROMOPENAI_API_KEY(optional, for smarter offer-letter extraction)OPENAI_MODEL(optional, defaultgpt-4.1-mini)INTERNAL_API_SECRET
Install
npm install
npm run prisma:generate
Database Setup
npm run prisma:migrate
npm run db:seed
Run
npm run dev
Open http://localhost:3000.
Demo Users (from seed)
- CEO:
ceo@ers.local - HR:
hr@ers.local - Manager:
manager@ers.local - Password:
Password123!
Core URLs
/login/hr/renewals/manager/requests/ceo/renewals/ceo/signature/admin/settings/admin/audit/admin/users/notifications
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/newand clickExtract & Prefill. - If
OPENAI_API_KEYis configured, extraction uses OpenAI structured parsing with heuristic fallback. - Uploading
OFFER_LETTERin a case detail also auto-maps extracted fields toRenewalCase.
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_atset 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-pdfwith 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.