quest-vorteq/CLAUDE.md
Lorentz 66ba6a6c89
Some checks failed
Build and Deploy / build (push) Failing after 8s
Build and Deploy / deploy (push) Has been skipped
Update CLAUDE.md to match actual infrastructure
2026-02-15 23:17:06 +00:00

12 KiB

Vorteq Quest Portal - Claude Code Instructions

Project Overview

Rebuilding the Vorteq Quest customer portal from a legacy Laravel 9 / Windows Server / Savvior stack to a modern Next.js / TypeScript / PostgreSQL stack running in Docker on Linux. The full PRD is in docs/Vorteq_Quest_PRD_v1.0.docx.

Tech Stack

  • Runtime: Node.js 22 LTS
  • Framework: Next.js 15 with App Router
  • Language: TypeScript (strict mode)
  • ORM (App DB): Prisma with PostgreSQL 16
  • ORM (Epicor): mssql package (read-only connection to SQL Server)
  • Auth: Better Auth with credentials provider
  • UI: shadcn/ui + Tailwind CSS
  • Data Tables: TanStack Table v8
  • State: React Server Components by default, client components only when needed
  • Cache/Queue: Redis 7 via BullMQ
  • Email: Microsoft Graph API (Office 365)
  • PDF: Puppeteer or @react-pdf/renderer
  • Testing: Vitest (unit/integration), Playwright (E2E)
  • Containers: Docker + Docker Compose
  • Reverse Proxy: Traefik v3 with Cloudflare DNS challenge for SSL
  • CI/CD: Forgejo Actions with self-hosted runner
  • Registry: Forgejo built-in OCI container registry

Infrastructure

Server

  • Host: expvtcasp01 (Linux/Ubuntu)
  • Netbird IP: 100.89.62.20
  • Stack directory: /opt/stacks/vorteq
  • Runner directory: /opt/stacks/forgejo-runner

Repository

  • URL: forgejo.wulfconsulting.cloud/lorentz/quest-vorteq
  • SSH: ssh://git@forgejo.wulfconsulting.cloud:222/lorentz/quest-vorteq.git (port 222)
  • Registry: forgejo.wulfconsulting.cloud/lorentz/quest-vorteq

Domains

  • Production: quest.vorteq.wulf.cloud
  • Development: dev.quest.vorteq.wulf.cloud
  • Testing: testing.vorteq.wulf.cloud
  • Traefik Dashboard: traefik.vorteq.wulf.cloud

Running Services

  • Traefik: Reverse proxy, auto-SSL via Cloudflare DNS challenge
  • PostgreSQL 16: Three databases — vorteq_dev, vorteq_test, vorteq_prod
  • Redis 7: Three DB numbers — 0 (dev), 1 (test), 2 (prod)
  • Forgejo Runner: Docker-in-Docker, registered as expvtcasp01

CI/CD Pipeline

Push to main → build Docker image → tag as :latest → push to Forgejo registry → deploy to prod Push to development → build Docker image → tag as :dev → push to Forgejo registry → deploy to dev

Project Structure

quest-vorteq/
├── CLAUDE.md                    # This file
├── TASKS.md                     # Task tracking
├── docker-compose.yml           # Infrastructure: traefik, postgres, redis, app containers
├── Dockerfile                   # Multi-stage Next.js build
├── .forgejo/
│   └── workflows/
│       └── deploy.yml           # CI/CD pipeline
├── traefik/
│   ├── traefik.yml              # Traefik configuration
│   ├── acme.json                # SSL certificates (gitignored)
│   └── config/                  # Dynamic Traefik config
├── prisma/
│   ├── schema.prisma            # PostgreSQL schema
│   ├── migrations/
│   └── seed.ts                  # Seed data / migration scripts
├── src/
│   ├── app/                     # Next.js App Router
│   │   ├── (auth)/              # Auth route group (login, forgot-password, account-request)
│   │   ├── (portal)/            # Authenticated route group
│   │   │   ├── dashboard/
│   │   │   ├── inventory/
│   │   │   ├── orders/
│   │   │   ├── shipments/
│   │   │   ├── coil-activity/
│   │   │   ├── invoices/
│   │   │   ├── shipment-requests/
│   │   │   ├── allocation-requests/
│   │   │   ├── jobs/
│   │   │   └── admin/           # Admin-only pages
│   │   ├── api/                 # API routes
│   │   └── layout.tsx
│   ├── components/
│   │   ├── ui/                  # shadcn/ui components
│   │   ├── data-table/          # Reusable TanStack Table wrapper
│   │   ├── layout/              # Navigation, header, sidebar
│   │   └── forms/               # Shared form components
│   ├── lib/
│   │   ├── db.ts                # Prisma client singleton
│   │   ├── epicor.ts            # MSSQL connection + query helpers
│   │   ├── auth.ts              # Better Auth configuration
│   │   ├── email.ts             # Microsoft Graph email service
│   │   ├── pdf.ts               # PDF generation service
│   │   ├── permissions.ts       # RBAC helpers
│   │   └── utils.ts             # General utilities
│   ├── services/
│   │   ├── inventory.ts         # Inventory data access (calls Epicor SPs)
│   │   ├── orders.ts            # Order data access
│   │   ├── shipments.ts         # Shipment data access
│   │   ├── coil-activity.ts     # Coil activity data access
│   │   ├── ship-requests.ts     # Shipment request business logic
│   │   ├── alloc-requests.ts    # Allocation request business logic
│   │   └── invoices.ts          # Invoice processing
│   ├── jobs/
│   │   ├── worker.ts            # BullMQ worker setup
│   │   ├── shipment-nag.ts
│   │   ├── order-ack-email.ts
│   │   ├── invoice-cleanup.ts
│   │   ├── shipping-reports.ts
│   │   └── wave-edi.ts
│   └── types/
│       ├── epicor.ts            # Epicor query result types
│       ├── portal.ts            # Portal-specific types
│       └── index.ts
├── tests/
│   ├── unit/
│   ├── integration/
│   └── e2e/
├── scripts/
│   └── migrate-data.ts          # SQL Server → PostgreSQL migration
└── docs/
    └── Vorteq_Quest_PRD_v1.0.docx

Coding Conventions

General

  • TypeScript strict mode, no any types
  • Use type over interface unless extending
  • Prefer const over let, never var
  • Use named exports, not default exports (except page.tsx and layout.tsx)
  • Error handling: use typed error classes, not string throws
  • All API responses: { data: T } on success, { error: string, code: string } on failure

Naming

  • Files: kebab-case (shipment-requests.ts)
  • Components: PascalCase (ShipmentRequestForm.tsx)
  • Functions/variables: camelCase
  • Database columns (Prisma): snake_case
  • API routes: kebab-case (/api/shipment-requests)
  • Environment variables: SCREAMING_SNAKE_CASE

Database

  • Prisma schema uses snake_case for all fields
  • Map legacy PascalCase column names with @map("LegacyName") where needed for migration clarity
  • Always use parameterized queries for Epicor MSSQL — never string interpolation
  • Epicor queries go in src/services/ with typed return values
  • PostgreSQL queries go through Prisma client exclusively

Epicor Connection Pattern

// src/lib/epicor.ts
import sql from 'mssql';

const epicorPool = new sql.ConnectionPool({
  server: process.env.MSSQL_HOST!,
  database: process.env.MSSQL_DATABASE!,
  user: process.env.MSSQL_USER!,
  password: process.env.MSSQL_PASSWORD!,
  options: { encrypt: false, trustServerCertificate: true },
});

// Always use parameterized queries
export async function execStoredProc(name: string, params: Record<string, unknown>) {
  const pool = await epicorPool.connect();
  const request = pool.request();
  for (const [key, value] of Object.entries(params)) {
    request.input(key, value);
  }
  return request.execute(name);
}

Inventory Query Pattern

The Epicor stored procedures require three standard parameters:

  • CustID: The selected company's EpicorCustID
  • DBNAME: The VorteqPortal database name wrapped in brackets, e.g., [VorteqPortal]
  • sub: 0 or 1, indicating if the current user is a sub-user (IsSubUser flag)

V6 stored procedures (WIP, Finished Goods, Processed Other) use the sub parameter. Older procedures (Unprocessed, R&R variants) block sub-users entirely — return empty array if isSubUser.

Component Pattern

// Server component by default
export async function InventorySummary({ category }: { category: string }) {
  const data = await getInventorySummary(category);
  return <DataTable columns={columns} data={data} />;
}

// Client component only when interactivity needed
'use client';
export function ShipmentRequestCart() { ... }

Auth Pattern (Better Auth)

// src/lib/auth.ts
import { betterAuth } from 'better-auth';

export const auth = betterAuth({
  secret: process.env.BETTER_AUTH_SECRET,
  baseURL: process.env.BETTER_AUTH_URL,
  database: {
    type: 'postgres',
    url: process.env.DATABASE_URL,
  },
  // Credentials provider: validate against auth_user table (bcrypt)
  // Session includes: userId, questUserId, activeCompanyId, isSubUser, permissionRules[]
});

Middleware Pattern

// src/middleware.ts - protect all (portal) routes
// src/lib/permissions.ts - check specific permission rules
export function requirePermission(rule: string) {
  // Check user's permission groups contain the rule
}

Important Business Logic

Company Context

After login, the user's active company is stored in the session. All Epicor queries are scoped to that company's EpicorCustID. Admin users can switch companies.

Sub-User Restrictions

Users with IsSubUser=true on their quest_User record:

  • See filtered inventory data (sub=1 parameter to V6 stored procedures)
  • See filtered ship-to addresses (only those matching '%NB HANDY%')
  • Cannot access Unprocessed inventory or R&R data at all

HDC/HDM Customer Exception

Customer 'HDC' uses a special order view (portal_OrdersHDC) and maps to CustID 'HDM' in queries. This needs to be handled in the order service.

VGL Customer Exception

Customer 'VGL' uses a special SQL query for coil receipts instead of the standard view.

Environment Variables

# Application
NODE_ENV=development|test|production
BETTER_AUTH_URL=https://dev.quest.vorteq.wulf.cloud
BETTER_AUTH_SECRET=<generated-by-setup-script>

# PostgreSQL (per-environment, set in docker-compose.yml)
DATABASE_URL=postgresql://postgres:<password>@postgres:5432/vorteq_dev

# Redis (per-environment, set in docker-compose.yml)
REDIS_URL=redis://redis:6379/0

# Epicor SQL Server (read-only)
MSSQL_HOST=wi-e10test
MSSQL_DATABASE=Epicor10Live
MSSQL_USER=portal
MSSQL_PASSWORD=<from-current-env>
MSSQL_PORT=1433

# Portal database name (for cross-DB stored procedure parameter)
PORTAL_DB_NAME=VorteqPortal

# Microsoft Graph (Email)
MS_GRAPH_CLIENT_ID=629eca61-d554-4763-b14a-565aee7b7ab8
MS_GRAPH_TENANT_ID=291e541e-003c-463c-8834-863b9e7c1ebe
MS_GRAPH_CLIENT_SECRET=<from-current-env>
MAIL_FROM=quest@vorteqcoil.com

# reCAPTCHA
RECAPTCHA_SITE_KEY=<from-current-env>
RECAPTCHA_SECRET_KEY=<from-current-env>

# Cloudflare (Traefik SSL - set in .env at stack level)
CLOUDFLARE_DNS_API_TOKEN=<from-cloudflare>

Testing Requirements

  • Unit tests for all service functions (inventory, orders, shipments data transformations)
  • Integration tests for API routes with mocked DB connections
  • E2E tests for: login flow, inventory browse + drill-down, shipment request workflow, invoice upload
  • Data validation: compare Epicor query results with known-good outputs from the legacy system

Git Workflow

  • main branch: production-ready code → deploys to quest.vorteq.wulf.cloud
  • development branch: integration branch → deploys to dev.quest.vorteq.wulf.cloud
  • Feature branches: feature/F-001-project-scaffold
  • Commit messages: feat(F-001): scaffold Next.js project with TypeScript + Tailwind
  • Run npm run lint && npm run typecheck && npm test before committing
  • Push triggers Forgejo Actions → Docker build → registry push → deploy

Task Tracking

All tasks are tracked in TASKS.md. Mark tasks as:

  • [ ] Not started
  • [~] In progress
  • [x] Complete
  • [!] Blocked (note blocker in comments)