# NEXX PLATFORM — procurementArea contract · PLANNED / NON-LIVE

> **Status: PLANNED / NON-LIVE.** This is the agreed contract *shape* for
> BKG-009 (`procurementArea` catalog discovery metadata). It is **not yet
> implemented**. The live contract remains **v1.5.2** (see
> `frontend-handoff.json` / `openapi-internal-v1.json` on this landing).
> **Do not** run `api:generate` / `api:check` against this note — when the
> backend implements and smoke-confirms, it ships as live **v1.6.0** via the
> normal artifact flow, in the **same package** as the pagination/search
> envelope (`pagination-search-planned-v1.6.0.md`).

Agreed: **2026-06-12**, Sasha GO. Source: Telegram "NEXX Platform NAD"
msgs #259–#262 (frontend request + provisional seed).

## What this is (and is not)

`procurementArea` — RU label **«Направление»** — is a lightweight normalized
marker answering "what does this supplier sell" for buyer discovery
(supplier-card chips + search filter). It is **catalog discovery metadata
only**, inside the current shared platform.

It is explicitly **NOT** the Phase 3 "domain" concept (no vertical
workspaces, no URL/user/data isolation), and the word *domain* is not used
anywhere in this contract.

Hard boundaries (per frontend #260, confirmed by backend):

- not involved in pricing, relationship lifecycle, cart eligibility/checkout;
- never copied into order / orderLine snapshots;
- never in official documents / PDF / VAT / accounting;
- no effect on roles or tenant boundaries; no stock/warehouse semantics;
- **PLATFORM-only**: not pushed to Engine / 1C, not part of master data.

## Model

- `procurementArea` is set **only on a supplier root category**.
- Child categories **inherit** it from their root (read-only, derived).
- Items inherit through their category.
- Supplier summary/detail expose a **derived aggregate** `procurementAreas`.
- The marker is **optional**: `null` / unassigned is always valid (legacy
  categories stay untouched — no backfill).
- `other` is a real dictionary value — a conscious supplier choice.

## Contract additions (all additive, camelCase)

### 1. Dictionary — `GET /api/internal/v1/catalog/procurement-areas/`

Controlled dictionary as **data-driven reference data**, NOT a hard OpenAPI
enum — `procurementArea` fields stay `string | null` in the schema, values
are validated server-side. Keys are **stable technical identifiers**;
labels/order can change without data migration or frontend redeploy.

```json
{
  "procurementAreas": [
    {
      "key": "food-beverage",
      "labels": { "ru": "Еда и напитки", "en": "Food & beverage" },
      "sortOrder": 10,
      "selectable": true
    }
  ]
}
```

- `selectable: false` = deprecated / hidden from selection UI; existing
  category records keep returning the key and stay readable.
- Auth: Bearer + `X-NEXX-Company-ID`, any active member (read-only).
- Small fixed list — no pagination envelope.

**Provisional pilot seed** (frontend #262; keys stable, labels/order pending
Kostya's product confirmation — label edits do not change this contract):

| key | RU | EN |
|---|---|---|
| `food-beverage` | Еда и напитки | Food & beverage |
| `beauty-health-supplies` | Красота и здоровье | Beauty & health supplies |
| `cleaning-hygiene` | Уборка и гигиена | Cleaning & hygiene |
| `packaging-disposables` | Упаковка и одноразовые материалы | Packaging & disposables |
| `equipment-tools` | Оборудование и инструменты | Equipment & tools |
| `office-business-supplies` | Офис и бизнес-расходники | Office & business supplies |
| `construction-maintenance` | Строительство и обслуживание | Construction & maintenance |
| `other` | Другое | Other |

### 2. Category

- `V1CategoryResponse` += `procurementArea: string | null`.
  - Root category: the assigned key (or `null`).
  - Child category: the **effective inherited** value from its root
    (derived, read-only).
- Root category create / PATCH accept optional `procurementArea`
  (a valid selectable key, or `null` to clear).
- Setting `procurementArea` on a **child** category → `400 VALIDATION_ERROR`
  (children only inherit).
- Unknown or non-selectable key on write → `400 VALIDATION_ERROR`.

### 3. Supplier summary / detail

- += `procurementAreas: string[]` — derived aggregate on both the suppliers
  list rows and the supplier card.
- Aggregated from root categories **with catalog content** (≥ 1 active item
  in the category subtree), deduplicated, ordered by dictionary `sortOrder`.
- Empty array is valid (supplier has no assigned areas yet).

### 4. Search / list filters (same v1.6.0 package)

- `GET /api/internal/v1/catalog/items/` += `area=<key>` — filters by the
  item's category root area. Combinable with `q`, `supplierId`,
  `categoryId`, `mine`, `limit`, `offset`.
- `GET /api/internal/v1/catalog/suppliers/` += `area=<key>` — suppliers whose
  derived `procurementAreas` contain the key. Combinable with `q`, `limit`,
  `offset`.
- Unknown `area` key on read → `400 VALIDATION_ERROR` (consistent v1
  validation posture; catches typos instead of silently returning empty).

## Boundary (important)

- This is a **PLANNED / NON-LIVE** contract shape for D3.1–D3.2 planning only.
- Live OpenAPI / handoff stays **v1.5.2** — **not** modified by this note.
- `api:generate` / `api:check` run **only after** live v1.6.0 artifacts ship.
- Backend implementation begins **after Stage 2 sign-off** (TDD, additive
  only), together with the pagination/search envelope; **v1.6.0** is
  published **only after backend smoke**.
- The shape is **open to frontend edits** until implementation begins.

---

Source of truth: Telegram "NEXX Platform NAD" #259–#262 + this note.
Questions → same chat or GitHub issues on `grabbly/nexx-platform-backend`.
