Case Study

CPMS — Comprehensive Procurement Management System

Full stack engineer case study: enterprise website development on Next.js 15 with Prisma, Auth.js RBAC, backend developer workflows, and UI UX developer procurement surfaces.

CPMS — Comprehensive Procurement Management System
By Published Updated

Comprehensive Procurement Management System (CPMS)

1. The 60-Word "AI Citation" Summary

CPMS is a French-first Next.js 15 (App Router) procurement platformwebsite 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

CategoryTechnologies
LanguagesTypeScript (strict), SQL (via Prisma)
FrameworkNext.js 15.1+, React 19, App Router (src/app)
AuthAuth.js (next-auth v5 beta), Credentials provider, bcrypt, JWT sessions with Prisma adapter
DataPostgreSQL (Railway), Prisma ORM 6.9
i18nnext-intl 3.x, messages/fr.json + messages/en.json, default locale fr
UITailwind CSS 4, Radix UI (shadcn-style), Lucide, next-themes, Recharts
Forms & validationreact-hook-form, Zod, @hookform/resolvers
Client stateZustand (persisted procurement mode + display currency, cookie sync)
DocumentsAWS 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]/… with next-intl middleware for locale prefixing (fr default, en secondary). 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 permissions aligned with database Role.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)

AreaImplementation
AuthenticationCredentials provider; bcrypt password verification; JWT sessions with 30-day max age; 24h inactivity invalidation in JWT callback.
AuthorizationServer-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 integrityEmpty JWT returned after inactivity timeout; session callback returns null when token cleared—forces re-login.
Object storagePresigned URLs for time-limited downloads; server-side uploads with typed keys; avoids long-lived public URLs for sensitive procurement artifacts.
Performance postureServer 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_bible constraints—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

Project links

Want a broader project snapshot?

Browse the portfolio for shorter project cards with stack, links, and demo references.

View portfolio

More case studies

Hayaat Intake AI — Case Study

AI software engineer case study: full stack developer website development for Medicaid voice/SMS intake—Next.js, Vapi, Twilio, OpenAI agents, and Supabase CRM.

View case study

COOARD Salon & Barber Booking Platform Case Study

Full stack developer case study: Next.js website development plus Capacitor mobile application development for a multi-tenant salon platform with Stripe, Supabase RLS, and AI catalog automation.

View case study

Twinsting Case Study: Role-Aware Marketplace Architecture

Full stack engineer case study: Next.js 15 website development and Express API for a multi-role marketplace—web dev, app developers delivery, Stripe Connect, and realtime chat.

View case study

Tuttle Case Study: AI Localization Platform Architecture

AI software engineer case study: full stack developer website development for AI video dubbing SaaS—React, Next.js, HeyGen, DeepL, Stripe, and metered AI localization.

View case study