# Pelagia Portal

An internal purchase order management system for a maritime vessel-operations company. Digitises the full PO lifecycle — from crew requisition through manager approval, vendor validation, accounts payment, and receipt confirmation — replacing ad-hoc email chains and spreadsheets with a single auditable workflow.

## Tech Stack

| Layer | Technology |
|---|---|
| Framework | Next.js 15 (App Router) |
| Language | TypeScript 5 (strict) |
| Database | PostgreSQL 16 via Prisma 5 |
| Auth | NextAuth.js v5 (credentials) |
| Styling | Tailwind CSS v4 + shadcn/ui |
| File Storage | Cloudflare R2 (production) / local filesystem (development) |
| Email | Resend (production) / console log (development) |

---

## Prerequisites

| Tool | Required Version |
|---|---|
| Node.js | >= 20.11.0 LTS |
| pnpm | >= 9.0.0 |
| PostgreSQL | >= 16 (local or Docker) |

Install pnpm if you don't have it:

```bash
npm install -g pnpm
```

---

## Development Setup

In development mode the app requires **only a database and auth secret** — Cloudflare R2 and Resend are not needed. File uploads are saved to `.dev-uploads/` on your local machine, and emails are printed to the terminal instead of being sent.

### 1. Install dependencies

```bash
pnpm install
```

### 2. Configure environment

Copy the example file and fill in the two required values:

```bash
cp .env.example .env.local
```

Minimum `.env.local` for development:

```env
NEXTAUTH_SECRET=<generate with: openssl rand -base64 32>
NEXTAUTH_URL=http://localhost:3000
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/pelagia_portal"
```

The R2 and Resend variables are not needed in development and can be left as placeholders.

### 3. Set up the database

Create the database (if it doesn't exist yet), run migrations, and seed sample data:

```bash
pnpm db:migrate     # runs prisma migrate dev
pnpm db:seed        # seeds vessels, accounts, vendors, and demo users
```

To inspect the database with a GUI:

```bash
pnpm db:studio      # opens Prisma Studio at http://localhost:5555
```

### 4. Start the dev server

```bash
pnpm dev
```

The app will be available at [http://localhost:3000](http://localhost:3000).

**Email behaviour in dev:** all notification emails are logged to the terminal in place of actual delivery. Look for lines starting with `📧 [DEV EMAIL]`.

**File upload behaviour in dev:** uploaded files are written to `.dev-uploads/` at the project root. This directory is git-ignored.

---

## Serving in Production

Production requires all environment variables to be set, including Cloudflare R2 credentials and a Resend API key.

### 1. Configure environment

Set the following variables in your hosting platform (Vercel, etc.) or in `.env.local` for a self-hosted deploy:

```env
# Auth
NEXTAUTH_SECRET=<strong random secret>
NEXTAUTH_URL=https://your-domain.com

# Database
DATABASE_URL=postgresql://<user>:<password>@<host>:<port>/<db>

# Cloudflare R2
R2_ACCOUNT_ID=<your cloudflare account id>
R2_ACCESS_KEY_ID=<r2 access key>
R2_SECRET_ACCESS_KEY=<r2 secret key>
R2_BUCKET_NAME=pelagia-portal
R2_PUBLIC_URL=https://<bucket>.<account>.r2.cloudflarestorage.com

# Email
RESEND_API_KEY=re_<your key>
EMAIL_FROM=noreply@yourdomain.com
EMAIL_FROM_NAME="Pelagia Portal"

# Report Issue button -> files a Forgejo issue (optional; token needs write:issue)
FORGEJO_URL=https://git.example.com
FORGEJO_REPO=owner/repo
FORGEJO_TOKEN=<forgejo access token>

# Non-prod banner (leave UNSET in production). When set, a fixed
# "INTERNAL DEV / STAGING - NOT PRODUCTION" banner is shown.
# NEXT_PUBLIC_ENV_LABEL="INTERNAL DEV / STAGING - NOT PRODUCTION"
```

### 2. Run database migrations

```bash
pnpm db:migrate:deploy    # runs prisma migrate deploy (safe for production)
```

> **Always run migrations before the new build serves traffic.** `pnpm build` only runs `prisma generate` (which updates the TypeScript client) — it does **not** apply migrations. Deploying new code whose client expects a column the DB doesn't have yet produces `P2022 … column does not exist` errors at runtime. The release workflow (`.forgejo/workflows/deploy.yml`) runs `migrate deploy` as part of the deploy; for manual deploys, run it (and restart) before/with the swap.

### 3. Build and start

```bash
pnpm build
pnpm start
```

The app listens on port 3000 by default. Point your reverse proxy (nginx, Caddy, etc.) or hosting platform to that port.

---

## Operations & Automation

This repo carries its own self-hosted **issue-to-deploy pipeline** (Forgejo + Claude Code
on the `pms1` server). The full design and runbook live in
**[`../automation/README.md`](../automation/README.md)**. In short:

- **Report Issue button** (portal header) files a Forgejo issue tagged `portal`.
- A **watcher** triages each issue (Claude posts a requirements breakdown and routes it
  to `claude-queue` or `interactive`), then for queued issues implements a fix and opens a PR.
- Merging a PR and pushing a **release tag (`vX.Y.Z`)** triggers a Forgejo Actions runner
  that deploys to production.
- A **staging instance** (`automation/staging-up.sh`, pm2 `ppms-staging` on port 3200,
  SSH-tunnel only) runs the latest `master` against a daily **prod-mirror test DB**
  (`pelagia_test`) for smoke testing before tagging a release.

Operational scripts live under [`../automation/`](../automation/): `claude-issue-watcher.sh`
(watcher), `refresh-test-db.sh` (nightly test-DB refresh), `staging-up.sh` (staging),
and `staging-tunnel.cmd` (Windows tunnel launcher).

---

## Database Management

| Command | Purpose |
|---|---|
| `pnpm db:migrate` | Create and run a new migration (dev only) |
| `pnpm db:migrate:deploy` | Apply pending migrations without prompting (CI/production) |
| `pnpm db:push` | Push schema changes without a migration file (prototyping only) |
| `pnpm db:seed` | Seed sample/demo data (dev) |
| `pnpm db:seed:prod` | Seed real production reference data — users, companies, cost centres, sites, and the full accounting-code hierarchy (idempotent) |
| `pnpm db:studio` | Open Prisma Studio GUI |
| `pnpm db:reset` | Drop and recreate the database, then re-seed (dev only) |

---

## Testing

```bash
pnpm test           # unit + integration tests (Vitest)
pnpm test:watch     # watch mode
pnpm test:e2e       # end-to-end tests (Playwright)
pnpm test:e2e:ui    # Playwright with interactive UI
```

---

## Other Scripts

```bash
pnpm lint           # ESLint
pnpm type-check     # tsc --noEmit
pnpm email:preview  # live-preview email templates at http://localhost:3001
```

---

## Project Structure

```
pelagia-portal/
├── app/                    # Next.js App Router pages and API routes
│   ├── (auth)/             # Login
│   ├── (portal)/           # Authenticated shell (sidebar + header)
│   │   ├── dashboard/
│   │   ├── po/             # PO creation, detail, edit
│   │   ├── approvals/      # Manager approval queue
│   │   ├── payments/       # Accounts payment queue
│   │   ├── history/        # Audit trail
│   │   └── admin/          # User, vessel, account, vendor management
│   └── api/
│       ├── auth/           # NextAuth endpoints
│       ├── files/sign/     # Generate presigned upload URL (production)
│       ├── files/dev/      # Local file upload/download handler (dev only)
│       └── reports/export/ # CSV / PDF export
├── components/             # Shared UI components (shadcn/ui + custom)
├── lib/                    # Business logic
│   ├── po-state-machine.ts # All PO state transitions enforced here
│   ├── permissions.ts      # Role → allowed-action map
│   ├── notifier.ts         # Email dispatch (Resend in prod, console in dev)
│   ├── storage.ts          # File storage (R2 in prod, local in dev)
│   └── validations/        # Zod schemas
├── emails/                 # React Email templates
├── prisma/                 # Schema and migrations
└── tests/                  # Vitest unit/integration + Playwright E2E
```

---

## Roles

| Role | Description |
|---|---|
| Technical | Deck/engine crew — create and submit POs, confirm receipt, add (unverified) vendors |
| Manning | Crew-management staff — same as Technical |
| Manager | Review, approve, reject, request edits; manage cost centres, items, vendors |
| Accounts | Process payment for approved POs (records payment reference + date); manage vendors |
| SuperUser | Combined Technical + Manning + Manager authority |
| Auditor | Read-only access to all records and reports |
| Admin | Manage users, companies, accounting codes, cost centres, sites, items, and vendors |

User accounts are provisioned by an Admin (or via Microsoft Entra SSO); there is no self-registration. SSO-only users have no password and may optionally set one from their profile.

## Domain Concepts

- **Cost Centre** — a PO is raised against a **Vessel** (surfaced as "Cost Centre" in the UI). Required on every PO.
- **Company** — the sister company a PO is billed under (e.g. PMS, HNR, DEI). Its GST/address details appear on the exported PO.
- **Accounting Code** — a 3-level hierarchy of 6-digit codes (Top Category → Sub-Category → Leaf). Only leaf codes are selectable on a PO.
- **PO Number** — auto-formatted `COMPANY/VESSEL/ID/FY` (e.g. `PMS/HNR1/9000/2024-25`); imported POs keep their original number.
- **Vendors** — submitters can add vendors; they stay *unverified* until a PO closes with them or a Manager/Accounts/Admin verifies them.
- **Import PO** (Manager/SuperUser) — uploads a Pelagia-format Excel PO straight into `CLOSED`, auto-creating the vendor and any new items.
