Commit graph

80 commits

Author SHA1 Message Date
nns b9d9174a61 test(ui-deep): items 4-5 specs + docs
Some checks failed
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 Web / Build + push Web (push) Has been cancelled
Docker Web / Deploy Web on stage (push) Has been cancelled
Item 4 (4 specs): Контрагенты CRUD через modal + ConfirmDialog, Группы
товаров create, Типы цен create, Единицы smoke.

Item 5 (3 specs): Роли (wizard + create), Сотрудники (owner-record,
create через UI с email чтобы createAccount требование выполнилось),
Owner запись не удаляется.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 13:11:47 +05:00
nns eb867697d0 test(ui-deep): setup + Item 1 — signup flow (5 specs)
Some checks are pending
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 Web / Build + push Web (push) Waiting to run
Docker Web / Deploy Web on stage (push) Blocked by required conditions
Sprint UI-deep, пункт 1: реальный Chromium через Playwright Test.
Установлены @playwright/test 1.60.0 и otplib (для item 11).
Конфиг tests/e2e/playwright.config.ts — workers=1, traces+screenshots
on-failure, screenshot dir reports/playwright-artifacts/.

Хелперы tests/e2e/lib/ui.ts:
- apiSignup() — быстрый signup через API + login
- attachSession() — кладёт access_token в localStorage, грузит путь
- watchPage() — listener console-errors и network 4xx/5xx
- expectNoErrors() — assert после flow'a

Item 1 (5 specs, все ✓ на стейдже):
- 1.1 attach session → /dashboard, без console-ошибок
- 1.2 создание товара через UI (Empty CTA → форма → Сохранить)
- 1.3 первый контрагент через Modal
- 1.4 создать товар + контрагент через API, открыть форму приёмки,
       smoke на компоненты страницы
- 1.5 OnboardingPage (/) рендерится

Найден 1 реальный баг → починен:
- ProductEditPage: race на currencies.data — если быстро Сохранить,
  цена-MoneyInput добавляет строку с currencyId='' → server 400 с
  криптичным JSON validation. Фикс: MoneyInput disabled пока
  !currencies.data + canSave проверяет row.currencyId.
- Form error display показывал "Request failed with status code 400";
  теперь использует общий humanizeError() (exporting из @/lib/api).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 12:33:10 +05:00
nns 64af42167b docs(sprint7): пункты 6-7 ✓ + итог по спринту
Some checks are pending
CI / Backend (.NET 8) (push) Waiting to run
CI / Web (React + Vite) (push) Waiting to run
CI / POS (WPF, Windows) (push) Waiting to run
Все 7 пунктов закрыты. Stage прошёл smoke-тест 5/5 после последнего деплоя.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 11:52:00 +05:00
nns 6fc74f8db6 docs(sprint7): пункт 5 ✓ + empty-state screenshot
Some checks are pending
CI / Backend (.NET 8) (push) Waiting to run
CI / Web (React + Vite) (push) Waiting to run
CI / POS (WPF, Windows) (push) Waiting to run
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 11:21:30 +05:00
nns cd83269d3a docs(sprint7): пункт 4 ✓ + skeleton screenshot
Some checks are pending
CI / Backend (.NET 8) (push) Waiting to run
CI / Web (React + Vite) (push) Waiting to run
CI / POS (WPF, Windows) (push) Waiting to run
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 11:07:54 +05:00
nns 56dd9fb639 docs(sprint7): пункт 3 ✓ + toast screenshot script
Some checks are pending
CI / Backend (.NET 8) (push) Waiting to run
CI / Web (React + Vite) (push) Waiting to run
CI / POS (WPF, Windows) (push) Waiting to run
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 10:59:06 +05:00
nns c201625b2b docs(sprint7): пункт 2 ✓ + screenshot script
Some checks are pending
CI / Backend (.NET 8) (push) Waiting to run
CI / Web (React + Vite) (push) Waiting to run
CI / POS (WPF, Windows) (push) Waiting to run
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 10:46:52 +05:00
nns 26959d56d1 docs(sprint7): пункт 1 ✓ (demo-seeder)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 10:22:06 +05:00
nns d89d6bf1dc docs(stage): итоговый отчёт — все 14 пунктов ✓ (94/94 шагов зелёные)
13 stage-сценариев, 94 шага — все green. 6 фиксов в проде:
- EF8 nav-collection (6 контроллеров)
- UNIQUE Article на products
- DateTime Kind=Unspecified→UTC (reports + audit-log)
- Enter.Post → Product.Cost (moving average)
- ABC Pareto по cumBefore
- Swagger operationIds+schemaIds

3 logic gap'a зафиксировано (не P0): stage-public→прод admin,
Enter/Loss без RowVersion, нет CSV-импорта Inventory.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 17:59:04 +05:00
nns a0b985178b test(stage): пункт 14 — POS Sync API 7/7 ✓ (sync + sales с idempotency)
Some checks are pending
CI / Backend (.NET 8) (push) Waiting to run
CI / Web (React + Vite) (push) Waiting to run
CI / POS (WPF, Windows) (push) Waiting to run
GET /api/pos/v1/sync — full snapshot products/prices/stocks/counterparties
с serverTime; since-инкремент работает (products пусто после first sync).
POST /api/pos/v1/sales с idempotency:
- batch-level: повтор того же IdempotencyKey → replayedFromCache=true,
  stock не дублирует списание;
- per-sale: новый IdempotencyKey + тот же ClientSaleId → возвращает
  существующий ServerSaleId (маркер в Notes);
- qty > stock → failed-секция с error, accepted=0.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 17:53:08 +05:00
nns 466595b4d5 fix(swagger): operationId + schemaId — генерация OpenAPI работает
Some checks are pending
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
В Development swagger.json валился двумя ошибками:

1. CustomOperationIds dereferencing api.ActionDescriptor.RouteValues['action']
   для минимальных API (/health, /metrics, /connect/*) кидало
   KeyNotFoundException. Делаем TryGetValue + fallback на RelativePath.

2. CustomSchemaIds с FullName! падал NRE на типах без FullName
   (generic-параметры). Fallback на t.Name через ??.

После фикса: /swagger/v1/swagger.json 200, 117 paths, все 19 новых
модулей (Enter/Loss/Transfer/Inventory/SupplierReturn/Demand/Reports/
AuditLog/2FA/POS/Signup) присутствуют, schemaId без дубликатов.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 17:51:23 +05:00
nns 6b6f27d238 test(stage): пункт 12 — 2FA TOTP 6/6 ✓ (enroll+verify+login flow+disable)
Some checks are pending
CI / Backend (.NET 8) (push) Waiting to run
CI / Web (React + Vite) (push) Waiting to run
CI / POS (WPF, Windows) (push) Waiting to run
TOTP-коды генерируются локально (RFC 6238 / Base32 + HMAC-SHA1)
из sharedKey. End-to-end: signup → enroll → verify (invalid+valid) →
login с otp_code (required+invalid+success) → disable → reset key.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 17:41:22 +05:00
nns 6a5bb52b13 test(stage): пункт 11 — OrgAuditLog 7/7 ✓ + UTC fix
Some checks are pending
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
CRUD продукта генерирует записи create/update/delete с diff'ом
полей; фильтры по entityType/entityId/action работают; multi-tenant
строго (org B не видит логи org A).

Bonus fix: тот же DateTime Kind=Unspecified→UTC что в reports,
применён к from/to в /api/admin/audit-log.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 17:39:54 +05:00
nns 97d5ae5eb0 fix(reports): 3 фикса по итогам stage-тестирования
Some checks are pending
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
1. **DateTime Kind=Unspecified → UTC** в ResolveRange / AsUtc.
   ASP.NET парсит 'from=2026-05-29' с Kind=Unspecified, Npgsql 8
   отказывается слать такие в timestamp with time zone (500).
   Принудительно конвертим Unspecified→UTC (трактуем как полночь
   UTC), Local→ToUniversalTime. Применено к Sales/Profit/ABC/Stock.

2. **Enter.Post теперь пересчитывает Product.Cost** по той же
   формуле скользящего среднего что Supply.Post. Без этого товары,
   попавшие в систему через Оприходование (а не через Supply),
   имели Cost=0 — Profit/ABC-отчёты показывали cost=0 и неверную
   маржу. Воспроизведение: Enter 100@30 + RetailSale 10@500 →
   Profit-отчёт показывал revenue=5000, cost=0 (должно cost=300).

3. **ABC report: Парето-граница по cumBefore (а не cumAfter).**
   Единственный товар с cumShare=100% валился в класс C, хотя
   полностью покрывает Парето — должен быть A. Чиним: товар
   принадлежит классу A если он нужен чтобы пересечь порог
   80% (cumBefore < 80%). Стандартный Парето-алгоритм.

stage-reports (8 шагов): Sales/Stock/Profit/ABC + CSV/XLSX
export + edge — все зелёные.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 17:35:31 +05:00
nns 475c5ca674 test(stage): пункт 9 — Demand 8/8 ✓ (Cash + Credit + post + multi-tenant)
Some checks are pending
CI / Backend (.NET 8) (push) Waiting to run
CI / Web (React + Vite) (push) Waiting to run
CI / POS (WPF, Windows) (push) Waiting to run
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 17:23:03 +05:00
nns 74e14ebeb5 test(stage): пункт 8 — SupplierReturn 8/8 ✓ (CRUD+Post+Unpost+ref validation+multi-tenant)
Some checks are pending
CI / Backend (.NET 8) (push) Waiting to run
CI / Web (React + Vite) (push) Waiting to run
CI / POS (WPF, Windows) (push) Waiting to run
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 17:21:30 +05:00
nns 9df8e0123e test(stage): пункт 7 — CustomerReturn 6/6 ✓ (создание из чека+walk-in+overreturn+multi-tenant)
Some checks are pending
CI / Backend (.NET 8) (push) Waiting to run
CI / Web (React + Vite) (push) Waiting to run
CI / POS (WPF, Windows) (push) Waiting to run
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 17:10:08 +05:00
nns 7d69006a94 test(stage): пункт 6 — Inventory 8/8 ✓ + logic gap по CSV-импорту
Some checks are pending
CI / Backend (.NET 8) (push) Waiting to run
CI / Web (React + Vite) (push) Waiting to run
CI / POS (WPF, Windows) (push) Waiting to run
Auto-populate (lines=null), explicit lines с пересчётом diff, PUT с
изменением actualQty (фикс EF8 nav-collection теперь работает),
Post → корректирующие StockMovement type=InventoryAdjustment, Unpost,
multi-tenant. + Информационный gap: нет CSV-импорта фактических qty,
для оператора склада ввод через JSON-API неудобен.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 17:05:28 +05:00
nns 24c3ff1635 test(stage): пункт 5 — Transfer 7/7 ✓ (CRUD+atomic post+unpost+multi-tenant)
Some checks are pending
CI / Backend (.NET 8) (push) Waiting to run
CI / Web (React + Vite) (push) Waiting to run
CI / POS (WPF, Windows) (push) Waiting to run
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 17:01:48 +05:00
nns 96e0d84f86 docs(stage): пункт 3 done — Enter зелёный, EF8 фикс на 5 контроллеров 2026-05-29 16:57:59 +05:00
nns 4c2841db5b docs(stage): пункт 2 done — каталог CRUD + multi-tenant зелёный, 2 фикса 2026-05-29 16:46:32 +05:00
nns 0511cfacfd test(stage): smoke + signup на test.admin.food-market.kz
Some checks are pending
CI / Backend (.NET 8) (push) Waiting to run
CI / Web (React + Vite) (push) Waiting to run
CI / POS (WPF, Windows) (push) Waiting to run
stage-smoke (5 шагов): signup happy-path, bootstrap (Store/Roles/
Units/ProductGroup/PriceTypes/RetailPoint), login → access+refresh
+ /api/me с правильным orgId+role=Admin, edge-cases (дубликат
email, короткий пароль, пустое название, кривой телефон), проверка
public-сайта.

Informational gap: stage-public (test.food-market.kz) использует тот
же build что прод-public, поэтому его форма signup POST'ит в прод
admin. Для stage-testing регистрируемся напрямую POST на test.admin.

Чек-лист stage-testing: пункт 1 ✓.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 16:29:04 +05:00
nns 4675f38a0f docs(sprint6): P2-4 done — все 5 пунктов выполнены, итог
Some checks failed
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
Docker Web / Build + push Web (push) Waiting to run
Docker Web / Deploy Web on stage (push) Blocked by required conditions
Docker Public / Build + push Public (push) Has been cancelled
Docker Public / Deploy Public on stage (push) Has been cancelled
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 17:59:47 +05:00
nns 971c9b29a5 docs(sprint6): TD-1 done 2026-05-28 17:51:36 +05:00
nns 77c7bb52d1 docs(sprint6): TD-4 done 2026-05-28 17:46:48 +05:00
nns 443eebe862 feat(logging): структурные log-fields в Serilog (TD-4)
LogEnrichmentMiddleware: после Authentication+Authorization вытягивает из
ClaimsPrincipal OrgId (claim org_id) и UserId (sub/NameIdentifier), плюс
CorrelationId из заголовка X-Correlation-ID (или генерирует Guid). Все три
кладутся в Serilog LogContext через PushProperty — каждая ILogger.Log*
внутри пайплайна автоматически получает эти поля как структурные
properties (не текст), пригодные для фильтрации в Loki/ELK без regex.

Эхо CorrelationId в response-header — клиент видит id для support.

Business-логи (структурные плейсхолдеры, не string interpolation):
- Supply.Post → "Supply posted: {SupplyNumber} supplier={SupplierId}
  store={StoreId} lines={LinesCount} total={Total}".
- RetailSale.Post → "RetailSale posted: {SaleNumber} store={StoreId}
  payment={Payment} lines={LinesCount} total={Total}".

docs/logging.md — паттерн, anti-pattern'ы (string interpolation, PII в
логах, токены/пароли), correlation-id workflow.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 17:46:17 +05:00
nns f936cd26c2 docs(sprint6): TD-2 done 2026-05-28 17:42:54 +05:00
nns 8f0773eab3 docs(sprint6): TD-6 done 2026-05-28 17:33:34 +05:00
nns 406fcb9d7d docs(sprint6): чек-лист — RowVersion, FluentValidation, Serilog, MediatR, 2FA
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 17:00:27 +05:00
nns c43f68c39b docs(sprint5): TD-5 done — все 4 пункта выполнены, итог
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 16:47:32 +05:00
nns 2f9bbc858f docs(sprint5): P1-22 done 2026-05-28 16:37:51 +05:00
nns ee0cd3ae86 docs(sprint5): P1-18 done 2026-05-28 16:27:04 +05:00
nns 6b8ec5408a docs(sprint5): P1-5 done 2026-05-28 16:19:15 +05:00
nns 602c0579ec docs(sprint5): чек-лист — Demand, OrgAudit, Email-шаблоны, ImportJob persistence
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 16:10:35 +05:00
nns d3355a9445 docs(sprint4): P1-17 done — все 3 пункта выполнены, итог
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 12:20:50 +05:00
nns 824ef8279c feat(observability): Prometheus метрики /metrics + бизнес-счётчики (P1-17)
prometheus-net.AspNetCore@8.2.1 + EF Core DbCommandInterceptor.

Endpoint: GET /metrics (text exposition, без auth — типичная практика;
на prod закроем nginx allow private-network).

Стандартные метрики (через UseHttpMetrics):
- http_requests_received_total (code/method/controller/action)
- http_request_duration_seconds (histogram, p50/p95/p99 SLO)
- process_cpu_seconds_total / dotnet_total_memory_bytes / GC counters

Кастомные бизнес-метрики (AppMetrics):
- food_market_documents_posted_total{type} — все типы документов
- food_market_sales_posted_total — alias по retail-sale (явно в SLO)
- food_market_supplies_posted_total — alias по supply
- food_market_documents_error_total{type, reason} — ошибки проведения
  с разбивкой по причине (serialization=40001, insufficient_stock,
  number_conflict, validation, other)
- food_market_db_query_duration_seconds{kind} — гистограмма SQL через
  DbMetricsInterceptor (kind=query для SELECT, command для CUD)

Tenant-меток в кастомных метриках НЕТ сознательно: на multi-tenant хосте
раздуло бы cardinality. Per-org разрез — через /api/reports/*.

Counters добавлены в:
- SuppliesController.Post (success + serialization-error)
- RetailSalesController.Post (success)
- PosController.CreateAndPostSaleAsync (success + number_conflict)

docs/observability.md — scrape-конфиг prometheus.yml, образец Grafana
dashboard (4 ряда: Health/Business/Database/Runtime), prometheus rules
с alert'ами (HighErrorRate, DbSerializationContention, NoSalesIn30Min).

Тесты: 3 интеграционных (endpoint доступен и возвращает text/plain с
встроенными метриками; sales counter инкрементится после Post; db_query
гистограмма накапливается).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 12:20:01 +05:00
nns 0854c55d9d docs(sprint4): P1-12b done 2026-05-28 12:10:45 +05:00
nns 1d45f44447 docs(sprint4): P1-12a done 2026-05-28 12:03:36 +05:00
nns d5d185cba3 docs(sprint4): чек-лист — POS Sync API + Prometheus
3 пункта (WPF UI вынесен на Windows-этап).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 12:00:25 +05:00
nns 879e6b8cee docs(sprint3): P1-19 done — все 5 пунктов выполнены, итог
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 11:40:01 +05:00
nns dbd08f6fd2 feat(openapi): улучшенный Swagger + TS-клиент через openapi-typescript (P1-19)
API:
• SwaggerGen с OpenAPI info (title/version/description),
  Bearer security-scheme (через OpenIddict JWT),
  стабильные operationId = Controller_VerbAction (HTTP-verb включён
  чтобы избежать коллизии когда ASP.NET стрипает Async-суффикс —
  WipeAll и WipeAllAsync ранее давали одинаковый operationId);
• CustomSchemaIds с префиксом из namespace (одноимённые nested
  record'ы в разных контроллерах больше не схлопываются — StockRow
  есть в Inventory_StockController и Reports_StockReportController).

UI:
• /swagger (UI) и /swagger/v1/swagger.json (документ) — только в Development.
  На prod не раскрываем (endpoint enumeration).

Web:
• Добавлен devDependency openapi-typescript@^7.5.2 + npm-script gen:api,
  читающий http://localhost:5081/swagger/v1/swagger.json.
• src/lib/api.generated.ts — сгенерированные типы (~7700 строк, все
  схемы и operations).
• src/lib/apiClient.ts — тонкая обёртка над axios api, использующая
  типы из generated. Подключена для пары контроллеров (Reports/Sales,
  Reports/ABC, Reports/Profit) как образец постепенной миграции.

docs/openapi.md — workflow генерации (live API или Swashbuckle CLI),
versioning, наставления для нового кода.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 11:39:22 +05:00
nns 96fa4bf990 docs(sprint3): P1-11 done
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 11:24:38 +05:00
nns 9795eeeafc docs(sprint3): P1-10 done
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 11:19:32 +05:00
nns 3ded4db73a docs(sprint3): P1-9 done
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 11:14:41 +05:00
nns 76380d86bf docs(sprint3): P1-8 done
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 11:10:05 +05:00
nns 8111574a08 docs(sprint3): чек-лист — 5 отчётов P1
5 пунктов: Sales/Stock/Profit/ABC отчёты + OpenAPI клиент.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 10:30:28 +05:00
nns a7b82eea86 docs(sprint2): P1-16 done — все 7 пунктов выполнены, итог
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 10:07:53 +05:00
nns 18d3c3aa1e docs(sprint2): P1-7 done
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 09:58:41 +05:00
nns cc9289ef75 docs(sprint2): P1-6 done
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 09:51:16 +05:00
nns 561291f226 docs(sprint2): P1-4 done
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 09:39:48 +05:00