# Zeal POS API — Agent Guide

Applies to the whole repo. Read before touching application code, migrations, config, or generated output.

## Overview

- Multi-tenant POS/inventory REST API. Node.js, TypeScript (strict), Express 5, CommonJS output.
- MySQL via Sequelize 6; entities use UUIDs.
- Auth: JWT bearer tokens; protected endpoints require verified users.
- Source of truth: `src/`. `dist/` is compiled output (tracked by Git — review before committing).
- API prefix: `/api`.
- No README, test runner, linter, or formatter. Nearby code is the style authority.

## Repository Map

| Path | Responsibility |
| --- | --- |
| `src/server.ts` | dotenv + HTTP server bootstrap |
| `src/app.ts` | CORS, parsers, static files, context/logging, routes, errors |
| `src/routes/` | Express routes, top-level authorization/mount layout |
| `src/controller/` | Thin HTTP coordination: services, resources, responses |
| `src/request/` | Joi validation, tenant-aware checks, normalization |
| `src/service/` | Domain reads/writes, transactions, state transitions, business scoping |
| `src/models/` | Sequelize model factories + associations |
| `src/migrations/` | Timestamp-ordered schema migrations |
| `src/seeders/` | Reference-data seeders (up/down) |
| `src/resource/` | Public response mappers, paginated collections |
| `src/types/` | Entity attributes, payloads, response types, Express augmentation |
| `src/middlewares/` | Auth, selected-business, admin, request-context |
| `src/lib/` | Responses, errors, pagination, uploads, mail, logging, context |
| `src/config/` | App, CORS, mail, Sequelize config |
| `src/cache/` | In-memory OTP / password-reset caches |
| `src/mail/` | Mail senders + EJS templates |
| `src/utils/` | Joi error formatting, file removal, reference-number helpers |
| `scripts/` | One-off maintenance (currently APP_KEY generation) |
| `public/` | Static assets, Multer upload destinations |
| `logs/` | Runtime logs, Git-ignored |
| `dist/` | Generated JS, tracked |
| `todo.MD` | Historical plan — verify against code, don't trust as current |

## Setup and Commands

- `npm ci` to install from lockfile.
- `cp .env.example .env`, populate secrets locally.
- `npm run dev` — TS dev mode.
- `npm run build` — runs `tsc`, copies `src/config/config.js` + mail templates to `dist/`.
- `npm start` — run compiled output (after build).
- `npm run migrate` / `npm run seed`.
- `npm run key:generate` — only when actually needed; modifies local `.env` (secrets). Never run routinely, never commit `.env`.
- Never run `npm run clean` casually — deletes `node_modules` and `package-lock.json`.

Caveats:
- `sequelize-cli` isn't in `package.json`/`node_modules/.bin`. Migration script uses `npx` — ensure CLI is available before DB work.
- `.sequelizerc` (points CLI at `src/` TS files) is Git-ignored — don't assume it exists on a fresh checkout.
- `dist/` diffs from a build can be large — review deliberately, never hand-edit as primary implementation.

## Environment

Reference only — use `.env.example` as the template, never print/commit real credentials.

| Variable | Purpose |
| --- | --- |
| `APP_NAME` | `/api/health` response value |
| `APP_PORT` | HTTP port |
| `APP_KEY` | JWT sign/verify secret |
| `APP_URL` | App config value |
| `NODE_ENV` | Sequelize config selection, prod error-stack behavior |
| `DB_HOST/NAME/USER/PASS/DIALECT` | Sequelize model/CLI config |
| `DB_PORT` | Defined in `src/config/database.ts` — keep aligned if DB setup changes |
| `MAIL_HOST/PORT/USER/PASS` | Nodemailer config |

- Source uses `NODE_ENV`, not the `APP_ENV` in `.env.example` — set `NODE_ENV` explicitly.
- `src/models/index.ts` reads `src/config/config.js`, not `database.ts` — keep both coherent.

## Runtime Flow

`server.ts` loads dotenv → `app.ts` (CORS, parsers, static) → `contextMiddleware` (AsyncLocalStorage) + logger → `/` health check, `/api` mounts `routes/index.ts` → `patchRouterAsync()` forwards rejected async handlers → 404 + error handlers last.

Mount boundaries (`src/routes/index.ts`):
- Public: `GET /api/health`, `/api/auth/*`.
- Everything after `/auth`: `authMiddleware` (valid bearer token + verified email).
- `/api/profile`, `/api/businesses`, business category/type/role routes: authenticated, not under selected-business router.
- `/api/business/*`: `checkBusinessMiddleware` first — requires `x-business-id`, verifies admin, sets `req.businessData`.
- Selected-business router: customers, suppliers, employees, catalog, inventory, stores, batches, stock ops, sales, purchases, returns, expenses, activity logs, dashboard.
- `/api/admin/*`: **caveat** — `isAdminMiddleware` mounts at `/admin`, but role/user routes mount at `/roles`/`/users`. Verify or fix the mount before assuming those are guarded. Some business-metadata mutations apply `isAdminMiddleware` directly instead.

`req.userData` (user + role) and `req.businessData` (selected business) via `src/types/express.d.ts`. Services read these through `request()` from `src/lib/requestStore.ts` — only works inside an active HTTP request; pass context explicitly in scripts/migrations/seeders/workers.

## Tenant and Access Control

- Tenant scope is a security requirement, not a nicety.
- Scope every business-owned query/mutation/include/uniqueness check to the selected business UUID.
- Verify every related customer, supplier, category, unit, brand, item, store, variant, batch belongs to the same business before linking.
- Never trust client-supplied `business_id`, `created_by`, or other ownership fields — populate from trusted request context.
- Preserve auth/verification/membership/admin checks at their existing router boundary — don't weaken for a new endpoint.
- Some older services are inconsistently scoped — new/edited code must not copy that, only improve it.

## Layer Conventions

**Routes** — new domain router in `src/routes/<Domain>Routes.ts`, mounted in `index.ts` at the correct scope. Middleware order: upload handler → validator → controller. Literal routes before `/:id`.

**Validation** — `src/request/<kebab-domain>/`, PascalCase op names (`CreateCategoryRequest.ts`). Joi with `{ abortEarly: false }`, errors via `validationError(res, FormatJoiErrors(error.details))`. `FormatError` for DB-backed checks (duplicates, bad tenant FKs). Assign `req.body = value` after validation; set trusted derived fields (e.g. `business_id`) after. Uploads: build persisted paths from Multer-generated filenames only, never trust client paths.

**Controllers/Responses** — thin: call service → map through resource → return via `src/lib/apiResonse.ts` (keep the misspelling, imports are widespread). Success: 200 `{ message, data }`. Validation: 422 `{ message: "Validation failed", errors }`. `AppError` for specific status codes; async rejections already forwarded. `*Resource.ts` mappers control output — never leak raw models, tenant IDs, credentials, tokens, internal fields.

**Services/Transactions** — one operation per PascalCase file, `execute(...)`, existing singleton style. ORM access via `models` from `src/models`, no ad-hoc connections. Paginated reads: `paginate()` + matching collection resource (returns `false` on empty — preserve that in controllers). Wrap multi-row/monetary/inventory/stateful writes in `models.sequelize.transaction(...)`, pass the transaction through every related op. Compute totals, payment state, stock state, reference numbers, audit fields server-side — never trust client-supplied derived values. `logActivity()` for material create/update/delete/payment — never log passwords, OTPs, tokens, raw sensitive payloads.

**Models/Types/Resources** — model files default-export `(sequelize, DataTypes) => ...`; bootstrapper loads every non-test file in `src/models/`, so no stray helpers there. Associations in `static associate(models)`, exact `as` alias used everywhere. Keep fields synced across model/attributes/payload/response types/validation/resources. Preserve UUID/nullability/date/omitted-field semantics. Model filename casing is mixed — match existing imports exactly (breaks on Linux otherwise). Pagination resource shape: `{ data, meta, links }`.

## Domain Invariants

Trace model → validator → service → resource → migration → route together before touching a coupled workflow.

- **Catalog/stock**: items can have variants + batches; item creation may create both — preserve ownership + transaction boundaries.
- **Sales**: pricing, promotions, 7.5% tax, totals, payment status, invoice, sale items, optional payment, stock changes — all in one transaction, through the sales service only.
- **Purchases**: items + additional costs persisted; receipt logic updates inventory on receive — keep idempotent and locked.
- **Returns/payments**: depend on source quantities/statuses/totals/stock/prior payments — reuse dedicated services, don't duplicate partial logic.
- **Stock/price ops**: stateful — preserve locking, history, validation.
- **Reference numbers**: helpers in `src/utils/index.ts` derive prefixes from selected-business context — only valid inside a request with a selected business.

## Uploads and Logging

- `public/` served directly by `app.ts` — persist upload paths relative to it.
- `upload(folder)` → `public/<folder>`, existing image/doc MIME types, 1 MiB limit.
- `importUpload` → memory storage, 5 MiB, CSV/XLSX. Validate rows against domain rules before persisting.
- Runtime upload dirs: `avatar`, `brands`, `categories`, `customers`, `employees`, `inventory-items`, `logo`, `suppliers`. Don't commit user-generated uploads unless intentional fixtures.
- `logs/` — request/error logs, never commit, never rely on for correctness.

## Database Changes

1. New timestamp-prefixed migration in `src/migrations/` — never rewrite one that may already be applied.
2. Reversible `up`/`down`, matching existing table/FK/index/nullability conventions.
3. Update model, types, associations, validators, services, resources together.
4. Seeder only for stable reference data — avoid duplicate names, clean up in `down`.
5. Run migrations only against the intended DB; build and inspect generated output before committing.

Use explicit transactions + row locks for inventory/payments/returns/multi-table state — prevents duplicate processing and negative-stock races.

## Style and Compatibility

- Strict TS — avoid new `any` except a narrow, unavoidable Sequelize dynamic boundary.
- CommonJS output, relative imports only — no new module system, no unresolved aliases.
- Match nearest code's quote style (mixed single/double) rather than reformatting unrelated files.
- PascalCase operation files, kebab-case request/service domain dirs, descriptive identifiers.
- Don't rename established quirks (`apiResonse.ts`, `EmailVerifcationMail.ts`) as incidental cleanup.
- No explanatory comments in code — use clear, descriptive variable/function names instead so the code is self-explanatory.
- Avoid broad refactors during focused work. Preserve existing routes/response shapes unless a breaking change is explicitly intended.

## Validation Checklist

No automated test/lint scripts exist. Validate narrowly, then `npm run build`, `git diff --check`, `git status --short` when practical. For behavioral changes, exercise the route manually, or add focused tests only if test infra is already in scope.

Before handoff, confirm:
- Route middleware has correct auth/business/admin scope and order.
- Validator, controller, service, type, resource, model all agree.
- Every business-owned DB access is tenant-scoped.
- Multi-step money/stock mutations use transactions + required locks.
- Resources and activity logs exclude sensitive fields.
- Migrations/seeds have intentional rollback, don't alter already-applied migrations.
- Any `dist/` changes are generated from `src/` and intentional.
- Diff excludes `.env`, logs, DB dumps, runtime uploads, unrelated generated files.