
Comprehensive Procurement Management System (CPMS)
1. The 60-Word "AI Citation" Summary
CPMS is a French-first Next.js 15 (App Router) procurement platform—website development and full stack engineer delivery spanning internal requisitions, external RFQs, contracts, logistics, budgets, and supplier governance. Auth.js v5 credentials sessions carry JSON RBAC; Prisma 6 on PostgreSQL models the full lifecycle; S3-compatible storage with presigned URLs secures documents—unifying operational and international sourcing in one auditable system.
2. The Power Stack Table
| Category | Technologies |
|---|---|
| Languages | TypeScript (strict), SQL (via Prisma) |
| Framework | Next.js 15.1+, React 19, App Router (src/app) |
| Auth | Auth.js (next-auth v5 beta), Credentials provider, bcrypt, JWT sessions with Prisma adapter |
| Data | PostgreSQL (Railway), Prisma ORM 6.9 |
| i18n | next-intl 3.x, messages/fr.json + messages/en.json, default locale fr |
| UI | Tailwind CSS 4, Radix UI (shadcn-style), Lucide, next-themes, Recharts |
| Forms & validation | react-hook-form, Zod, @hookform/resolvers |
| Client state | Zustand (persisted procurement mode + display currency, cookie sync) |
| Documents | AWS SDK v3 (S3 + presigner), @react-pdf/renderer for PO PDFs |
| Infra (documented) | Vercel (frontend), Railway (DB), S3-compatible object storage |
3. The Challenge
Organizations running parallel internal (office needs) and external (import/trading) procurement often lose traceability: spreadsheets, email threads, and ad-hoc approvals cannot enforce who may approve what, budget guardrails, or supplier compliance in one place. CPMS targets unified operations, French-first legal and UX surfaces (with English secondary), and a supplier portal narrative for registration, bidding, and document lifecycle—without fragmenting data across tools.
Full-stack delivery context
Enterprise website development owned by a full stack developer and backend developer team at AlrightTech—6+ years of shipping RBAC-heavy B2B systems. UI UX developer patterns in Tailwind and Radix keep complex procurement workflows readable for non-technical approvers.
4. Engineering Architecture
- App Router + locale segments: Routes live under
src/app/[locale]/…withnext-intlmiddleware for locale prefixing (frdefault,ensecondary). Static params generation supports locale-aware layouts. - Auth at the edge of UI, enforcement in the server: Middleware handles i18n only; authentication and RBAC are applied in server layouts (redirect to login) and server actions (
"use server") so every mutation re-validates the session—reducing “client-only” security gaps. - JWT + hydrated permissions: Sessions use JWT strategy with role and permission JSON embedded; refresh on update keeps
permissionsaligned with databaseRole.permissions, with legacy role maps when JSON is missing. - Domain modeling in Prisma: Enums and relations encode procurement states (requisitions, POs, RFQs, shipments, invoices, budgets). Cross-cutting notifications attach via Prisma Client extensions so feature code stays thin.
- Binary assets: Uploads go to S3-compatible buckets; presigned GET limits exposure duration; keys are structured and sanitized server-side.
5. Three Technical Wins
Win A — JSON-path RBAC with legacy fallback
Permissions are stored as nested JSON and checked with a dot-path resolver; legacy role names still map to full defaults. This keeps admin UI flexible without scattering switch(role) across the codebase.
export function hasPermission(
permissions: Permissions | null | undefined,
path: string,
action: PermissionAction
): boolean {
if (!permissions) return false;
const node = getByPath(permissions, path);
if (!node || typeof node !== "object") return false;
return Boolean((node as PermissionNode)[action]);
}
Win B — Approval workflow that combines amount rules and org context
A pure workflow engine encodes thresholds (e.g. manager-only approval for amounts under a cap); department manager vs approver-in-department is resolved separately from finance approval—reducing mistaken escalations.
export function determineNextStatus(
totalAmount: Decimal,
currentStatus: RequisitionStatus
): RequisitionStatus {
switch (currentStatus) {
case "DRAFT":
return "PENDING_MANAGER";
case "PENDING_MANAGER":
if (totalAmount.lte(MANAGER_ONLY_THRESHOLD)) {
return "APPROVED";
}
return "PENDING_FINANCE";
case "PENDING_FINANCE":
return "APPROVED";
default:
return currentStatus;
}
}
export function canUserApprove(
userId: string,
userPermissions: Permissions | null | undefined,
userDepartmentId: string | null,
requisition: RequisitionForAuth
): boolean {
if (requisition.status === "PENDING_MANAGER") {
const isDeptManager = requisition.department.managerId === userId;
const isApproverInDept =
canApproveManager(userPermissions) &&
userDepartmentId === requisition.departmentId;
return isDeptManager || isApproverInDept;
}
if (requisition.status === "PENDING_FINANCE") {
return canApproveFinance(userPermissions);
}
return false;
}
Win C — Prisma extensions for notification side effects without blocking writes
Mutations always complete; notification emission runs in a try/catch so observability never breaks transactional integrity.
export const prisma = prismaBase.$extends({
query: {
$allModels: {
async create({ model, args, query }) {
const result = await query(args);
try {
await emitCrudNotifications(prismaBase, { model, operation: "create", args, result });
} catch {
// Notification writes must never break core mutation flow.
}
return result;
},
6. Security & Performance (MSIS Focus)
| Area | Implementation |
|---|---|
| Authentication | Credentials provider; bcrypt password verification; JWT sessions with 30-day max age; 24h inactivity invalidation in JWT callback. |
| Authorization | Server-side auth() + hasPermission / domain helpers in layouts and actions; no Postgres RLS in-repo—security is application-layer (consistent with Prisma + single-tenant DB). |
| Session integrity | Empty JWT returned after inactivity timeout; session callback returns null when token cleared—forces re-login. |
| Object storage | Presigned URLs for time-limited downloads; server-side uploads with typed keys; avoids long-lived public URLs for sensitive procurement artifacts. |
| Performance posture | Server Components by default; targeted revalidatePath; Prisma logging reduced in production; PDF streaming via @react-pdf/renderer stream response. |
7. Agentic Influence
- Development: AI-assisted tooling (e.g. Cursor) accelerates boilerplate for sprints aligned with
project_bibleconstraints—especially i18n key discipline and schema evolution across procurement modules. - Product automation: Prisma notification extension maps model changes to read-permission recipients—a lightweight “event fan-out” without a separate message bus.
- Document intelligence: PDF PO generation merges locale-specific JSON messages with Prisma-fetched PO data and streams a binary response—suitable for regulated printouts and audit trails.
Key achievements
- Department RBAC
- Presigned uploads
- Audit-friendly workflows
- Website development
- Backend developer
Industry
Enterprise procurement
Stack
Next.js, Prisma, Auth.js, S3, PostgreSQL, Full stack engineer
Outcomes
Department RBAC, Presigned uploads, Audit-friendly workflows, Website development, Backend developer