# MRP QR Code System A single-container, self-hosted Manufacturing Resource Planning (MRP) app built around printable QR-coded traveler cards. Designed for small fabrication shops running on an Unraid server with phone-based operators. ## Status Shipped (see [`docs/BUILD-PLAN.md`](docs/BUILD-PLAN.md)): steps 1, 2, 3, 4, 5, 6, 8. - **1.** Scaffold + auth (admin email/password, operator name/4-digit PIN with 12 h device session, PIN lockout, audited sessions). - **2.** Admin CRUD (users, machines, operation templates, projects / assemblies / parts) with content-addressed STEP / PDF / DXF / SVG file uploads. - **3.** Operation authoring with per-operation QR tokens (192-bit, base64url). - **4.** Operator scan flow — phone scan resolves `/op/scan/`, single-claim enforced at DB level, Start / Pause / Done with inline QC for steps that require it, TimeLog rows for every claim. - **5.** PDF traveler generation — per-operation card + per-part cover sheet with the full operation list and file manifest. Printed via `pdf-lib` (no native deps). - **6.** Fasteners + purchase orders — per-project BOM of fasteners with unresolved-need rollups, PO lifecycle (`draft → sent → partial → received`, or `cancelled`), per-line receipt entry with auto-advance, and vendor-ready PDF downloads. - **8.** In-browser STEP viewer — OpenCascade (WASM, via `occt-import-js`) parses STEP/STP, `three.js` renders with OrbitControls. Thumbnails are captured from the first rendered frame, uploaded to the content-addressed file store, and displayed on the assembly parts list. No native deps, no server-side GL. Planned (not yet shipped): dashboard (step 7) → dedicated QC operations (9) → OpenAPI docs + backups (10). ## Core concepts - **Project → Assembly → Part → Operation.** Each operation is one shop-floor step (cut, bend, rivet, weld, …) and gets its own printable QR card. - **Single claim.** Only one operator can hold an operation at a time; other scans show it as in-progress. - **Two roles.** Admins (email + password) plan the work. Operators (PIN) execute it from their phones. - **Files.** STEP / PDF / DXF / SVG upload per part; STEP viewer will render in-browser so phones don't need a CAD app. - **Purchasing.** Fasteners roll up across a project into PO drafts. - **Online only.** The server lives in the shop; no offline/PWA queueing. ## Stack - Next.js 15 (App Router) + React 19 + TypeScript - Prisma + SQLite (file-backed, on a single `/data` volume) - Tailwind CSS 4 - bcryptjs for password / PIN hashing - Zod for input validation and environment parsing ## Deployment The primary deployment target is **Unraid**, using an image built by a **Gitea Actions** runner on every push to `main` and pulled from the private registry at **`registry.alwisp.com`** into Unraid's Docker GUI. ``` push to main ─► Gitea Actions (docker-build.yml) ─► registry.alwisp.com (docker build + push) /:latest │ ▼ Unraid Docker tab ─► pull / force-update ``` Two deploy paths are supported; pick one: - **`docker pull`** — Unraid pulls a prebuilt image the Gitea runner already tagged. Fastest, this is what the runner is for. - **`docker build`** — Unraid clones this repo and builds the image locally, no registry required. See [`docs/DEPLOY.md`](docs/DEPLOY.md) for the full, click-by-click Unraid GUI walkthrough (template fields, volume mapping, env vars, update flow, backups). ### TL;DR Unraid install 1. **Docker tab → Add Container**. 2. **Repository**: `registry.alwisp.com//:latest` (the owner/repo path matches `${{ gitea.repository }}` from the workflow). 3. **Network Type**: Bridge. **Port**: host `3000` → container `3000`. 4. **Path**: host `/mnt/user/appdata/mrp-qrcode` → container `/data`. 5. **Variables** (required): - `APP_URL` = the public HTTPS URL your reverse proxy serves (`https://mrp.yourdomain.tld`) - `APP_SECRET` = a ≥32-char secret (`openssl rand -base64 48`) - `BOOTSTRAP_ADMIN_EMAIL`, `BOOTSTRAP_ADMIN_PASSWORD`, `BOOTSTRAP_ADMIN_NAME` 6. Apply. The container runs migrations, creates the bootstrap admin on first boot, and comes up on `:3000`. ## Local development Prerequisites: Node 20+, npm. ```bash cp .env.example .env # edit .env: set APP_SECRET to >=32 random chars npm install npx prisma migrate deploy npm run db:seed # creates the bootstrap admin from .env npm run dev ``` Visit and sign in as the bootstrap admin. ## CI: Gitea Actions Image builds are driven by `.gitea/workflows/docker-build.yml`. On every push to `main` the runner: 1. Logs into `registry.alwisp.com` with the `REGISTRY_USER` / `REGISTRY_TOKEN` repo secrets. 2. Runs `docker build -t registry.alwisp.com/${{ gitea.repository }}:latest .` 3. Pushes the `:latest` tag. Point Unraid at `registry.alwisp.com//:latest` and use **Check for Updates / Force Update** (or the *CA Auto Update Applications* plugin) to roll new builds. ## Environment All env vars are documented in [`.env.example`](.env.example). `APP_SECRET` must be set and at least 32 characters in production. ## Project layout ``` app/ Next.js routes (UI + /api/v1/*) components/ Shared React components lib/ env, prisma, auth, session, password, qr, audit, request helpers prisma/ schema.prisma + migrations/ scripts/ seed.ts and future ops scripts docker/ entrypoint.sh docs/ Project docs (DEPLOY, BUILD-PLAN, ARCHITECTURE) .gitea/workflows/ Gitea Actions (docker-build.yml → registry.alwisp.com) ``` ## Not in this repo The top-level `AGENTS.md`, `SKILLS.md`, `hubs/`, and `skills/` directories are the coding-agent instruction suite this project was started from. They are reference material for AI assistants, are listed in `.dockerignore`, and are not shipped in the Docker image.