- App/README.md: add FORGEJO_*/NEXT_PUBLIC_ENV_LABEL env vars and an 'Operations & Automation' section pointing to automation/README.md. - App/CLAUDE.md: complete the env var list (AZURE_AD_*, FORGEJO_*, GST_SERVICE_URL, NEXT_PUBLIC_ENV_LABEL) and note the prod-mirror test DB used by autofix/staging. - .env.example: document NEXT_PUBLIC_ENV_LABEL. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
125 lines
5.9 KiB
Markdown
125 lines
5.9 KiB
Markdown
# CLAUDE.md
|
||
|
||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||
|
||
## Commands
|
||
|
||
```bash
|
||
# Development
|
||
pnpm dev # Next.js + Turbopack at localhost:3000
|
||
pnpm lint # ESLint
|
||
pnpm type-check # tsc --noEmit
|
||
|
||
# Tests
|
||
pnpm test # Unit tests (Vitest, jsdom)
|
||
pnpm test:watch # Unit tests in watch mode
|
||
pnpm test:integration # Integration tests (Vitest, node + real DB)
|
||
pnpm test:e2e # E2E tests (Playwright, headless)
|
||
pnpm test:e2e:ui # E2E tests with interactive UI
|
||
pnpm test:all # All test suites
|
||
|
||
# Run a single test file
|
||
pnpm test -- tests/unit/po-line-items-editor.test.tsx
|
||
pnpm test:integration -- tests/integration/create-po.test.ts
|
||
|
||
# Database
|
||
pnpm db:migrate # Create + apply migration (dev)
|
||
pnpm db:migrate:deploy # Apply migrations (CI/prod)
|
||
pnpm db:seed # Populate sample data
|
||
pnpm db:studio # Prisma GUI at localhost:5555
|
||
pnpm db:reset # Drop + recreate + seed (dev)
|
||
```
|
||
|
||
## Architecture
|
||
|
||
### Overview
|
||
|
||
Internal purchase order management system for a maritime company. Full-stack Next.js 15 App Router app with Prisma + PostgreSQL, NextAuth v5 credentials auth, and Tailwind CSS v4.
|
||
|
||
**Key design decisions:**
|
||
- Server Components for all data-fetching pages; Client Components only where interactivity is needed
|
||
- Server Actions for all mutations (form submissions, approvals, etc.)
|
||
- Prisma `Decimal` fields **cannot** be passed directly to Client Components — convert with `Number()` in the Server Component before passing as props (see `po-detail.tsx` → `lineItemsForEditor` pattern)
|
||
- File storage toggles automatically: Cloudflare R2 in production, `.dev-uploads/` directory in development
|
||
- Email toggles automatically: Resend in production, console log in development
|
||
|
||
### PO Lifecycle (State Machine)
|
||
|
||
`lib/po-state-machine.ts` enforces all status transitions. The canonical flow:
|
||
|
||
```
|
||
DRAFT → SUBMITTED → MGR_REVIEW → MGR_APPROVED → SENT_FOR_PAYMENT → PAID_DELIVERED → CLOSED
|
||
↓↑
|
||
EDITS_REQUESTED / REJECTED / VENDOR_ID_PENDING
|
||
```
|
||
|
||
Every status change is validated against the state machine and recorded as a `POAction` row (audit trail).
|
||
|
||
### Role-Based Permissions
|
||
|
||
`lib/permissions.ts` defines `hasPermission(role, permission)` and `requirePermission(role, permission)`. Roles: `TECHNICAL`, `MANNING`, `ACCOUNTS`, `MANAGER`, `SUPERUSER`, `AUDITOR`, `ADMIN`.
|
||
|
||
**Pattern:** Server Actions call `requirePermission()` at the top before any DB write.
|
||
|
||
### Key Directories
|
||
|
||
- `app/(portal)/` — All authenticated pages (portal layout with sidebar)
|
||
- `app/api/po/[id]/export/` — PDF and XLSX export endpoint
|
||
- `lib/validations/po.ts` — Zod schemas for PO forms; exports `TC_FIXED_LINE` and `TC_DEFAULTS`
|
||
- `lib/po-state-machine.ts` — All valid status transitions with required roles
|
||
- `lib/notifier.ts` — Email dispatch (Resend in prod, console in dev)
|
||
- `lib/storage.ts` — File upload/download (R2 in prod, local in dev)
|
||
- `components/po/` — PO-specific components (line items editor, status badge, etc.)
|
||
- `tests/integration/helpers.ts` — `makeSession()`, `makePoForm()`, `fd()` for integration test setup
|
||
|
||
### Cost Centre Model
|
||
|
||
A PO's "cost centre" is either a **Vessel** or a **Site**. `PurchaseOrder` has both `vesselId String?` (nullable) and `siteId String?` — exactly one is set.
|
||
|
||
**Form encoding:** All PO creation/edit forms use a `costCentreRef` field with values `v:<vesselId>` (vessel) or `s:<siteId>` (site). Server actions parse this to set the correct FK.
|
||
|
||
**Display pattern:** `po.vessel?.name ?? po.site?.name ?? "—"` everywhere a cost centre name is shown.
|
||
|
||
**URL pre-select:** `/po/new?costCentreRef=v:<id>` or `?costCentreRef=s:<id>`.
|
||
|
||
**Terminology:** Admin pages use the real entity names (Vessel Management, Sites). PO-facing pages use "Cost Centre" for the combined concept. Budget heads are labelled "Accounting Code" (not "Account").
|
||
|
||
### GST Calculation
|
||
|
||
`totalAmount = sum(quantity × unitPrice × (1 + gstRate))` for each line item. The `gstRate` is stored as a decimal on `POLineItem` (e.g., `0.18` = 18%). This applies in Server Actions when computing `totalPrice` per line and the PO `totalAmount`.
|
||
|
||
### Environment Variables
|
||
|
||
```
|
||
NEXTAUTH_SECRET # Required always
|
||
NEXTAUTH_URL # Required always (e.g., http://localhost:3000)
|
||
DATABASE_URL # PostgreSQL connection string
|
||
|
||
AZURE_AD_CLIENT_ID, AZURE_AD_CLIENT_SECRET, AZURE_AD_TENANT_ID
|
||
# Microsoft Entra SSO (prod). auth.ts reads them at module
|
||
# load — set placeholders in non-SSO/dev envs so it boots.
|
||
|
||
# Optional in dev (defaults to local storage + console email):
|
||
R2_ACCOUNT_ID, R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY, R2_BUCKET_NAME, R2_PUBLIC_URL
|
||
RESEND_API_KEY, EMAIL_FROM, EMAIL_FROM_NAME
|
||
|
||
# Report Issue button (lib/forgejo.ts); token needs write:issue:
|
||
FORGEJO_URL, FORGEJO_REPO, FORGEJO_TOKEN
|
||
|
||
GST_SERVICE_URL # GstService microservice (defaults to localhost:3003)
|
||
NEXT_PUBLIC_INVENTORY_ENABLED # Inventory feature flag
|
||
NEXT_PUBLIC_ENV_LABEL # When set, shows a non-prod banner (EnvBanner). Leave unset in prod.
|
||
```
|
||
|
||
### Operations & automation
|
||
|
||
This repo runs a self-hosted issue-to-deploy pipeline on the `pms1` server (Forgejo +
|
||
headless Claude Code). See [`../automation/README.md`](../automation/README.md). Relevant
|
||
when working in this codebase:
|
||
|
||
- The **Report Issue** button (portal header) files a Forgejo issue; a watcher triages it
|
||
and, for auto-fixable ones, implements a fix and opens a PR. Deploys are gated on a
|
||
human merging the PR and pushing a `vX.Y.Z` tag.
|
||
- Automated fixes and the **staging** instance run against `pelagia_test`, a **daily mirror
|
||
of the production database**, in dev mode (console email, local storage). Migrations are
|
||
applied to it, so its schema tracks `master`. Never assume an empty DB — it holds prod-like data.
|