Commit graph

141 commits

Author SHA1 Message Date
nns ba54155225 fix(stage): rate-limit 5/min на /connect/token, nginx route /metrics+/swagger, Swagger в Production через IncludeSwagger
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) Has been cancelled
Docker Web / Deploy Web on stage (push) Has been cancelled
Verify-Sprint баги A-D:
- A: на stage docker-compose.yml был "RateLimiting__PerMinute=200" — убран,
  теперь работают дефолты (5/мин, 20/час). 6-я попытка с тем же IP/паролем → 429.
- B: web-контейнер nginx не имел location = /metrics → запрос ловился SPA
  fallback'ом (index.html, 947 байт). Добавлен proxy_pass на api:8080.
- C: web-nginx не имел location /swagger/ → swagger.json возвращал SPA HTML.
  Добавлены /swagger/ + редирект /swagger → /swagger/.
- D: Swagger подключался только в Development. Добавлен флаг IncludeSwagger
  (env IncludeSwagger=true) — Program.cs включает UseSwagger() и в Production
  если флаг выставлен. На prod admin.food-market.kz флаг не ставим.

Проверено через https://test.admin.food-market.kz:
- 6 неверных логинов подряд: 1-5 → 400, 6-7 → 429 ✓
- /metrics → 14967 байт prometheus exposition ✓
- /swagger/v1/swagger.json → 422 КБ openapi 3.0.1 ✓
- /swagger/ → swagger-ui (redirect на /swagger/index.html) ✓

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-04 01:36:42 +05:00
nns a1cccdeef5 docs(sprint9): итог — все 4 пункта ✓, stage 8/8 e2e зелёные
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 21:38:26 +05:00
nns 76a175f491 feat(pwa+mobile+s9): PWA owner read-only + mobile tweaks + S9 stage 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 9 пункт 3 (mobile-адаптация):
- DataTable: min-w-max sm:min-w-[640px] → узкие таблицы (Loyalty, Promotions)
  влезают на 375px без horizontal-scroll, широкие (Products) скроллятся
  внутри overflow-auto родителя.
- Mobile-audit спека (stage-ui-s9-mobile-audit) — 20 screenshot'ов в
  reports/mobile/ (375 + 768 viewport × 10 страниц + seed-demo).
  Smoke: no console-errors, layouts читаемы.

Sprint 9 пункт 4 (P2-9 PWA):
- public/manifest.webmanifest — read-only PWA владельца. Shortcuts:
  Дашборд, Sales/Profit/Stock отчёты. display=standalone (homescreen icon).
- public/sw.js — service worker:
  • SPA navigate: network-first + offline-fallback на /offline.html.
  • GET /api/*: network-first + cache-fallback (read-only кеш).
  • CSS/JS/SVG: stale-while-revalidate.
  • Мутации (POST/PUT/DELETE): не вмешиваемся, сеть.
- public/offline.html — статический fallback с кнопкой «Открыть дашборд».
- index.html: <link rel='manifest'>, apple-touch-meta, lang=ru-KZ.
- main.tsx: navigator.serviceWorker.register('/sw.js') в production only
  (dev hot-reload не мешает).
- deploy/nginx.conf: /sw.js no-cache, /manifest.webmanifest правильный
  content-type, /offline.html static.

Stage e2e:
- stage-ui-s9-loyalty.spec (4/4 ✓): programs/cards/promotions endpoints
  + UI рендер + SALE20 на 500₸ → total=400 (валидно через API).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 21:22:30 +05:00
nns 91128a7ed0 feat(loyalty+promotions): P2-12 + P2-13 — лояльность и промокоды (Sprint 9 п.1-2)
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) Waiting to run
Docker Web / Deploy Web on stage (push) Blocked by required conditions
Docker API / Build + push API (push) Has been cancelled
Docker API / Deploy API on stage (push) Has been cancelled
Domain:
- LoyaltyProgram { Type=Percentage|FixedAmount|PointsAccrual, Rate,
  MinSubtotal, IsActive } — org-scoped.
- LoyaltyCard { ProgramId, CounterpartyId, CardNumber unique per org,
  Balance, IsBlocked }.
- Promotion { Type=Percent|FixedDiscount, Value, Scope=All|ProductGroups|
  Products, Code unique per org, period, ProductGroupIds/ProductIds (jsonb) }.
- RetailSale: LoyaltyCardId, LoyaltyBonusApplied, LoyaltyPointsAccrued,
  PromotionId, PromotionCode (snapshot), PromotionDiscount.

EF:
- SalesConfigurations: indexes, FK Restrict, jsonb-converters для Guid-
  списков Promotion (ValueComparer для change-tracker).
- Phase9b миграция: 3 таблицы + 6 колонок на retail_sales.
- RolePermissions: LoyaltyManage, PromotionsManage добавлены (попадают
  в All() для Admin).

API:
- /api/loyalty/programs CRUD (Get/List/Create/Update/Delete; запрет delete
  при существующих картах → 409).
- /api/loyalty/cards CRUD + /issue + /{id}/block + /{id}/unblock + /lookup
  (POS использует при оплате — 404 если нет, 409 если blocked/inactive).
- /api/promotions CRUD; код уникален per org (БД-индекс + 23505 → 409).
- RetailSale.Create/Update: новые поля input.LoyaltyCardNumber +
  input.PromotionCode. Метод ApplyLoyaltyAndPromotionAsync:
  • Lookup карты, проверка active/blocked/MinSubtotal.
  • Расчёт скидки или баллов в зависимости от Type.
  • Lookup промокода, проверка периода/MinSaleAmount/scope.
  • MatchingSubtotal для Scope=ProductGroups/Products считаем по
    input.Lines (sale.Lines ещё пустой в этот момент).
  • Финальный Total = Subtotal - DiscountTotal - LoyaltyBonusApplied
    - PromotionDiscount, max(0).
- RetailSale.Post: начисление баллов на LoyaltyCard.Balance (внутри
  транзакции, чтобы rollback не оставил orphan баллы).

UI:
- /loyalty/programs — list + create/edit modal с Type/Rate/MinSubtotal.
- /loyalty/cards — list + issue modal (Program select + AsyncSelect
  counterparty + CardNumber).
- /promotions — list + create/edit modal (Type/Value/период/MinSaleAmount/Code).
- Sidebar: новый блок «Продажи» с пунктами Промокоды/Программы/Карты
  (Admin-only).
- i18n: ru.json + en.json пополнены nav-ключами.

Тесты:
- LoyaltyFlowTests (3/3 ✓): percentage уменьшает Total на 10%, points-accrual
  пополняет Balance после Post, multi-tenant lookup→404 чужой org.
- PromotionFlowTests (2/2 ✓): SALE20 уменьшает Total на 20%, невалидный
  код→400 с понятной message и field=promotionCode.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 21:06:10 +05:00
nns a5314b5be9 test(s8-4): MinIO stage e2e + final progress
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
Все 4 пункта спринта 8 закрыты. Stage 8/8 e2e зелёные.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 20:25:19 +05:00
nns d451e77642 test(s8-3): i18n stage e2e
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-31 20:09:40 +05:00
nns 749829c12f test(s8-2): telegram stage e2e
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-31 19:57:03 +05:00
nns abace49a45 test(s8-1): SignalR stage e2e + progress
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-31 19:42:33 +05:00
nns dd2e1e7af2 feat(realtime): SignalR hub /hubs/notifications per-org + dashboard live
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
Docker Web / Build + push Web (push) Waiting to run
Docker Web / Deploy Web on stage (push) Blocked by required conditions
P2-7 Sprint 8 пункт 1.

Backend:
- src/food-market.api/Realtime/NotificationsHub.cs — SignalR-хаб, группы
  org:{orgId:N}. JWT через Authorization-хедер (стандартно) или через
  query ?access_token=... (для WebSocket — браузерные не могут слать
  кастомные хедеры). SuperAdmin override через ?orgOverride=<id>.
- NotificationsPublisher.cs — singleton, IHubContext-обёртка.
- Program.cs — AddSignalR + MapHub. Middleware копирует ?access_token=
  в Authorization для /hubs/* до UseAuthentication.
- RetailSalesController.Post → публикует SalePosted + LowStockPayload
  если после движения товара остаток < MinStock. Best-effort: notify
  ошибка не валит транзакцию.
- SuppliesController.Post → SupplyPosted.

Events (camelCase в JSON):
- SalePosted { saleId, number, total, storeId, cashierName, retailPointId, postedAt }
- SupplyPosted { supplyId, number, total, supplierId, supplierName, postedAt }
- LowStock { productId, productName, storeId, storeName, quantity, minStock }

Web:
- @microsoft/signalr 10.0.0 client.
- src/lib/useNotificationsHub.ts — hook с автореконнектом, accessTokenFactory.
- DashboardPage:
  • liveRevenueDelta / liveCountDelta — оптимистическое приращение
    «Выручка сегодня» сразу при SalePosted (до refetch stats);
  • toast.info на SupplyPosted; toast.error на LowStock;
  • Wifi/WifiOff индикатор в header.

Тесты:
- SignalRNotificationsTests: A постит retail-sale → A получает SalePosted,
  B (другая org) НЕ получает — multi-tenant. ✓ 1/1 локально.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 19:29:59 +05:00
nns 51aae4482f test(ui-deep): items 10-14 — все 59/59 ✓ на стейдже
Some checks failed
CI / Backend (.NET 8) (push) Has been cancelled
CI / Web (React + Vite) (push) Has been cancelled
CI / POS (WPF, Windows) (push) Has been cancelled
Item 10 (2 specs): OrgAuditLog после seed-demo — записи видны, diff раскрывается.

Item 11 (4 specs): 2FA flow через API (UI 2FA пока не реализован).
Самодельная TOTP-генерация (RFC 6238) на crypto.createHmac sha1 —
без otplib v13 plugin'ов.

Item 12 (4 specs): неверный пароль — читаемая ошибка не «Request failed».
Forgot-password + login OK happy-path. Known: за 10 попыток login не
получили 429 — rate-limit possibly disabled.

Item 13 (5 specs, P0): multi-tenant изоляция HOLDS. GET/PUT/DELETE
товара A с токеном B → все 404/403, UI B не видит имя/данные A.

Item 14 (5 specs): mobile viewport 375x667 — sidebar схлопывается,
drawer открывается+закрывается, products list без horizontal overflow,
ConfirmDialog влезает.

Итого: 59 specs, найдены 6 багов (починены), 2 known issues
(Supply lost-update, login rate-limit).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 13:53:57 +05:00
nns 8b6d139e3e test(ui-deep): items 6-9 — Supply/RetailSale/InventoryDocs/Reports
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
Item 6 (3 specs): Supply UI + найден P2 баг lost-update (нет ETag).
Item 7 (4 specs): RetailSale + CustomerReturn — oversell/underpayment.
Item 8 (5 specs): 6 doc-форм Submit state, Transfer From≠To, CSV-import.
Item 9 (6 specs): Sales/Stock/Profit/ABC + CSV download через
waitForEvent + XLSX endpoint validation.

lib/ui.ts: signup timeout=60s + ignore network-flake console errors.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 13:37:01 +05:00
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
nns 6254b61caa docs(sprint2): P1-3 done
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 09:32:47 +05:00
nns ab74d06706 docs(sprint2): P1-2 done
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 09:24:52 +05:00
nns 683d64dc9c docs(sprint2): P1-1 done
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 09:18:25 +05:00
nns 880be11bd8 docs(sprint2): чек-лист — 7 документов учёта (P1)
7 пунктов: Enter, Loss, Transfer, Inventory, CustomerReturn,
SupplierReturn, Hangfire dashboard.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 09:04:35 +05:00
nns 9ef9df3ed5 docs(sprint1): P1-21 done — все 9 пунктов выполнены, итог
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 03:16:25 +05:00
nns e988a7dbbc docs(sprint1): P1-20 done
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 03:02:06 +05:00
nns b613adf558 docs(sprint1): P0-9 done
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 02:52:24 +05:00
nns 99c1f1b780 docs: чек-лист релиза (P0-9)
Пред/во время/после выкатки: предусловия (.env/issuer/сертификаты/бэкап-таймер),
тесты+миграции перед релизом, бэкап, деплой через CI, smoke после (health/ready,
логин, ключевые потоки, permission 403, rate-limit 429), откат, прод. Ссылается на
secrets.md / backup-restore.md / openiddict-keys.md / stage-setup.md.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 02:52:16 +05:00
nns 37e9d28f69 docs(sprint1): P0-8 done
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 02:51:23 +05:00
nns 45326281f9 docs(deploy): .env.example + secrets.md, проброс OpenIddict env в compose (P0-8)
deploy/.env.example — все required/опц. переменные (POSTGRES_PASSWORD, REGISTRY,
*_TAG, OPENIDDICT_ISSUER/CERT_PASSWORD, FM_* бэкапа, Cors/RateLimiting/MoySklad).
docs/secrets.md — таблица переменных, где живут секреты (SMTP в БД, сертификаты в
volume), ротация, гигиена. compose: api получает OpenIddict__Issuer (за прокси
обязателен) и OpenIddict__CertPassword из .env. compose config валиден.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 02:51:13 +05:00
nns 5b981dd34b docs(sprint1): P0-6 done
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 02:49:18 +05:00
nns 7c34bb1abd feat(deploy): авто-бэкап БД+uploads — systemd timer/service + скрипт (P0-6)
food-market-backup.sh: pg_dump -Fc контейнера + tar uploads, ротация 30 дней,
атомарная запись через .tmp+mv. food-market-backup.{service,timer} — ежедневно
03:00 с догоном пропущенных. docs/backup-restore.md — установка таймера, ручной
бэкап, восстановление БД (drop+create / --clean) и uploads, проверка дампа.

Скрипт проверен против food-market-postgres: дамп PGDMP custom-format,
248 TOC, pg_restore --list читает. Установку на prod-vm не делаем — только артефакты.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 02:49:08 +05:00
nns 744847661d docs(sprint1): P0-1 done
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 02:47:12 +05:00
nns 422b7ad5ea feat(auth): prod X509-ключи OpenIddict с persistent self-signed (P0-1)
OpenIddictKeyConfigurator: dev — прежний RSA-ключ в App_Data (поведение не
менялось, шифрование access-token выключено); prod/stage — отдельные X509
сертификаты подписи и шифрования из конфига (OpenIddict:SigningCertPath /
EncryptionCertPath / CertPassword, можно env). Нет файла → генерируется
persistent self-signed (RSA 2048, 5 лет) и сохраняется в App_Data (volume),
а не dev-ephemeral — токены переживают рестарт.

Проверено: prod выдаёт 5-сегментный JWE, /api/me 200; рестарт → те же
сертификаты (fingerprint совпал), pre-restart токен валиден. dev — 3-сегментный
JWT, /api/me 200. docs/openiddict-keys.md.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 02:47:00 +05:00
nns 00964f587a docs(sprint1): P0-5 done
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 02:42:07 +05:00
nns 2e98e384f5 docs(sprint1): P0-4 done
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 02:23:59 +05:00
nns 6f1566c2c3 docs(sprint1): P0-3 done
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 02:20:12 +05:00
nns a15100f3bc docs(sprint1): чек-лист прогресса стабилизации
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 02:14:25 +05:00
nns 35d70c5d80 docs: ТЗ на доработку и тестирование (полный аудит 2026-05-22)
Два документа после полного обхода кодовой базы:
- docs/TZ-доработка.md — что нужно сделать, P0/P1/P2 приоритеты,
  дорожная карта по спринтам, технический долг
- docs/TZ-тестирование.md — сценарии тестирования по модулям,
  multi-tenant изоляция, регрессионный чек-лист, стратегия
  покрытия unit/integration/E2E

Сводка готовности: ядро (auth/catalog/Supply/RetailSale/multi-tenancy)
85-95%, но критичные пробелы: ОФД, складские документы Enter/Loss/
Transfer/Inventory, Demand, отчёты, POS-приложение, observability.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 15:30:04 +05:00
nns fc9f7c9ee4 docs(audit): полный аудит цепочки авторизации — 2026-05-06
Some checks failed
CI / POS (WPF, Windows) (push) Waiting to run
CI / Backend (.NET 8) (push) Successful in 53s
CI / Web (React + Vite) (push) Successful in 42s
Docker API / Build + push API (push) Successful in 1m19s
Docker Web / Build + push Web (push) Successful in 35s
Docker API / Deploy API on stage (push) Failing after 38s
Docker Web / Deploy Web on stage (push) Successful in 13s
Завершающий пункт пакета фиксов по ролям/валидации/удалению. Обход:
1. /connect/token — IsActive + BelongsToLiveOrg + SuperAdmin bypass.
2. JWT cookie vs Bearer — все три AuthN-схемы переопределены в
   OpenIddictValidationAspNetCoreDefaults; cookie не активна для API.
3. X-Org-Override — фильтрует по IsInRole(SuperAdmin), подделать нельзя.
4. Tenant query filters — ITenantEntity и IOptionalTenantEntity
   подключаются через reflection, фильтр консистентен с tenant.context.
5. Smoke per-role — sidebar+RoleGuard за один проход покрывает все
   tenant-роуты; tenant-admin на /super-admin URL → описан risk + future fix.
6. Reset password / deactivate account — токены revoke в БД одним SQL.
7. Catch-22 для SuperAdmin платформы — он не Employee и не имеет
   OrganizationId, через текущие endpoint-ы deactivate невозможен.

Findings разбиты на critical (закрыто этим пакетом), high/medium (не
закрыто — будущая серия) и low (косметика).
2026-05-06 11:32:07 +05:00
nns 633bdf3ef0 fix(auth): закрыть критические дыры — orphan login, self-delete, owner-delete, override-баннер
Some checks are pending
CI / POS (WPF, Windows) (push) Waiting to run
CI / Backend (.NET 8) (push) Successful in 46s
CI / Web (React + Vite) (push) Successful in 41s
Docker API / Build + push API (push) Successful in 1m12s
Docker Web / Build + push Web (push) Successful in 31s
Docker API / Deploy API on stage (push) Successful in 17s
Docker Web / Deploy Web on stage (push) Successful in 12s
Аудит 2026-04-27. Полный отчёт — docs/audit-2026-04-27.md.

Что закрыто:

— /connect/token (AuthorizationController) теперь отказывает в login если
  AppUser привязан к удалённой/архивной Organization. SuperAdmin обходит
  проверку (ему org не нужна). Жалоба: nurnetps@gmail.com мог логиниться
  после удаления своей org из SuperAdmin консоли.

— SuperAdminOrganizationsController.Delete (DELETE org) каскадно
  деактивирует всех AppUser привязанных к этой org (IsActive=false,
  OrganizationId=null) и помечает Status='revoked' для всех их
  OpenIddictTokens. Раньше Org удалялась, а юзеры оставались валидными
  с активными refresh-tokens на 30 дней.

— EmployeesController.Delete теперь soft-delete (IsActive=false,
  FiredAt). Запрещены: 403 если попытка удалить себя; 403 если
  попытка удалить Owner (Organization.AccountOwnerUserId ==
  employee.UserId). Сообщения с инструкцией («передайте права»,
  «покинуть через настройки»).

— /api/me возвращает hasLiveOrg и hasActiveEmployee — frontend
  использует это для редиректа на /no-organization вместо белого экрана.

— Новая страница /no-organization (NoOrganizationPage) — fallback для
  orphan AppUser. CTA: создать новую org через публичный /signup
  или попросить инвайт. Кнопка «выйти». TenantRouteGuard редиректит
  orphan юзеров туда.

— SuperAdminAsOrgBanner: добавлена проверка через useMe — баннер
  рендерится только если у текущего юзера есть Identity-роль
  SuperAdmin. Lingering localStorage override от прошлой сессии
  (другой юзер логинился до этого) автоматически чистится.

— auth.ts: clearTokens() теперь сбрасывает superAdminAsOrg и
  superAdminEditMode. login() вызывает clearTokens() ПЕРЕД запросом
  чтобы новый юзер не унаследовал override-состояние от предыдущего.

— deploy/recovery-restore-orphan-owners.sql — идемпотентный скрипт
  деактивирующий уже накопленных orphan AppUser (как nurnetps) и
  revoke их токены. Применён на стейдже: 1 user деактивирован,
  9 токенов revoked.

— deploy/Dockerfile.api: убран `--no-restore` из publish — два
  раздельных шага роняли build с NETSDK1064 на свежих analyzer-
  зависимостях, теперь restore идёт внутри publish.

Smoke (стейдж):
- nurnetps@gmail.com /connect/token → invalid_grant.
- admin@food-market.local /connect/token → access_token выдан.
- food-market.zat.kz/, /signup/, app.../login, /health → 200.
2026-04-27 09:28:18 +05:00
nurdotnet 55a63a6446 docs: audit of our domain entities vs. live OtherSystem API
Cross-checked every entity (Product, Counterparty, Supply, RetailSale,
Stock, Store, RetailPoint, Organization, ProductGroup, Barcode, Price,
PriceType, Country, Currency, VatRate, UoM) against real responses from
OtherSystem's API — a flat list of:
 - fields we have and MS doesn't (to justify or drop)
 - fields MS has and we don't (to add)
 - semantic mismatches (e.g. MS holds prices in kopecks, our decimal)

Report only, no code changes — to be discussed with the user before
touching models/migrations. Priorities are split into P1 (import
parity: ExternalCode, Code, TrackingType enum, PaymentItemType, KZ
entrepreneur type), P2 (semantic fixes: RetailSale payment sums,
Overhead on supply, legal fields on Organization), P3 (nice-to-have),
and a list of deliberate divergences (why our VatRate/StockMovement
exist even though MS doesn't model them that way).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 12:57:06 +05:00
nurdotnet 9facd4845d ops: Forgejo on git.zat.kz as primary, GitHub as mirror
Pushing straight to GitHub from KZ is a lottery — TCP to github.com
times out often enough that git push becomes a flake. Fix: Forgejo runs
on the stage server (sqlite, single container), all pushes go there
first (local network, always reliable), a systemd timer mirrors the
whole repo into GitHub every 10 minutes so GitHub stays up-to-date as
a backup + CI source.

What's committed here is the infra-as-code side:
- deploy/forgejo/docker-compose.yml — Forgejo 7 on :3000 (HTTP) and :2222 (SSH)
- deploy/forgejo/food-market-forgejo.service — systemd unit that drives compose
- deploy/forgejo/mirror-to-github.sh + mirror timer/service — push to GH every 10 min
- deploy/forgejo/nginx.conf — vhost for git.zat.kz (certbot to be run once DNS is set)
- docs/forgejo.md — how to clone/push, operations, what's left for the user (DNS + certbot)

GitHub Actions CI is untouched: commits land on GitHub via the mirror
and the self-hosted runner picks them up as before.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 12:27:45 +05:00
nurdotnet 36da65693d feat(ops): Telegram <-> tmux bridge + local docker-registry unit
Telegram bridge lets me drive the local Claude Code tmux session from my
phone — inbound messages are typed into the 'claude' session, pane diffs
are streamed back as plain Telegram messages (TUI noise, tool-call
blocks, echoed user input and already-sent lines are filtered so only
the assistant's actual reply reaches the chat). Deployed as
food-market-telegram-bridge.service, reads creds from
/etc/food-market/telegram.env (not committed).

Also committing the local docker-registry unit for reproducibility —
registry:2 on 127.0.0.1:5001, data persisted in
/opt/food-market-data/docker-registry.

Setup docs in docs/telegram-bridge.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 10:53:45 +05:00
nurdotnet 8ca0886f36 docs(stage): switch stage subdomain to food-market.zat.kz
User clarified: zat.kz is the project's domain (not space-time.kz, which
hosts the unrelated legacy food-market-server). Future prod will be on
food-market.kz once purchased.

- Updated /etc/nginx/conf.d/food-market-stage.conf on server: server_name
  food-market.zat.kz, proxies to docker stage on :8081.
- docs/stage-access.md: all references switched to food-market.zat.kz.
- Memory updated to record domain plan.

Once a DNS A-record food-market.zat.kz → 88.204.171.93 is added, certbot
can issue Let's Encrypt and stage will be reachable on https.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 20:31:20 +05:00
nurdotnet 9739506dc8 docs(stage): how to expose stage externally — DNS + cerbot vs proxmox port
Stage deploy lands the api+web containers fine (last deploy succeeded), but
external 8080/8081 are blocked at the Proxmox/provider firewall (verified:
ufw on the VM is inactive, so the block is upstream).

Added /etc/nginx/conf.d/food-market-stage.conf on the server: vhost on
port 80 (which IS open) for server_name food-market-stage.space-time.kz
proxying to 127.0.0.1:8081. Once a DNS A-record is added, certbot can
issue Let's Encrypt — same pattern the existing food-market-server uses.

docs/stage-access.md — runbook with the three options (DNS subdomain,
open Proxmox port, SSH tunnel for quick test).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 20:14:00 +05:00
nurdotnet ce62561257 ci/deploy: stage deploy workflow + notifications + server plan
.github/workflows/deploy-stage.yml:
- Triggers on successful "Docker Images" workflow or manual dispatch.
- SSHes to stage server via STAGE_SSH_KEY, copies deploy/docker-compose.yml
  and nginx.conf, writes .env with current SHA + POSTGRES_PASSWORD.
- `docker compose pull && up -d --remove-orphans`.
- Smoke-tests /health with 5 retries (5s each).
- Pings Telegram on success/failure with commit SHA + stage URL.

.github/workflows/notify.yml:
- Separate workflow_run listener for CI/Docker failures, sends Telegram
  message with link to the failed run.

deploy/docker-compose.yml port remap (stage server already uses 80/443/5000/5432):
- API: 8080 (was 8080, confirmed free)
- Web: 8081 (was 80 — taken by legacy nginx)
- Postgres: 127.0.0.1:5434 (was 5433 — and now localhost-only, safer)

docs/stage-setup.md — one-time server setup runbook:
- Verified specs: Ubuntu 24.04, 4 CPU, 15 GB RAM, 4 GB free disk (tight).
- Step 1: `sudo usermod -aG docker nns` so deploy doesn't need sudo.
- Step 2: generate STAGE_POSTGRES_PASSWORD secret via `openssl rand`.
- Step 3: port-conflict check.
- Step 4: first manual deploy via gh workflow run.
- Disk-usage monitoring via cron → Telegram when >85%.

Secrets now in repo:
  TELEGRAM_BOT_TOKEN, TELEGRAM_CHAT_ID,
  STAGE_SSH_HOST, STAGE_SSH_PORT, STAGE_SSH_USER, STAGE_SSH_KEY
Still needed from user: STAGE_POSTGRES_PASSWORD (one openssl command).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 13:46:03 +05:00
nurdotnet 56b95d70e2 ci: move POS (Windows, 2x multiplier) to tag/manual only; document budget
Each push previously burned ~21 billable GitHub Actions minutes because the
Windows POS build cost 10 (5 real × 2x Windows multiplier). That gives us
~95 pushes/month on the 2000-minute free tier — too tight for active dev.

- POS job now gates on `startsWith(github.ref, 'refs/tags/v')` OR
  workflow_dispatch. Every-commit CI stays Linux-only.
- CI trigger adds `tags: ['v*']` and workflow_dispatch so releases can build
  the .exe on demand.
- docs/24x7.md: new table with per-job minute/multiplier breakdown and the
  break-even point where a self-hosted runner becomes cheaper (~200 commits/mo).

Post-change estimate: ~11 billable min/commit → fits 180 commits/month in
the free tier. Windows minutes only spent when tagging a release.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 11:36:29 +05:00
nurdotnet dfa7ce075a ci/deploy: GitHub Actions + Docker images + DB backup + 24x7 plan
.github/workflows/ci.yml — on push/PR:
  - backend job: dotnet restore/build/test with a live postgres service
  - web job: pnpm install + vite build + tsc, uploads dist artifact
  - pos job: windows-latest, dotnet publish self-contained win-x64
    single-file exe as artifact

.github/workflows/docker.yml — on push to main (if src changed) or manual:
  - api image → ghcr.io/nurdotnet/food-market-api:{latest,sha}
  - web image → ghcr.io/nurdotnet/food-market-web:{latest,sha}
  - uses buildx + GHA cache

deploy/Dockerfile.api — multi-stage (.NET 8 sdk → aspnet runtime),
  healthcheck on /health, App_Data + logs volumes mounted.

deploy/Dockerfile.web — node20 build → nginx 1.27 runtime; ships the
  Vite dist + nginx.conf that proxies /api, /connect, /health to api
  service and serves the SPA with fallback to index.html.

deploy/nginx.conf — SPA + API reverse-proxy configuration.

deploy/docker-compose.yml — production-shape stack: postgres 16 +
  api (from ghcr image) + web (from ghcr image), named volumes, env-
  driven tags so stage/prod can pin specific SHAs.

deploy/backup.sh — pg_dump wrapper with 3 modes: local (brew
  postgres), --docker (compose container), --remote HOST:PORT. Writes
  gzipped dumps to ~/food-market-backups, 30-day retention.

docs/24x7.md — explains where Claude/CI/stage live, which pieces
  depend on the Mac, and the exact steps to hand off secrets via
  ~/.food-market-secrets/ so I can push them into GitHub Secrets.

Next, once user supplies Proxmox + FTP + Telegram creds: stage deploy
workflow, notification workflow, and (optional) claude-runner VM so
I no longer depend on the Mac being awake.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 11:26:01 +05:00
nns fd2f5ae4f3 Phase 0: project scaffolding and end-to-end auth
- .NET 8 LTS solution with 7 projects (domain/application/infrastructure/api/shared/pos.core/pos[WPF])
- Central package management (Directory.Packages.props), .editorconfig, global.json pin to 8.0.417
- PostgreSQL 14 dev DB via existing brew service; food_market database created
- ASP.NET Identity + OpenIddict 5 (password + refresh token flows) with ephemeral dev keys
- EF Core 8 + Npgsql; multi-tenant query filter via reflection over ITenantEntity
- Initial migration: 13 tables (Identity + OpenIddict + organizations)
- AuthorizationController implements /connect/token; seeders create demo org + admin
- Protected /api/me endpoint returns current user + org claims
- React 19 + Vite 8 + Tailwind v4 SPA with TanStack Query, React Router 7
- Login flow with dev-admin placeholder, bearer interceptor + refresh token fallback
- docs/architecture.md, CLAUDE.md, README.md

Verified end-to-end: health check, password grant issues JWT with org_id,
web app builds successfully (310 kB gzipped).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 13:59:13 +05:00