# 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 + custom session enrichment - **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. ## 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 (node:22-alpine, standalone output) ├── .forgejo/ │ └── workflows/ │ └── deploy.yml # CI/CD pipeline ├── traefik/ │ ├── traefik.yml # Traefik configuration │ ├── acme.json # SSL certificates (gitignored) │ └── config/ │ └── dev-local.yml # Routes dev domain → host:3001 for local dev server ├── prisma/ │ ├── schema.prisma # PostgreSQL schema (auth + quest + ship/alloc/finance/paint/doc/wave/lts domains) │ ├── 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/ # Summary + 6 category pages + detail drill-down │ │ │ ├── orders/ │ │ │ └── select-company/ │ │ ├── api/ │ │ │ ├── auth/ # Better Auth catch-all + custom login + set-active-company │ │ │ ├── health/ │ │ │ └── account-request/ │ │ ├── layout.tsx │ │ ├── globals.css │ │ └── page.tsx │ ├── components/ │ │ ├── ui/ # shadcn/ui primitives (button, card, input, table, toast, etc.) │ │ ├── dashboard/ # summary-card, quick-nav-card, recent-orders-table, recent-shipments-table │ │ ├── inventory/ # inventory-summary-table, inventory-detail-table │ │ ├── orders/ # orders-table │ │ ├── layout/ # portal-sidebar, portal-header, breadcrumb │ │ └── company-selector.tsx │ ├── hooks/ │ │ └── use-toast.ts │ ├── lib/ │ │ ├── db.ts # Prisma client singleton │ │ ├── epicor.ts # MSSQL connection pool + query helpers + typed errors │ │ ├── auth.ts # Better Auth config (Prisma adapter, credentials provider) │ │ ├── auth-client.ts # Better Auth client-side helpers │ │ ├── session.ts # Quest-specific session data (cookie-based) │ │ ├── permissions.ts # RBAC: getQuestSession, requirePermission, hasPermission, isAdmin, isSubUser, getActiveCompany, getUserCompanies, PermissionRules constants │ │ └── utils.ts # General utilities (cn, etc.) │ ├── services/ │ │ ├── inventory.ts # Epicor SP calls for 6 inventory categories (summary + detail) │ │ ├── orders.ts # portal_Orders view queries, HDC/HDM exception handling │ │ └── dashboard.ts # Dashboard data aggregation │ ├── types/ │ │ ├── epicor.ts # Epicor query result types │ │ ├── portal.ts # Portal-specific types │ │ └── index.ts │ └── middleware.ts # Rate limiting, public route allowlist, auth check (currently disabled in dev) ├── tests/ │ ├── unit/ │ ├── integration/ │ └── e2e/ ├── scripts/ │ └── migrate-data.ts # SQL Server → PostgreSQL migration └── docs/ # Legacy system documentation (see Reference Documentation above) ``` ### Not Yet Created (Planned) ``` src/app/(portal)/shipments/ src/app/(portal)/coil-activity/ src/app/(portal)/invoices/ src/app/(portal)/shipment-requests/ src/app/(portal)/allocation-requests/ src/app/(portal)/jobs/ src/app/(portal)/admin/ src/services/shipments.ts # Shipment data access src/services/coil-activity.ts # Coil activity data access src/services/ship-requests.ts # Shipment request business logic src/services/alloc-requests.ts # Allocation request business logic src/services/invoices.ts # Invoice processing src/lib/email.ts # Microsoft Graph email service src/lib/pdf.ts # PDF generation service src/jobs/ # BullMQ workers (shipment-nag, order-ack-email, etc.) src/components/data-table/ # Reusable TanStack Table wrapper src/components/forms/ # Shared form components ``` ## Current Development State **Phase 1 (Foundation):** Complete — project scaffold, Docker infra, Prisma schema (all domains), Epicor connection, Better Auth, middleware, permissions, company selector. **Phase 2 (Core Features):** In progress - Dashboard: implemented (summary cards, quick nav, recent orders/shipments tables) - Inventory: implemented (all 6 category summaries + detail drill-down) - Orders: implemented (list page with search and CSV export, HDC exception) - Remaining: shipments, coil-activity, invoices, shipment-requests, allocation-requests, jobs, admin pages **Dev workflow:** Local Next.js dev server with hot reload via `PORT=3001 npm run dev`. Traefik routes `dev.quest.vorteq.wulf.cloud` to host:3001 via `traefik/config/dev-local.yml`. The `app-dev` Docker container should be stopped during local dev to avoid route conflicts. **Auth status:** Better Auth is configured with Prisma adapter. A custom login endpoint exists at `/api/auth/login`. Auth middleware is temporarily disabled in dev mode (`DEV_MODE=true` bypasses to a mock admin session). The dev domain (`dev.quest.vorteq.wulf.cloud`) also bypasses auth in middleware. **Git:** Currently only `main` branch. No `development` branch yet. ## 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 ### Formatting (Prettier) - Single quotes, semicolons, trailing commas (es5) - 2-space indentation, 80 char print width - Tailwind class sorting via `prettier-plugin-tailwindcss` ### Database - Prisma schema uses snake_case for all fields - Map legacy PascalCase column names with `@map("LegacyName")` where needed - 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 ### Component Pattern ```typescript // Server component by default export async function InventorySummary({ category }: { category: string }) { const data = await getInventorySummary(category); return ; } // Client component only when interactivity needed 'use client'; export function ShipmentRequestCart() { ... } ``` ## Epicor Connection Patterns ### Connection (`src/lib/epicor.ts`) - Singleton connection pool via `mssql` package - Typed error classes: `EpicorConnectionError`, `EpicorQueryError`, `EpicorTimeoutError` - `execStoredProc(name, params)` — execute stored procedure, returns `T` - `execQuery(query, params)` — execute parameterized SQL, returns `T` - `execPortalStoredProc(name, custId, dbName, sub, additionalParams)` — convenience wrapper with standard portal params - `getPortalDbName()` — returns `[VorteqPortal]` (bracketed DB name for cross-DB SP parameter) - `checkConnection()` — health check - Graceful shutdown on SIGINT/SIGTERM ### Inventory Stored Procedure Parameters **V6 procedures** (WIP, Finished Goods, Processed Other): - `@Customer` — the selected company's EpicorCustID - `@DBNAME` — portal database name in brackets, e.g., `[VorteqPortal]` - `@SUBUSER` — 0 or 1 (sub-user flag) - Detail variants also accept `@PART`, `@PLANT`, `@WAREHOUSE` for filtering **Non-V6 procedures** (Unprocessed, R&R variants): - `@CUSTID` — the selected company's EpicorCustID - `@DBNAME` — portal database name in brackets - No sub-user parameter — sub-users are blocked entirely (return empty array) - Detail variants accept `@part`, `@plant`, `@warehouse` (lowercase) ### Orders Pattern - Uses `execQuery` with parameterized SQL against Epicor views - Standard view: `dbo.portal_Orders WHERE CustomerID = @CustID` - HDC exception: uses `dbo.portal_OrdersHDC` view and maps CustID `HDC` → `HDM` ### Column Mapping Epicor SP results use PascalCase column names. Services map them to snake_case: ```typescript function mapSummaryRow(raw: Record): InventorySummaryRow { return { part_num: String(raw.VorteqPartNum ?? ''), description: String(raw.VorteqPartDesc ?? ''), on_hand_qty: Number(raw.OnHandQty ?? 0), // ... }; } ``` ## Auth & Session Pattern ### Better Auth (`src/lib/auth.ts`) - Prisma adapter connecting to `auth_user`, `auth_session`, `auth_account` tables - Email + password enabled, email verification disabled - 7-day session expiry ### Quest Session (`src/lib/permissions.ts`) `getQuestSession()` enriches the Better Auth session with: - `questUserId` — from `quest_user` table - `activeCompanyId` — from cookie-based session store (`src/lib/session.ts`) - `isSubUser` — from `quest_user.is_sub_user` - `permissionRules[]` — collected from user type → permission groups → permission rules - `userType` — e.g., "Admin", "Super Admin", "Customer User" In dev mode (`DEV_MODE=true`), returns a mock admin session with all permissions. ### Permission Helpers (`src/lib/permissions.ts`) - `requirePermission(rule)` — throws if user lacks permission - `hasPermission(rule)` — returns boolean - `isAdmin()` / `requireAdmin()` — check for Admin or Super Admin user type - `isSubUser()` — check sub-user status - `requireActiveCompany()` — throws if no company selected - `getActiveCompany()` — returns the active `quest_company` record - `getUserCompanies()` — returns accessible companies (admins see all active) - `PermissionRules` — constant object with all permission rule names ## Important Business Logic ### Company Context After login, the user's active company is stored in a cookie (`quest_session`). All Epicor queries are scoped to that company's `EpicorCustID`. Admin users can switch companies and see all active companies. ### Sub-User Restrictions Users with `is_sub_user=true` on their `quest_user` record: - See filtered inventory data (SUBUSER=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. Handled in `src/services/orders.ts`. ### VGL Customer Exception Customer 'VGL' uses a special SQL query for coil receipts instead of the standard view. ## Commands ```bash # Development npm run dev # Start Next.js dev server npm run build # Production build npm run start # Start production server npm run lint # ESLint npm run typecheck # TypeScript type checking (tsc --noEmit) npm test # Vitest npm run test:e2e # Playwright E2E tests # Database npx prisma migrate dev # Create + apply migration (dev) npx prisma migrate deploy # Apply migrations (prod) npx prisma db push # Push schema without migration npx prisma generate # Regenerate Prisma client npx prisma studio # Database GUI tsx prisma/seed.ts # Run seed script # Docker docker compose up -d # Start all services docker compose ps # Check service status docker compose logs -f vorteq-dev # Tail dev container logs # Pre-commit checks npm run lint && npm run typecheck && npm test ``` ### Local Development (Hot Reload) ```bash # Start local dev (instead of Docker container) docker compose stop app-dev # Release the competing Traefik route PORT=3001 npm run dev # Run in a tmux pane — hot reload at https://dev.quest.vorteq.wulf.cloud # Stop local dev (restore containerized version) # Ctrl+C the dev server, then: docker compose start app-dev ``` Traefik routes `dev.quest.vorteq.wulf.cloud` → `http://172.17.0.1:3001` via `traefik/config/dev-local.yml`. CI/CD pipeline is unchanged — pushes to `main` still build and deploy the production container. ## Environment Variables ```env # Application NODE_ENV=development|test|production DEV_MODE=true # Bypasses auth, uses mock admin session BETTER_AUTH_URL=https://dev.quest.vorteq.wulf.cloud BETTER_AUTH_SECRET= # PostgreSQL (per-environment, set in docker-compose.yml) DATABASE_URL=postgresql://postgres:@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= 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= MAIL_FROM=quest@vorteqcoil.com # reCAPTCHA RECAPTCHA_SITE_KEY= RECAPTCHA_SECRET_KEY= # Cloudflare (Traefik SSL - set in .env at stack level) CLOUDFLARE_DNS_API_TOKEN= ``` ## 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` (not yet created) - 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)