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

160 lines
8.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# API error catalog
Каталог HTTP-кодов и тел ответов, которые возвращает `food-market.api`.
Используется фронтом для `humanizeError(response)` и QA для regression
проверки. Если поле `error` есть — это user-facing сообщение; `errors`
(множественное) — структурированные ошибки валидации (ASP.NET
ValidationProblemDetails).
## Формат
```jsonc
// Универсальный шаблон 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)`:
```typescript
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.