# Handover — Absher operations dashboard

For the developer taking this over, in particular to connect the customer mobile app.

Read this first, then `GUIDE.md` (how the business works, page by page) and `AI-CONTEXT.md` (one-page summary).

---

## 1. Run it

```bash
cp .env.example .env     # DATABASE_URL + AUTH_SECRET
npm install
npx prisma generate      # npm blocks lifecycle scripts by default, so run this yourself
npx prisma db push       # creates prisma/dev.db from the schema
npm run db:seed          # demo data: 5 users, 5 customers, 6 products, orders, rounds…
npm run dev              # http://localhost:3000
```

Sign in with `admin@absher.local` / `absher123` (every seeded user shares that password; the
other four show what the permission matrix does: Sales Manager, Dispatcher, Warehouse Agent,
Accountant).

`npm run db:reset` wipes and reseeds. `npm run build && npm start` serves the production build.

Verified on a clean copy: install → generate → push → seed → build → start, all 24 pages served.

## 2. What it is

A Next.js 16 (App Router, Turbopack, React 19) back office with Prisma + SQLite. Everything
is server-rendered; writes go through **server actions**, not an HTTP API. ~12 500 lines of
TypeScript: 54 files under `src/app`, 20 components, 22 library modules, 14 action modules.

```
prisma/schema.prisma     All models. SQLite today; switch the datasource for Postgres/MySQL.
prisma/seed.ts           Demo data, including a chronological stock ledger.
src/app/(dash)/          One folder per page; `page.tsx` reads with Prisma and renders.
src/app/api/export/*     Excel/CSV exports (dashboard session required).
src/app/api/uploads/     Serves files written to `uploads/` (product photos).
src/app/print/*          A4 documents (proforma, delivery note, round sheet, counter receipt).
src/actions/*            Every write. "use server"; one module per department.
src/lib/                 Business rules: pricing, stock, receivables, auth, access, i18n.
src/components/          Shared UI (forms in popups, POS terminal, maps, charts, barcode).
```

Conventions worth keeping:

- **Every write is a server action** in `src/actions`, and starts with
  `actor(department, operation, formData)` — that checks the permission and, for edits and
  deletes, requires a reason (`motif`) which lands in `ActivityLog`.
- **Stock only moves through `moveStock()`** (`src/lib/stock.ts`), which writes a `StockMove`
  with its type, source document and running balance. Nothing writes `Product.quantity` directly.
- **Prices are never typed in.** `unitPriceFor()` (`src/lib/pricing.ts`) picks the price from the
  customer's account type and enforces the min/max quantity window.
- **Page access lives in one place**: `src/lib/access.ts` lists the pages and their department,
  the sidebar hides what the role cannot view, and `src/proxy.ts` refuses the same URLs when
  typed by hand.
- **Forms open in popups** (`FormDialog`); pages themselves show data.
- **Interface is English + Arabic (RTL)**; printed documents stay non-Arabic. All UI strings go
  through `t()` and must exist in `src/lib/i18n/dictionaries.ts` (638 keys, all translated).

## 2b. Starting from an empty database

`npm run db:seed` fills the demo data. For a real installation use:

```bash
ADMIN_EMAIL=you@company.dz ADMIN_PASSWORD="a long password" npm run db:fresh
```

`prisma/fresh.ts` empties every table and leaves only what is needed to sign in and start:
the five roles with their permission matrix, one Super Admin, and the company sheet with the
document numbering back to PF-1 / BL-1. It also clears uploaded product photos. Without the two
environment variables it keeps `admin@absher.local` / `absher123`, which must then be changed
on /users straight away.

## 3. Before this runs anywhere but a laptop

- **`AUTH_SECRET` must be set.** `src/lib/auth.ts` and `src/proxy.ts` fall back to the literal
  string `absher-local-dev-secret`; anyone who knows it can forge a session cookie.
- **SQLite is a single file** (`prisma/dev.db`, git-ignored). For a server with the mobile app
  talking to it, move the datasource to PostgreSQL — the schema needs no other change.
- **Uploads are on local disk** (`uploads/`, git-ignored, served by `/api/uploads/[...path]`).
  Object storage is the usual replacement once there is more than one machine.
- **Action errors are only readable in `next dev`.** A production build replaces the message with
  a generic one, so "Minimum order for this account type is 5000" becomes "This action could not
  be completed". Fixing that properly means returning errors as values (`useActionState`) rather
  than throwing — worth doing before real users see it.
- `package.json#prisma` is deprecated in Prisma 7; move the seed entry to `prisma.config.ts` at
  the next upgrade.

## 4. Connecting the customer mobile app

The dashboard was built with the app in mind, but **the API itself does not exist yet**. What is
already in place, and what has to be written:

### Already in the data model

| Hook | Where | What it does today |
|---|---|---|
| `Notification` | `prisma/schema.prisma` | Rows with `audience` (`CUSTOMER` / `MANAGEMENT`), `customerId`, `type`, `title`, `body`, `read`. Written by `notifyCustomer()` when an account is accepted or rejected, its type changes, or a document is issued or sent. |
| `Proforma.sentToApp`, `DeliveryNote.sentToApp` | same | Set by the "Send to app" button. The flag is the dashboard saying "this document is ready for the customer". |
| `Customer` | same | `status` (`PENDING` / `ACTIVE` / `INACTIVE` / `REJECTED`), `accountType`, phone, address, lat/lng, credit limit, billing identifiers. |
| `Order` | same | Already carries `customerId`, lines, status (`NEW` → `ACCEPTED` → `SCHEDULED` → `DELIVERED` / `PARTIAL`, then `RECONCILED` when paid; `REJECTED` when refused), delivered quantities, payments. An order created from the app fits as a `NEW` order. |
| Reusable rules | `src/lib/` | `unitPriceFor()` (price + quantity window), `catalogForForms()` (catalog as the app would need it), `customerBalance()` (invoiced / paid / remaining), `src/lib/receivables.ts` (FIFO allocation, 15-day terms). Call these from the API so the app and the dashboard can never disagree. |

### Missing — the work to do

1. **Customer authentication.** `Customer` has no credential field; only internal `User`s sign in
   (`src/lib/auth.ts`, bcrypt + a `jose` JWT in the `absher_session` cookie). The app needs its
   own flow — phone + OTP is the natural fit — and its own token. Do not reuse the dashboard
   cookie: it carries the staff permission matrix.
2. **An HTTP API.** Everything today is a server action, callable only from the dashboard's own
   pages. Add route handlers under `src/app/api/mobile/…` that authenticate the customer token
   and then call the same functions in `src/lib` and `src/actions`. A minimal surface:
   - `GET /catalog` — active products with the price for *that customer's* account type and the
     allowed quantity window (`catalogForForms` already shapes this).
   - `GET /orders`, `POST /orders` — their own orders; a new one lands as `NEW` for staff to accept.
   - `GET /documents` — proformas and delivery notes where `sentToApp = true`.
   - `GET /account` — profile, credit limit, outstanding balance (`customerBalance`).
   - `GET /notifications`, `POST /notifications/:id/read` — rows already written for the customer.
3. **Document files.** `/print/*` pages are HTML behind the staff session. The app needs a PDF (or
   an open signed URL); rendering the same page to PDF server-side is the smallest step.
4. **Push.** Nothing stores a device token. Add a `CustomerDevice` table and send on the same
   events that already call `notifyCustomer()`.
5. **Scope the gate.** `src/proxy.ts` redirects everything without a staff cookie to `/login`; the
   mobile routes must be allowed through there and authenticated on their own.

Keep the rules on the server: the app should never compute a price, a quantity limit or a balance
by itself.

## 5. State of the code

- TypeScript: clean (`npx tsc --noEmit`).
- Lint: 2 known errors, both pre-existing and harmless — `Date.now()` during render in
  `statistics/page.tsx`, and a `setState` in an effect in `nav.tsx` (icons mount after hydration).
- Production build: compiles, 33 routes.
- Checked before handover: every page for all five roles in both languages (270 loads, no
  errors), and every server action exercised against the database.
