13 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):
mssqlpackage (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
Reference Documentation\
All legacy system documentation is in the docs/ directory:
\
vorteqcoil-portal-documentation.md— Complete interface documentation of the legacy portal: navigation, sections, column layouts, business logic. Reference when building UI pages.\vorteqcoil-visual-documentation.html— Visual reference guide with color coding system and UI patterns. Reference for design decisions.\VorteqDiscovery_20260206_205453.txt— Server infrastructure discovery: IIS config, scheduled tasks, services.\VorteqAppDiscovery_20260206_210519.txt— Application code discovery: Laravel routes, controllers, models, views, Blade templates. Reference when implementing features.\VorteqEpicorDB_20260210_211850.txt— Epicor database schema: tables, stored procedures, views. Reference when writing Epicor queries.\EpicorDatabase.php— Legacy PHP database class with all Epicor query implementations. Primary reference for porting queries to TypeScript.\_FILE_LISTING.txt— Complete file listing of the legacy Laravel application.\_MODEL_MAP.txt— Legacy Eloquent model-to-table mappings. Reference for Prisma schema design.\_SQL_QUERIES_FOUND.txt— All SQL queries extracted from the legacy codebase. Reference for service layer implementation.
' CLAUDE.md
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
anytypes - Use
typeoverinterfaceunless extending - Prefer
constoverlet, nevervar - 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 EpicorCustIDDBNAME: 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
mainbranch: production-ready code → deploys toquest.vorteq.wulf.clouddevelopmentbranch: integration branch → deploys todev.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 testbefore 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)