food-market/docs/error-codes.md
nns 72d0a71307
Some checks are pending
Auto-tag / Create date-tag (push) Waiting to run
CI / Backend (.NET 8) (push) Waiting to run
CI / Web (React + Vite) (push) Waiting to run
CI / POS (WPF, Windows) (push) Waiting to run
Docker API / Build + push API (push) Waiting to run
Docker API / Deploy API on stage (push) Blocked by required conditions
docs(s24): docs cross-check + auto-gen + onboarding + test gap-fill (8/8 ✓)
1. Docs cross-check — обновил performance-baseline.md (Sprint 18/20/23
   фиксы), secrets.md (16 новых env-vars из Sprint 20+ — Authentication
   Google/Microsoft, Monitoring, Cleanup, Hangfire:Cron, Telegram,
   Maintenance, App, Storage, PUBLIC_GA_ID/YM_ID).

2. Auto-gen api-reference — ApiReferenceDocsJob (Hangfire weekly вс
   05:30 UTC) + Python-эквивалент `/tmp/gen-api-ref.py` для commit
   actual snapshot. docs/api-reference.md = 195 endpoints, 57 controllers.

3. Coverage gap-fill — Sprint18To23FeaturesTests.cs (16 Facts):
   - bulk-update + cross-tenant isolation
   - UserPresets CRUD
   - inline-edit price PATCH
   - CSV import 2 строки транзакцией
   - OrgExport create + list isolation
   - 1C-CSV import с русскими заголовками
   - audit-log export CSV streaming + BOM check
   - MoySklad sync-status stub
   - SSO providers + 503 unconfigured + 400 unknown provider
   - bug-001 NUL byte → 400
   - bug-004 tiny price → 400
   - export CSV BOM
   Покрывает все новые контроллеры Sprint 18-23 + regression-protect
   для критичных багов.

4. Contract tests — deploy/swagger-diff.sh: pull /swagger/v1/swagger.json
   с двух URL, diff endpoints+schemas через python3. Exit 0/1/2 для
   blue-green safety gate. Multi-path auto-detect.

5. docs/error-codes.md — каталог HTTP-кодов API (200-503) + humanizeError
   pattern для фронта + retry-policy таблица.

6. docs/glossary.md — 50+ доменных терминов (Tenant/Organization/Stock/
   StockMovement/RetailSale/Counterparty/Owner/Employee/Role/Permission/
   advisory lock/Serializable/…) с ссылками на code-сущности.

7. docs/ONBOARDING.md — first 3 days для нового разработчика
   (install → запуск → структура → первый PR + FAQ).

8. README.md — обновил под текущее состояние: React 19, Sprint-history
   1-24, ссылки на ключевые docs, корректный 5-min quick start.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-08 02:15:56 +05:00

8.7 KiB
Raw Blame History

API error catalog

Каталог HTTP-кодов и тел ответов, которые возвращает food-market.api. Используется фронтом для humanizeError(response) и QA для regression проверки. Если поле error есть — это user-facing сообщение; errors (множественное) — структурированные ошибки валидации (ASP.NET ValidationProblemDetails).

Формат

// Универсальный шаблон single-error:
{ "error": "Понятный текст для пользователя.", "field": "Optional" }

// ValidationProblemDetails (FluentValidation / DataAnnotations):
{ "type": "...", "title": "One or more validation errors occurred.",
  "status": 400, "errors": { "Name": ["..."], "Prices[0].Amount": ["..."] } }

// retryable flag (Sprint 23):
{ "error": "...", "retryable": true }

Коды

200/201/204 — OK / Created / NoContent

Корректно. Тело — DTO или пусто.

400 — Bad Request

Когда Тело Что показать
Validation от FluentValidation ValidationProblemDetails с errors.{field}: [msg] Подсветить поле + показать сообщение
Business-rule (например, draft пустой) {error: "Нельзя провести пустой чек."} toast + не закрывать форму
Сумма оплаты < total {error: "Сумма оплаты X меньше итога Y. Доплатите...", field: "PaidCash"} подсветить поле PaidCash
Required price = 0 после rounding (Sprint 23 bug-004) {error: "Цена «X» обязательна и должна быть больше 0."} подсветить prices section
NUL-byte в строке (Sprint 23 bug-001) errors.Name: ["Поле Name не должно содержать управляющих символов..."] подсветить поле
Дубликат barcode при создании {error: "Штрихкод X уже используется товаром «Y»."} toast
Дубликат артикула {error: "Артикул «X» уже занят в этой организации."} toast
Невалидный CSV / 1С-import errors: [{row, error}] таблица с подсветкой строк

401 — Unauthorized

Когда Тело Что показать
Нет токена / устаревший токен пусто или OpenIddict-{error: "missing_token"} редирект на /login, refresh с RT
Garbage / tampered JWT {error: "missing_token"} logout + login
Refresh-token недействителен {error: "invalid_grant", error_description: "..."} logout

403 — Forbidden

Когда Тело Что показать
Нет permission на mutating action пусто или ProblemDetails toast: «Нет прав на это действие»
Регулярный Admin лезет в /hangfire пусто redirect → 404 на фронте
Cashier пытается удалить заявку пусто скрыть кнопку delete для Cashier

404 — Not Found

Когда Что показать
Document не найден (включая cross-tenant — нельзя раскрыть существование!) «Запись не найдена. Возможно, удалена.»
Endpoint не существует (типо в URL) (фронту не должно встречаться)

409 — Conflict

Когда Тело Что показать
DbUpdateConcurrencyException (xmin) {error: "Документ изменён в другом окне..."} toast + reload
Чек уже проведён, повторный post {error: "Чек уже проведён."} toast
Serialization failure 40001 (Sprint 23 bug-003) {error: "Конфликт параллельных операций. Попробуйте ещё раз.", retryable: true} auto-retry один раз, при повторе — toast
Дубликат preset name {error: "Пресет с таким именем уже существует..."} подсветить input name
In-flight org-export ≥3 {error: "Уже в очереди 3+ экспорта. Подождите..."} toast
Удаление непустой группы товаров {error: "Нельзя удалить группу, содержащую товары/подгруппы."} toast

413 — Payload Too Large

Когда Что показать
Body > nginx limit (10 MB по default) «Файл слишком большой. Лимит: 10 МБ.»

429 — Too Many Requests

Когда Тело Что показать
Rate-limit на signup (3/h IP) пусто или Retry-After header «Слишком много попыток. Попробуйте через час.»
Rate-limit на forgot-password (3/h email + 10/h IP) то же то же
Rate-limit на feedback (5/час) то же то же
IP-limit (60/мин общий) то же «Слишком много запросов с вашего IP.»

431 — Request Header Fields Too Large

Когда Что показать
Слишком большие/много HTTP-headers (нечем фиксить с UI; нечасто)

500 — Internal Server Error

После Sprint 23 — очень редко. Если встречается:

  • Все NUL-byte 500 → теперь 400 (bug-001).
  • Все serialization 40001 → теперь 409 (bug-003).
  • Все остальные uncaught exceptions → Serilog лог + correlation-id header.

Что показать пользователю: «Произошла ошибка. Попробуйте ещё раз или сообщите администратору. Код: {x-correlation-id}». Этот correlation id находится в x-correlation-id response-header — записываем в audit.

501 — Not Implemented

Когда Тело Что показать
SSO callback flow (Sprint 20 scaffold) {status: "scaffolded", message, email, next} «SSO ещё не настроено полностью»

503 — Service Unavailable

Когда Тело Что показать
SSO провайдер не сконфигурирован {error: "SSO для X не настроено.", hint: "..."} скрыть кнопку SSO
(резерв на maintenance window) пусто «Сервис недоступен»

humanizeError на фронте

src/lib/api.ts → humanizeError(err):

export function humanizeError(err: AxiosError): string {
  const data = err.response?.data as any
  // 1. Single-error (наш стандарт)
  if (data?.error) return data.error
  // 2. ValidationProblemDetails
  if (data?.errors) {
    const first = Object.values(data.errors).flat()[0]
    return first ?? 'Ошибка валидации'
  }
  // 3. По статусу
  switch (err.response?.status) {
    case 401: return 'Сессия истекла. Войдите снова.'
    case 403: return 'Нет прав на это действие.'
    case 404: return 'Запись не найдена.'
    case 409: return 'Конфликт версий. Перезагрузите страницу.'
    case 413: return 'Файл слишком большой.'
    case 429: return 'Слишком много запросов. Подождите немного.'
    case 500: return `Ошибка сервера. Код: ${err.response.headers['x-correlation-id'] ?? 'unknown'}`
    case 503: return 'Сервис временно недоступен.'
  }
  return err.message ?? 'Неизвестная ошибка'
}

Retry-policy

Код Retry? Условие
401 Один раз — после refresh-token Если refresh тоже 401 → logout
409 c retryable: true Один авто-retry с задержкой 500ms Sprint 23 фикс — серверная сторона уже retry'ит до 5 раз, клиентский — дополнительный safety net
429 Через Retry-After секунд (если есть) Не более 3 попыток
500 НЕТ авто-retry Пользователь сам решает
503 Через 5 секунд До 2 попыток

Без auto-retry: 400, 403, 404, 413, 501.