diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..82e6ea4 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,307 @@ +# 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 +```typescript +// 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) { + 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 +```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() { ... } +``` + +### Auth Pattern (Better Auth) +```typescript +// 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 +```typescript +// 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 + +```env +# Application +NODE_ENV=development|test|production +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` +- 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)